
go 的纯 go mysql 驱动(github.com/go-sql-driver/mysql)默认不缓存结果集行数据,所有行均按需流式读取,天然支持海量结果集的低内存逐行处理,无需手动启用“unbuffered”模式。
在使用 Go 操作 MySQL 时,开发者常会联想到 C API 中 mysql_use_result()(流式、无缓冲)与 mysql_store_result()(全量加载、缓冲)的区别,并试图寻找 Go 驱动中的等效机制。但事实上,Go MySQL 驱动的设计哲学从根本上消除了这一区分需求。
该驱动是纯 Go 实现,不依赖 libmysqlclient,因此不提供 mysql_use_result 类似的显式 API。更重要的是,它默认即为“流式无缓冲”行为:
- 查询执行后,rows, err := db.Query(...) 返回的 *sql.Rows 对象不会预加载任何行数据到内存;
- 每次调用 rows.Next() 才从底层 TCP 连接读取下一个完整数据包(MySQL 协议中每行独立成包);
- rows.Scan() 仅解析当前行的字段,不触碰后续行;
- 整个过程仅依赖两层底层缓冲:
- 操作系统 TCP socket 缓冲区(内核管理,自动调节);
- 驱动内部通信缓冲区(默认 4KB 起,动态扩容,仅用于拼装协议帧,与行数无关)。
这意味着,即使查询返回千万级行,只要你的处理逻辑及时调用 rows.Close()(或使用 defer rows.Close()),内存占用将始终保持在常量级别(约几 KB 至数十 KB),而非随结果集线性增长。
✅ 正确用法示例:
rows, err := db.Query("SELECT id, name, content FROM huge_table WHERE created_at > ?", cutoff)
if err != nil {
log.Fatal(err)
}
defer rows.Close() // 关键:确保连接资源及时释放
for rows.Next() {
var id int
var name, content string
if err := rows.Scan(&id, &name, &content); err != nil {
log.Printf("scan error: %v", err)
continue // 或 break,视错误策略而定
}
// ✅ 此处逐行处理,内存无累积
processRow(id, name, content)
}
// 检查迭代过程中是否发生错误(如网络中断、解码失败)
if err := rows.Err(); err != nil {
log.Fatal("row iteration error:", err)
}⚠️ 注意事项:
- 务必调用 rows.Close():虽 *sql.Rows 在 GC 时会自动关闭,但延迟关闭可能导致连接池耗尽或服务器端游标未释放;
- *避免在循环外保留 `sql.Rows` 引用**:它持有活跃连接,长期驻留会阻塞连接复用;
- 不需设置特殊 DSN 参数:如 readTimeout、writeTimeout 可按需配置,但不存在 ?unbuffered=true 等伪参数;
- 与 sql.DB 连接池协同工作:流式读取不影响连接池行为——单次 Query 占用一个连接,读取完毕即归还(除非事务中)。
总结:Go MySQL 驱动通过协议层精细控制与零行级缓存设计,让“处理超大结果集”成为开箱即用的能力。你无需模拟 C 风格的缓冲切换,只需遵循标准 rows.Next()/Scan()/Close() 模式,即可安全、高效、低内存地完成流式数据消费。











