sql.open()中的"xxx"是驱动注册名,由第三方驱动在init()中向database/sql注册,必须完全匹配,如mysql为"mysql"、postgresql为"postgres"、sqlite为"sqlite3";拼错或未导入驱动(如import _ "github.com/go-sql-driver/mysql")会导致panic。

Go 的 database/sql 本身不驱动任何数据库,它只提供统一接口;真正干活的是第三方驱动包,比如 github.com/go-sql-driver/mysql —— 没它,sql.Open("mysql", dsn) 直接 panic。
sql.Open("xxx", dsn) 中的 "xxx" 是哪来的?
这个字符串是驱动注册名,不是随便写的。它由驱动包在 init() 函数里向 database/sql 注册,必须完全匹配:
- MySQL 驱动注册的是
"mysql",所以写sql.Open("mysql", dsn) - PostgreSQL(
lib/pq)注册的是"postgres",不是"postgresql" - SQLite(
mattn/go-sqlite3)注册的是"sqlite3",Go 1.21+ 原生database/sql/sqlite3也用这个名 - SQL Server(
denisenkom/go-mssqldb)注册的是"mssql",拼成"msql"或"sqlserver"都会报sql: unknown driver
漏掉 import _ "github.com/go-sql-driver/mysql" 这种下划线导入,就等于没注册,驱动名再对也没用。
为什么 sql.Open 后 db.Ping() 才报错?
sql.Open 只初始化连接池配置,不建真实连接。常见现象是:代码跑通、db 变量非 nil,但第一次 Query 或 Exec 才爆出 dial tcp: i/o timeout 或 connection refused。
- 必须显式调用
db.Ping()或更推荐的db.PingContext(ctx, timeout)做启动校验 - 默认
Ping()超时不可控(依赖底层驱动和系统设置),生产环境务必用带 context 的版本 - 驱动本身不自动重试,
PingContext失败就是失败,得自己决定是重试、降级还是 abort
DSN 字符串里密码含 @ / ? : 怎么办?
MySQL 和 PostgreSQL 的 DSN 是类 URL 格式,@、/、:、? 都是分隔符。如果密码是 pa@ss/w?rd,直接拼进 DSN:user:pa@ss/w?rd@tcp(...)/db,解析器会把它切错成用户名 user、密码 pa、host ss/w?rd@tcp(...),然后报错。
- 必须对
username和password单独做url.QueryEscape() - 不要手拼 DSN,封装一个函数生成:
fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true", url.QueryEscape(user), url.QueryEscape(pass), addr, dbname) - PostgreSQL 的
sslmode=disable在本地开发时极易遗漏,不加会导致连接卡住(尤其 macOS 上)
为什么不同数据库的 SQL 语法不能混用?
database/sql 统一了 API(Query、Exec、BeginTx),但不统一 SQL 语法。例如:
- 参数占位符:
MySQL和SQLite用?,PostgreSQL用$1、$2 - 插入后取 ID:
MySQL支持LAST_INSERT_ID(),PostgreSQL用RETURNING id,SQLite也支持last_insert_rowid(),但语义不等价 - Upsert:
SQLite是INSERT OR REPLACE,PostgreSQL是ON CONFLICT DO UPDATE,MySQL是ON DUPLICATE KEY UPDATE
跨数据库迁移时,最常崩的地方不是 Go 代码,而是这些嵌在 Exec 和 Query 里的 SQL 字符串 —— 它们不属于 database/sql 管辖范围,得靠桥接层或业务封装隔离。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











