go原生database/sql只是接口规范,需显式调用rows.close()归还连接,db.exec与db.query不可混用,事务必须用tx对象,连接池需合理配置。

Go 原生 database/sql 不是 ORM,也不自动管理连接或事务生命周期——它只是个接口规范,底层行为全靠驱动实现,用错地方立刻卡死、泄漏、报 sql: connection is busy。
为什么 db.Query 后必须调用 rows.Close()
很多人以为 rows 被 GC 回收就自动释放连接,其实不是。database/sql 的连接复用机制依赖显式关闭 rows 来归还连接到连接池。不关,连接一直被占着,池子很快耗尽。
- 常见错误现象:
sql: connection pool exhausted或查询突然变慢、超时 - 使用场景:所有带结果集的查询,比如
SELECT;db.QueryRow无需手动关(内部已处理) - 实操建议:用
defer rows.Close()放在rows, err := db.Query(...)后立刻写,哪怕后续有return - 注意:如果
rows.Next()没遍历完就 return,defer仍会执行,安全
db.Exec 和 db.Query 别混用
db.Exec 用于无结果集操作(INSERT/UPDATE/DELETE),返回 sql.Result;db.Query 用于有结果集操作,返回 *sql.Rows。用反了会 panic 或漏数据。
- 常见错误现象:
panic: sql: expected statement, not query(用Exec执行SELECT)或sql: no rows in result set(用QueryRow处理无返回的语句) - 参数差异:
Exec返回的Result可调LastInsertId()和RowsAffected();Query必须自己遍历rows.Scan() - 性能影响:对写操作用
Query会多一次空结果集解析开销,且连接不会及时释放
事务里别直接用 db.Query,要用 tx.Query
事务对象 *sql.Tx 是独立连接上下文,所有操作必须走它自己的方法。用原 db 对象发请求,会脱离事务控制,变成自动提交的独立语句。
- 常见错误现象:事务里
INSERT成功了,但ROLLBACK后发现数据还在——因为用了db.Exec而非tx.Exec - 使用场景:只要开了
tx, err := db.Begin(),后面所有 SQL 操作都必须用tx.Query、tx.Exec、tx.QueryRow - 容易踩的坑:忘记把
tx传进函数,函数内部仍用全局db;或 defertx.Rollback()后没判断是否已Commit,导致重复 rollback 报错
连接池配置不调等于裸奔
默认连接池最大只有 0(不限制),但实际受系统文件描述符限制;最小空闲连接默认 0,意味着高并发下频繁建连,延迟飙升。
- 关键配置项:
db.SetMaxOpenConns(n)、db.SetMaxIdleConns(n)、db.SetConnMaxLifetime(d) - 推荐值(中等负载):
MaxOpenConns=20、MaxIdleConns=10、ConnMaxLifetime=30m - 兼容性影响:MySQL 驱动对
ConnMaxLifetime敏感,不设可能导致“connection was closed”错误;SQLite 驱动不支持连接池,设了也无效 - 调试技巧:用
db.Stats()查当前连接数和等待数,上线前必看
最常被忽略的是 SetConnMaxLifetime 和 SetMaxIdleConns 的组合效果——Idle 连接超时后不会立刻销毁,而是等下次被取用时才检测并重建,所以实际连接老化比预期慢半拍。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











