Rows.Close() 必须手动调用,否则连接无法归还连接池,导致查询变慢、超时、连接池耗尽及文件描述符泄漏;Go 中 Query()/QueryContext() 返回的 *sql.Rows 需显式关闭,defer rows.Close() 仅在 rows 成功获取后安全,且需配合 rows.Err() 检查。

Rows.Close() 不调用会卡住连接池
数据库查询返回的 Rows 对象底层绑定了一个连接(或连接池中的某个连接),不显式调用 Rows.Close(),该连接就无法归还给连接池。即使你已经遍历完所有结果、甚至函数都 return 了,连接仍被占用——它不会自动释放。
常见错误现象包括:
- 后续查询开始变慢,
context deadline exceeded或i/o timeout频繁出现 - 连接池耗尽:日志里反复出现
sql: all connections are busy - 进程打开的文件描述符数(
lsof -p $pid | wc -l)持续上涨,最终触发too many open files
Go 的 database/sql 中 Rows.Close() 必须手动调用
不同于 QueryRow() 返回的单行结果(内部自动管理生命周期),Query() 和 QueryContext() 返回的 *sql.Rows 是流式迭代器,必须由开发者负责关闭。Go 官方文档明确指出:“The caller must call Close when the iterator is done with it.”
正确做法是:
- 在
for rows.Next()循环结束后立即rows.Close() - 使用
defer rows.Close()要格外小心:如果rows是从函数返回值中拿到的(比如封装成接口或提前 return),defer可能失效或过早执行 - 更安全的写法是把
rows.Close()放在if err != nil分支之后、循环之前做兜底,再在循环末尾显式调用一次(避免 panic 导致跳过)
示例:
rows, err := db.Query("SELECT id, name FROM users WHERE age > ?", 18)
if err != nil {
return err
}
defer rows.Close() // 这里 defer 是安全的,因为 rows 已成功获取
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
return err
}
// 处理数据
}
// rows.Err() 检查迭代是否因错误中断
if err := rows.Err(); err != nil {
return err
}
// rows.Close() 已由 defer 执行,无需重复
Python psycopg2 / mysql-connector-python 的等效行为
Python 生态中没有统一的 Rows 抽象,但类似问题一样存在:
-
psycopg2的cursor.fetchall()或fetchone()不需要额外关闭,但cursor本身需在不用时close();若用cursor.iterate()(流式读取),也需确保cursor.close() -
mysql-connector-python的cursor.execute()后若调用cursor.fetchall(),底层连接不会卡死,但若用cursor.fetchmany(size)迭代且中途 break,未消费完的结果集可能阻塞连接归还 - SQLAlchemy 的
Result(2.0+)推荐用with result:上下文管理,或显式调用result.close(),否则连接可能滞留
关键判断点:只要驱动支持“流式拉取”(streaming fetch),就一定存在资源绑定关系,不能只靠“读完了”就认为安全。
为什么不是所有语言都强制自动关闭?
自动关闭(比如通过 __del__ 或 Drop)看似省心,但实际不可靠:
- GC 触发时机不确定,连接可能在高并发下长时间得不到回收
- 循环引用会导致 Python 的
__del__不执行,Rust 的Drop虽可靠,但 MongoDB 驱动中游标未消费完就丢弃,仍会泄漏缓冲区 - 连接池需要精确控制“可用连接数”,延迟释放等于人为制造瓶颈
真正可靠的模式,是让 Rows 的生命周期与作用域严格对齐——也就是你在哪开的,就在哪关。哪怕多写一行 rows.Close(),也好过半夜盯着 sql: all connections are busy 日志发呆。


















