db.Query用于多行结果处理,db.QueryRow专为零或一行设计;用错会导致静默丢数据、资源泄漏或逻辑错误。

db.Query 和 db.QueryRow 不是“选一个用”,而是**用途完全不同的两个函数**:前者处理多行结果,后者专为零或一行设计。用错会导致静默丢数据、资源泄漏或逻辑错误。
什么时候必须用 db.Query?
当你明确需要遍历全部结果(哪怕只预期 1 行),或要判断结果集行数时,db.Query 是唯一选择。
- 常见错误现象:
db.QueryRow返回单行成功,但实际数据库有 2 行 —— 它只取第一行,第二行被丢弃,且不报错 - 典型使用场景:分页列表、导出全量数据、校验“是否唯一”(需确认只有 1 行)
- 关键操作链:
rows, err := db.Query(...)→defer rows.Close()→for rows.Next() { ... }→if err = rows.Err(); err != nil { ... } - 容易踩的坑:忘记
rows.Close(),连接池会逐渐耗尽;忽略rows.Err(),网络中断等错误被掩盖
db.QueryRow 的真实适用边界
db.QueryRow 只适合“业务上严格要求最多返回一行”的查询,比如主键查单条、聚合函数 COUNT(*)、MAX(id) 等。
- 它内部调用
rows.Next()一次后就直接rows.Close(),无法得知是否有第二行 - 返回
sql.ErrNoRows表示零行,这是正常分支,不是异常;返回其他error才需处理 - 参数差异:和
db.Query一样支持占位符和变参,但扫描方式更简洁:err := row.Scan(&id, &name) - 性能影响:比
db.Query少一次循环开销,但差别微乎其微;别为这点性能牺牲语义正确性
如何安全地判断查询结果是零行、一行还是多行?
不能依赖 db.QueryRow,也不能靠 len(rows)(*sql.Rows 没有长度属性)。
- 标准做法:用
db.Query+ 计数器 + 提前退出逻辑 - 示例核心片段:
rows, _ := db.Query("SELECT id FROM users WHERE status = ?", "active") count := 0 var id int for rows.Next() { if err := rows.Scan(&id); err != nil { // 处理扫描错误 break } count++ if count > 1 { break // 或记录日志、返回错误 } } _ = rows.Close() if count == 0 { // 零行 } else if count == 1 { // 一行 } else { // 多行(按需处理) } - 注意:
rows.Err()必须在循环后检查,否则底层 I/O 错误会被忽略
预编译语句(db.Prepare)该不该用?
重复执行的查询(如用户登录校验、订单状态更新)必须用,否则每次 db.Query 都触发 SQL 解析和计划生成,浪费数据库 CPU。
立即学习“go语言免费学习笔记(深入)”;
- 正确姿势:
stmt, _ := db.Prepare("SELECT id FROM users WHERE email = ?"),复用stmt.Query或stmt.QueryRow - 不要在循环内反复
db.Prepare—— 这等于没预编译,还增加开销 - 预编译语句本身不需要手动关闭,
sql.DB会在连接回收时自动清理;但若长期不用,可显式stmt.Close() - 兼容性注意:MySQL 和 PostgreSQL 对预编译的支持一致,但 SQLite 在某些版本中对并发使用有限制
db.Query 返回的 *sql.Rows 是一个带状态的游标资源 —— 它不光承载数据,还绑定着底层连接。一旦开始遍历,就必须走完或主动中断并关闭,否则那条连接就卡住了。


















