rows.scan()必须传指针,如&name;多列须一次性扫描,否则游标错位;推荐用sql.rawbytes避免拷贝;反射构造参数时需用field.addr().interface()而非pointer()。

rows.Scan() 必须传指针,不是值也不是 unsafe.Pointer
直接传变量(比如 name)或 reflect.Value.Pointer() 会导致 panic:sql: Scan error on column index 0: destination not a pointer。这是因为 rows.Scan() 内部用反射检查每个参数是否为有效指针类型——只认 *T,不认 uintptr 或原始值。
- ✅ 正确:传
&name、&user.Age、field.Addr().Interface() - ❌ 错误:传
name、user.Name、reflect.Value.Pointer() - ⚠️ 注意:结构体字段必须是可寻址的(不能是字面量或不可导出字段)
多列扫描必须一次调用,不能分多次 Scan()
常见错误是写成两行:rows.Scan(&id) 然后又 rows.Scan(&name)。这会让第二次调用跳到下一行,导致漏数据、sql.ErrNoRows 或静默失败——因为 rows.Scan() 每次都推进游标。
- ✅ 正确:
rows.Scan(&id, &name, &email)—— 一整行所有列一次性填入 - ❌ 错误:对同一行反复调用
Scan(),哪怕只填一个字段 - ? 提示:列数必须严格匹配参数个数,否则 panic 或截断
用 RawBytes 避免拷贝,尤其处理大文本或二进制字段
默认用 *string 或 *[]byte 扫描时,rows.Scan() 会分配新内存并复制数据;而 *sql.RawBytes 允许复用底层缓冲区,省掉拷贝开销,在读取几千行以上时性能差异明显。
- ✅ 推荐场景:日志内容、JSON 字段、BLOB、长文本
- ✅ 写法:
var data sql.RawBytes; rows.Scan(&data),之后再string(data)或json.Unmarshal(data, &v) - ⚠️ 关键限制:
RawBytes数据仅在当前行生命周期内有效,离开rows.Next()循环就失效
反射构造 Scan 参数时,别用 Pointer(),要用 Interface()
想动态生成 []interface{} 传给 rows.Scan(),最容易踩的坑是误用 reflect.Value.Pointer()。它返回的是 uintptr,不是 Go 类型系统认可的指针,Scan 会直接 panic。
- ✅ 正确路径:
field.Addr().Interface()→ 返回*string、*int64等真实指针类型 - ❌ 错误写法:
uintptr(field.UnsafeAddr())或field.Addr().Pointer() - ? 补充:结构体必须传指针(如
&u),且字段需导出(首字母大写)
RawBytes 的生命周期和反射里那个 Interface() —— 这两个点一旦错,要么数据乱码,要么程序崩得没提示。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











