rows.close()必须显式调用,因为它负责将底层数据库连接归还连接池;仅遍历完或next()返回false不释放连接,漏调会导致连接池耗尽、查询超时及泄漏。

漏掉 rows.Close() 会导致连接池迅速耗尽,新查询卡住、超时,甚至 panic。
为什么 rows.Close() 必须显式调用
它不只是“清理结果集”,而是把底层数据库连接归还给 database/sql 连接池。不调用,连接就一直被占着,无法复用。
-
rows.Next()返回false(比如遍历完或出错)≠ 连接已释放 -
db.QueryRow()内部自动调用Close();db.Query()不会 - 即使
for rows.Next()提前break或发生Scan错误,也必须确保rows.Close()执行
defer rows.Close() 的正确位置
不能写在 db.Query() 后立刻 defer —— 那样会在函数返回时才执行,而此时可能还没开始遍历,更无法检查 rows.Err()。
- 应在获取
rows后、进入for rows.Next()前调用defer rows.Close() - 必须在检查
rows.Err()之前完成Close(),否则rows.Err()可能不准确(例如扫描中途断连,只有Close()后才暴露) - 错误写法:
rows, _ := db.Query(...); defer rows.Close(); if err != nil { ... }—— 此时err是Query错误,不是遍历错误
GORM 场景下如何避免遗漏
GORM 的 Find()、First() 等方法内部自动管理连接,无需手动 Close;但直接调用 Rows() 时和原生 database/sql 行为一致。
- 用
db.Rows()流式处理大数据集时,必须配defer rows.Close() - 不要在
defer中忽略rows.Close()的返回 error —— 它可能包含网络中断、连接失效等关键信息 - 若封装了通用查询方法(如
FindBy()),确保所有分支路径都覆盖rows.Close(),尤其错误提前返回处
连接泄漏的典型现象与验证方式
不是等到服务崩了才发现 —— 早该有感知手段。
- 现象:
db.Query()开始超时、返回sql.ErrConnDone;netstat -an | grep :3306显示大量ESTABLISHED连接;MySQL 中SHOW PROCESSLIST出现大量Sleep状态连接 - 验证:调用
db.Stats(),观察InUse持续上涨且Idle趋近于 0 - 注意:
db.Close()是关闭整个连接池,不是替代rows.Close()的方案
最易被忽略的一点:很多人以为 defer 就是“安全兜底”,但 defer 的执行时机依赖作用域,而 rows 生命周期必须和实际使用严格对齐——尤其是嵌套函数、提前 return、recover panic 等场景,defer rows.Close() 很可能根本没机会运行。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











