
本文介绍如何使用 Go 的 database/sql 配合 MySQL 驱动,创建指向数据库实例(而非具体库)的通用连接池,并通过 USE database 或带库名的全限定表名(如 db.table)灵活访问多个数据库。
本文介绍如何使用 go 的 database/sql 配合 mysql 驱动,创建指向数据库实例(而非具体库)的通用连接池,并通过 use database 或带库名的全限定表名(如 db.table)灵活访问多个数据库。
在 Go 应用中管理多个同实例下的数据库(例如 10 个业务库共存于同一 MySQL 实例),最佳实践并非为每个库单独维护连接池——这会显著增加资源开销与管理复杂度。相反,应复用单个连接池连接到 MySQL 实例本身(即不指定默认数据库),再通过 SQL 层动态切换上下文。
实现的关键在于:DSN 中省略数据库名。MySQL 协议允许连接时不选择任何数据库,此时连接处于“实例级”就绪状态。示例如下:
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func initDBPool() (*sql.DB, error) {
// 注意:DSN 末尾为 "/",未指定数据库名
dsn := "root:password@tcp(127.0.0.1:3306)/"
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
// 推荐:配置连接池参数以提升稳定性与复用率
db.SetMaxOpenConns(50)
db.SetMaxIdleConns(20)
db.SetConnMaxLifetime(60 * time.Second)
// 验证连接有效性(非必须,但建议)
if err := db.Ping(); err != nil {
return nil, err
}
return db, nil
}获得该连接池后,有两种主流方式操作不同数据库:
✅ 方式一:执行 USE database_name 切换默认库
适用于需要连续执行多条针对同一库的语句(如事务内)。注意:USE 是会话级命令,需确保在同一个 *sql.Conn 上执行(推荐显式获取连接):
conn, err := db.Conn(context.Background())
if err != nil {
return err
}
defer conn.Close()
_, err = conn.ExecContext(context.Background(), "USE my_app_db1")
if err != nil {
return err
}
// 后续 Query/Exec 将默认在此库中执行
rows, err := conn.QueryContext(context.Background(), "SELECT * FROM users")✅ 方式二:使用全限定表名(推荐用于简单查询)
直接在 SQL 中显式指定数据库名,完全规避上下文切换,线程安全且无会话依赖:
rows, err := db.Query("SELECT * FROM app_db1.users WHERE id = ?", 123)
if err != nil {
return err
}
// 或跨库 JOIN(需权限支持)
rows, err := db.Query("SELECT u.name, o.amount FROM app_db1.users u JOIN app_db2.orders o ON u.id = o.user_id")⚠️ 重要注意事项:
-
sql.Open仅初始化连接池,不立即建连;首次db.Ping()或实际查询时才触发真实连接。 - 若应用需频繁切换数据库,*避免在长生命周期的 `sql.Tx` 中混用多个库**,因事务绑定到当前会话数据库,跨库 DML 可能失败或行为未定义。
- 使用
xorm等 ORM 时,其底层仍基于*sql.DB,因此同样适用上述 DSN 策略;只需在初始化xorm.Engine时传入无库名的 DSN,并在 SQL 构造阶段处理库前缀。 - 安全提示:动态拼接数据库名存在 SQL 注入风险,务必校验库名白名单(如
map[string]bool{"app_db1":true, "app_db2":true})。
总结:一个实例级连接池 + 全限定表名(或受控的 USE 切换),是兼顾性能、简洁性与可维护性的标准方案。它既避免了 10 倍连接池冗余,又保留了多库隔离的灵活性。


















