database/sql不能直连自建存储,因它仅识别实现driver.driver接口的驱动;需手动注册驱动、实现open及conn/rows等方法,并处理类型、上下文、命名参数等兼容性细节。

为什么 database/sql 不能直接连你的自建存储
因为 database/sql 是抽象层,它只认实现了 driver.Driver 接口的类型。你写的 HTTP 存储、KV 文件、自研协议服务,它一概不认——除非你亲手补上这个“翻译官”。
常见错误现象:sql.Open("mydb", "...") 报错 sql: unknown driver "mydb" (forgotten import?),其实不是忘了 import,是根本没注册驱动。
- 必须在 main 或 init 包里调用
sql.Register("mydb", &MyDriver{}) -
MyDriver必须实现Open(dsn string) (driver.Conn, error) - DSN 字符串完全由你定义,
database/sql不解析也不校验,只是原样传给你
driver.Conn 要做哪些事才不算裸奔
它不是连接池里的“活连接”,而是 SQL 操作的执行上下文。很多驱动只实现了最简路径,结果一并发就 panic 或数据错乱。
关键点在于:它要能撑住 Query、Exec、Begin 这三类操作,且线程安全(或明确声明不安全)。
立即学习“go语言免费学习笔记(深入)”;
-
Query(query string, args []driver.Value):返回driver.Rows,注意args是[]interface{}经过ConvertValue转换后的结果,不是原始参数 -
Exec(query string, args []driver.Value):返回影响行数和最后插入 ID,如果你的存储不支持 auto-increment,就别硬填lastInsertId -
Begin():如果存储本身不支持事务,返回ErrSkip是合法的,database/sql会自动降级为非事务执行
如何让 Rows 不卡死、不漏字段、不崩在 Scan 上
这是最容易翻车的一环。用户写 rows.Scan(&a, &b),你的 Rows 却在 Columns() 返回空切片,或 Next() 里忘了移动游标,或 Scan() 直接 panic —— 全部都会变成不可捕获的 runtime error。
核心约束:所有方法都要可重入、可多次调用,且状态推进必须严格匹配 database/sql 的预期节奏。
-
Columns()必须返回字段名字符串切片,顺序与Query结果列严格一致;若无 schema(如日志行),可用占位名如["time", "level", "msg"] -
Next(dest []driver.Value):每次调用应填充dest并返回true,无更多数据时返回false;千万别在里面做阻塞 IO,应提前拉取好一批数据缓存 -
Scan()不是你实现的——那是database/sql内部调用Rows后自动做的,你只需保证dest被正确赋值
自建存储驱动里最容易被忽略的兼容性坑
Go 的 database/sql 在 1.19+ 加了对 driver.ColumnType 的支持,用于获取字段类型元信息;但如果你的存储没有类型概念(比如纯 JSON 行存),强行返回 "TEXT" 可能导致 sql.NullString 失效或 Scan 类型断言失败。
还有两个隐形开关:是否支持 context.Context、是否支持 driver.NamedValue(即命名参数)。不支持就别暴露对应方法,否则 database/sql 会在某些路径下调用它们并 panic。
- 若不支持 context,就别实现
QueryContext/ExecContext;否则必须处理 cancel 和 timeout - 若 DSN 或协议不支持命名参数(如
SELECT * FROM t WHERE id = :id),就让NamedValueChecker返回NotSupported - 所有 error 都建议包装成
driver.ErrBadConn或driver.ErrSkip等标准错误,否则连接池可能无法正确重试或剔除坏连接
真正麻烦的从来不是写完驱动,而是当别人用 db.QueryRow("SELECT ?").Scan(&x) 时,你的驱动得知道那个 ? 对应的是第几个 args,而且不能假设 SQL 解析器在你之前运行过。











