Go中不应使用interface{}传驱动实例,因会丧失编译期类型检查、导致运行时panic且无法静态推导驱动能力;应定义DBExecutor和DriverFactory接口,通过显式注册协议前缀映射实现驱动自动识别,并统一处理列名归一化与特性隔离。

为什么不用 interface{} 直接传驱动实例
抽象工厂在 Go 里不是靠“定义一堆空接口”来体现的,而是靠一组有明确契约的工厂方法。用 interface{} 接收驱动,等于放弃编译期类型检查,后续调用 Query、Exec 时容易 panic,而且无法静态推导哪个驱动实现了哪些能力(比如事务支持、批量插入语法差异)。
正确做法是先定义数据访问层契约:
type DBExecutor interface {
Query(query string, args ...any) ([]map[string]any, error)
Exec(query string, args ...any) (int64, error)
}
type DriverFactory interface {
NewExecutor(dsn string) (DBExecutor, error)
}
每个具体驱动(mysql.Factory、postgres.Factory)实现 DriverFactory,返回各自封装好的 DBExecutor 实现。
如何让工厂自动识别 DSN 并匹配驱动
硬编码 if strings.HasPrefix(dsn, "mysql://") 看起来快,但会把解析逻辑和工厂耦合,测试难写,新增驱动还得改老代码。
立即学习“go语言免费学习笔记(深入)”;
更可控的方式是显式注册 + 前缀映射:
- 维护一个全局
map[string]DriverFactory,key 是协议名("mysql"、"pg") - DSN 解析统一走
url.Parse,取u.Scheme作为查找 key - 未注册协议时 panic 或返回明确错误(
"unsupported driver scheme: sqlite3"),不静默 fallback
示例片段:
var factories = map[string]DriverFactory{
"mysql": mysql.NewFactory(),
"pg": postgres.NewFactory(),
}
func GetExecutor(dsn string) (DBExecutor, error) {
u, err := url.Parse(dsn)
if err != nil {
return nil, err
}
f, ok := factories[u.Scheme]
if !ok {
return nil, fmt.Errorf("unsupported driver scheme: %s", u.Scheme)
}
return f.NewExecutor(dsn)
}
不同驱动对 Query 返回结构的处理差异
MySQL 驱动通常返回列名小写,PostgreSQL 默认大小写敏感且可能带双引号;SQLite 不支持 RETURNING,而 PG 支持,这直接影响 Exec 后能否拿到自增 ID。
抽象层不能假装这些差异不存在。建议:
-
DBExecutor.Query统一返回[]map[string]any,但要求各实现内部做列名 normalize(例如全转小写或按配置保留原样) - 避免在抽象层拼 SQL,尤其别写
"INSERT ... RETURNING id"这种 PG 专属语法;如需 ID,拆成两步:先Exec,再用驱动特有方式获取(lastInsertId()或pgconn.CommandTag.RowsAffected()) - 如果业务强依赖某驱动特性(如 MySQL 的
ON DUPLICATE KEY UPDATE),不要塞进抽象接口,而是暴露RawExecutor()方法向下透出底层连接
测试时怎么绕过真实数据库又不写大量 mock
直接 mock DBExecutor 接口太重——每个测试都要手写 Query 返回值,且容易漏覆盖 error 分支。
更轻量的做法是用内存型驱动替代:
- MySQL 场景可用
github.com/go-sql-driver/mysql+github.com/bradfitz/gomemcache/memcache不适用,换用github.com/mattn/go-sqlite3起一个临时 DB(file::memory:) - PostgreSQL 可用
github.com/jackc/pgx+testcontainers-go启容器,但本地开发时太重;更推荐用pgmock或自己写个极简sqlmock风格的 fake driver,只 stubQuery和Exec两个方法 - 关键点:fake driver 必须能区分不同 SQL 模式并返回预设结果,比如
SELECT.*FROM users返回用户数据,INSERT则更新内存 map
这样既保持测试真实路径,又不依赖外部服务。
真正麻烦的是事务嵌套和连接池超时这类行为,它们在抽象层之下,工厂本身不感知——得靠上层业务代码控制,或者用 context.WithTimeout 包一层执行器。



















