
Go 里没有“框架内置多数据库支持”这回事,所谓多数据库或主从架构,全靠你手动初始化多个 *sql.DB 或 *gorm.DB 实例,并严格隔离使用边界。混用、复用、共享连接池,轻则查错库,重则事务失效、连接泄漏、服务雪崩。
为什么不能只用一个 *sql.DB 切库名
MySQL 的 USE database_name 是会话级命令,sql.Open 返回的 *sql.DB 是连接池抽象,不是单个连接。你无法控制某次 db.Query 落在哪个物理连接上,更没法保证事务期间所有语句都走同一个连接——所以靠 USE 切库是不可靠的。
- 执行
db.Exec("USE log_db")只影响当前获取到的那个底层连接,下次Query可能拿到另一个连着user_db的连接 - 事务中跨库操作(比如先写 user 表再写 log 表)必然失败:MySQL 不允许一个事务跨库提交
- 连接池参数(如
SetMaxOpenConns)是全局生效的,主库突发流量可能耗尽全部连接,从库查询直接阻塞
sql.Open 多实例必须独立初始化且命名明确
每个数据库对应一个独立变量,DSN 完整且不共用,连接池参数单独调优:
// 正确:两个独立实例
userDB, _ := sql.Open("mysql", "user:pass@tcp(10.0.1.10:3306)/user_db?charset=utf8mb4&parseTime=True&loc=Local")
logDB, _ := sql.Open("mysql", "log:pass@tcp(10.0.1.11:3306)/log_db?charset=utf8mb4&parseTime=True&loc=Local")
<p>userDB.SetMaxOpenConns(30)
userDB.SetMaxIdleConns(10)
userDB.SetConnMaxLifetime(5 * time.Minute)</p><p>logDB.SetMaxOpenConns(10) // 日志库通常读多写少,连接数可设低些
logDB.SetMaxIdleConns(5)
logDB.SetConnMaxLifetime(10 * time.Minute)
- 别用
var db *sql.DB全局变量覆盖;不同库必须不同变量名(如userDB、logDB) - DSN 中的
host和database都要写全,哪怕只是主从 IP 不同、库名相同 - 主从场景下,从库 DSN 的
host必须指向从库地址,不能靠应用层“路由逻辑”把写请求发给从库
GORM 多实例共存的三个硬性前提
GORM 每个 *gorm.DB 都自带完整连接池、日志器、回调链,它不帮你做连接复用或自动路由。想让多个 GORM 实例不打架,必须满足:
立即学习“go语言免费学习笔记(深入)”;
- 驱动导入带下划线:
import _ "gorm.io/driver/mysql",漏了就静默失败,首次查询卡死或报"invalid connection" - DSN 格式与驱动严格匹配:MySQL 驱动不能传 PostgreSQL 的 DSN;PostgreSQL 用户名含
@必须用url.QueryEscape编码,否则报"pq: password authentication failed"却密码正确 - 事务绝对不跨实例:
userDB.Transaction(...)内调用logDB.Create(...)完全游离于事务外——GORM 做不了分布式事务,业务层必须用补偿(例如先写主库成功后,异步发消息写日志库,失败告警)
主从读写分离不是自动发生的
Go 没有内置主从路由中间件。所谓“读走从库”,是你自己在 handler 或 service 层判断操作类型,然后显式选择 userDB(主)还是 slaveDB(从):
func GetUser(ctx context.Context, id int) (*User, error) {
// 显式用从库查,避免压主
row := slaveDB.QueryRowContext(ctx, "SELECT id, name FROM users WHERE id = ?", id)
// ...
}
<p>func CreateUser(ctx context.Context, u User) error {
// 显式用主库写
_, err := userDB.ExecContext(ctx, "INSERT INTO users (...) VALUES (...)", ...)
return err
}
- 不要试图封装一个
DBRouter结构体,在里面根据 SQL 关键字(如SELECT/INSERT)自动分发——SQL 解析不可靠,prepare 语句、子查询、注释都会让它失效 - 主从延迟容忍度要从业务定:用户注册后立即查自己资料,必须走主库;后台报表类查询,可强制走从库
- 从库挂了怎么办?别让整个服务不可用:对
slaveDB.Ping()做健康检查,失败时降级回主库读(但要加日志告警)
最易被忽略的一点:连接池参数没拆开调,主库高峰时把所有连接占满,从库查询排队等连接,看起来像“从库慢”,其实是连接池配置错误。每个库的 SetMaxOpenConns 和 SetMaxIdleConns 必须按各自负载独立设置,不能抄一份配置到处用。


















