用 database/sql + github.com/go-sql-driver/mysql 驱动即可连接 MySQL,无需自实现协议;需显式导入驱动、正确配置 DSN(含 parseTime=true 和 loc=Local)、调用 db.Ping() 校验连通性、合理设置连接池、手动关闭 rows、事务中统一使用 tx 对象并显式提交或回滚。

用 database/sql + mysql 驱动就能连,别自己写底层协议
Go 官方不内置 MySQL 协议实现,必须搭配第三方驱动。最常用的是 github.com/go-sql-driver/mysql,它实现了 database/sql/driver 接口,和标准库无缝协作。别去找“纯 Go 实现的 MySQL 客户端”或试图封装 TCP 连接——既没必要,又容易出错。
常见错误现象:sql: unknown driver "mysql" (forgotten import?),本质是只写了 import "database/sql",却没导入驱动包(驱动注册靠 _ "github.com/go-sql-driver/mysql" 的 init 函数)。
- 必须显式导入驱动:
import _ "github.com/go-sql-driver/mysql" - DNS 字符串格式固定:
user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true&loc=Local,注意&是 URL 中的 & 符号,不是 & 实体 -
parseTime=true才能让time.Time正确解析 DATETIME 字段;loc=Local避免时区错乱(否则可能默认 UTC)
sql.Open 不是真的连数据库,只是初始化连接池
sql.Open 返回 *sql.DB 时,根本不发任何网络请求。它只校验 DSN 格式、注册驱动是否可用,然后配置连接池参数。真正建连发生在第一次执行 Query、Exec 或 Ping 时。
容易踩的坑:db, err := sql.Open("mysql", dsn); if err != nil { panic(err) } —— 这里 err 几乎不会非 nil,但后续 db.Ping() 才暴露真实连通性问题。
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 上线前务必加
if err := db.Ping(); err != nil { log.Fatal(err) } - 设置连接池:
db.SetMaxOpenConns(25)、db.SetMaxIdleConns(25)、db.SetConnMaxLifetime(5 * time.Minute),避免连接长期空闲被 MySQL server kill(默认 wait_timeout=28800 秒,但云数据库常更短) - 不要在每次请求都
sql.Open,复用全局*sql.DB实例
查询结果要手动 rows.Close(),否则连接泄漏
用 db.Query 得到 *sql.Rows 后,即使循环读完所有数据,也必须显式调用 rows.Close()。否则底层连接不会归还给连接池,MaxOpenConns 很快耗尽,后续请求卡住或报 dial tcp: i/o timeout。
典型错误写法:rows, _ := db.Query("SELECT id FROM users"); for rows.Next() { /* ... */ } —— 缺少 defer rows.Close() 或 rows.Close() 调用。
- 正确模式:
rows, err := db.Query(...); if err != nil { ... }; defer rows.Close() - 如果用
db.QueryRow,它内部自动关闭Rows,不用额外处理 - 注意
rows.Err():循环结束后检查rows.Err()是否为 nil,捕获读取过程中的 I/O 错误(比如网络中断)
事务里别混用 db.Query 和 tx.Query
一旦开始事务(tx, _ := db.Begin()),所有操作必须走 tx 对象,不能穿插使用原始 db。否则语句不在同一事务上下文,回滚无效,还可能因连接复用导致数据不一致。
错误场景:事务中用 db.Exec("INSERT ...") 写日志表,同时用 tx.Query("SELECT ...") 查业务数据 —— 日志插入会立即提交,无法随主事务回滚。
- 统一用
tx.Query/tx.Exec/tx.Prepare - 事务结束必须显式
tx.Commit()或tx.Rollback(),Go 不会自动回滚 panic 或 return - 想在事务中查最新状态?用
SELECT ... FOR UPDATE加行锁,而不是依赖外部db查询
MySQL 连接看着简单,但 DNS 参数含义、连接池行为、资源释放时机、事务边界这四块,任何一个疏忽都会在高并发或长时间运行时突然崩掉。尤其注意 parseTime 和 loc 组合对时间字段的影响,线上查出来的时间比存进去的早 8 小时,八成是这里没配对。

















