必须确保旧连接池彻底空闲再销毁,否则直接关闭后仍有 goroutine 执行 Query/Exec 会触发 sql: database is closed panic;应先调用 db.SetMaxOpenConns(0) 拒绝新请求,再用 db.PingContext 循环检测是否真正关闭(最多等 2 秒)。

切换数据库时连接池没释放导致 panic
Go 的 sql.DB 本身不是连接,而是连接池的抽象;直接关闭旧连接池后立刻新建同名驱动实例,若旧池中还有 goroutine 正在执行 Query 或 Exec,会触发 sql: database is closed panic。必须确保旧连接池彻底空闲再销毁。
- 用
db.Close()后调用db.PingContext(ctx)循环检测是否真正关闭(最多等 2 秒),避免假性“已关” - 切换前用
db.SetMaxOpenConns(0)主动拒绝新请求,再等所有活跃查询结束 - 不要复用同一个
sql.Register名称注册不同驱动(如同时注册"mysql"和"postgres"),Go 会 panic 报错sql: Register called twice for driver "xxx"
用 interface{} 包装 db 实例导致类型断言失败
常见错误是把 *sql.DB 存进 map[string]interface{},切换后取出来直接当 *sql.DB 用,结果 runtime panic:interface conversion: interface {} is *sql.DB, not *sql.DB——这其实是 Go 模块版本不一致导致的类型不兼容(不同 module path 下的 sql.DB 被视为不同类型)。
- 统一使用
github.com/lib/pq、github.com/go-sql-driver/mysql等标准驱动,避免混用 fork 版本 - 声明变量时明确类型:
var db *sql.DB,而不是var db interface{} - 若需抽象,定义自己的接口(如
type DB interface { Query(...); Exec(...) }),而非依赖interface{}
事务跨数据库切换时 context 被提前 cancel
切换过程中如果正在跑事务,而你用 context.WithTimeout 控制切换流程,可能在事务 commit 前就 cancel 掉 context,导致 context canceled 错误,且事务状态不可知。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 事务中的数据库操作必须绑定独立的、不随切换逻辑 cancel 的 context(如
context.Background()) - 切换流程本身应走单独 goroutine + channel 通知,主业务线程继续用原
db完成当前事务 - 切完后新 db 不要自动继承旧事务状态——Go 的
sql.Tx绑定具体*sql.DB实例,无法迁移
测试环境用 sqlite 切换到生产 postgres 时 DDL 不兼容
SQLite 的 CREATE TABLE 语句在 PostgreSQL 上常报错,比如 INTEGER PRIMARY KEY AUTOINCREMENT 在 pg 中得写成 SERIAL PRIMARY KEY,或者 TEXT 类型默认长度限制差异引发插入截断。
立即学习“go语言免费学习笔记(深入)”;
- DDL 语句按目标数据库方言生成:用
migrate工具(如github.com/golang-migrate/migrate)分目录管理postgres/和sqlite/迁移文件 - 避免在代码里拼接 SQL 字符串建表,改用
database/sql+ 驱动特定的driver.Valuer处理类型映射 - CI 中对每个目标数据库跑一遍
go test -tags=postgres和-tags=sqlite,不共用一套测试用例

















