用 db.query() 查多行必须先调用 rows.next() 再 scan(),否则 panic;需用 sql.null* 类型处理 null;优先使用 querycontext() 控制超时;批量查询应避免循环调用 query(),改用 in 或预处理语句。

用 db.Query() 查多行数据,别直接扫 rows.Scan()
查多行必须先调用 db.Query() 得到 *sql.Rows,再循环 rows.Next() 扫描——跳过 Next() 直接 Scan() 会 panic,错误信息是 "sql: no rows in result set" 或更隐蔽的 "sql: Scan called without calling Next"。
常见错误场景:把单行查询的 db.QueryRow().Scan() 习惯套用到多行上;或者忘了检查 rows.Err() 导致错误被静默吞掉。
-
rows.Next()必须在每次Scan()前调用,它负责移动游标并准备下一行数据 - 循环结束后务必调用
rows.Close(),否则连接不释放,容易触发"too many connections" - 如果查询结果为空,
rows.Next()返回false,循环不会执行,不用额外判断空切片
sql.NullString 和其他 Null* 类型不是可选技巧,是必须用的
MySQL 字段允许为 NULL,而 Go 原生类型(如 string、int64)不能表示 “有值” 和 “NULL” 的区别。直接用 string 接 NULL 列会导致 "sql: Scan error on column index X: unsupported Scan, storing driver.Value type <nil> into type *string"</nil>。
正确做法是用 sql.NullString、sql.NullInt64、sql.NullBool 等。它们带 Valid 字段,能明确区分 NULL 和空值。
- 字段定义为
sql.NullString后,扫描后需判断ns.Valid才能安全取ns.String - 不要试图用指针类型(如
*string)替代——database/sql不支持直接 Scan 到未初始化的指针 - 如果业务层需要统一处理 NULL,建议封装一层结构体或用
sql.Scanner实现自定义 Scan 方法
用 db.QueryContext() 而不是 db.Query(),超时控制不能靠 guess
默认 db.Query() 没有上下文,一旦 MySQL 慢查询、网络卡顿或连接池耗尽,goroutine 就一直挂起,直到 TCP 超时(通常是几分钟),这会拖垮整个服务。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
db.QueryContext() 允许传入带 timeout 的 context.Context,是唯一可控的中断方式。
- 典型写法:
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second),查完立刻cancel() - 注意:即使
QueryContext()返回 error,也要手动rows.Close()(如果返回了非 nil*sql.Rows),否则资源泄漏 - 连接池配置(如
SetMaxOpenConns)和查询超时是两回事——前者管并发数,后者管单次查询生命周期
别在循环里反复调用 db.Query(),批量查用 IN 或预处理语句
比如要查 100 个 user_id 对应的用户信息,写成 for 循环里每次 db.Query("SELECT ... WHERE id = ?", id),会发起 100 次 round-trip,还可能快速占满连接池。
两种靠谱方案:
- 拼
IN查询(适合 ID 数量可控,如 ≤ 1000):SELECT * FROM users WHERE id IN (?, ?, ?),然后用sql.In(需自己实现或用github.com/alexandrevicenzi/go-sql等小工具展开参数) - 用预处理语句 +
QueryRow()或Query()复用(适合高频固定查询):stmt, _ := db.Prepare("SELECT name FROM users WHERE id = ?"),之后stmt.Query(id) - 避免字符串拼接 SQL,尤其拼
IN列表时——必须用参数占位符,否则 SQL 注入风险拉满
真正难处理的是跨表、分页、条件动态组合的场景,这时候该上 ORM 或 query builder,而不是硬扛 raw SQL。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










