防 nil 指针 panic:先判 v.Kind() == reflect.Ptr && v.IsNil(),再决定跳过或生成 "col IS NULL";勿依赖 v.IsValid();interface{} 需先 v.Elem() 再处理。

反射拼 WHERE 条件时 nil 指针 panic 怎么防?
最常见错误就是 v.Interface() 直接调用,遇到 *string 或 *int64 字段值为 nil 时立刻崩溃。panic 信息通常是 reflect: call of reflect.Value.Interface on zero Value 或更隐蔽的 nil pointer dereference。
实操建议:
- 每个字段先判断
v.Kind() == reflect.Ptr && v.IsNil(),为真就跳过或生成"col IS NULL" - 别依赖
v.IsValid()—— 它对 nil 指针返回true,但后续取值仍 panic - 若字段是
interface{}类型,需先v.Elem()解一层再判断,否则v.Interface()可能返回空接口而非底层值 - 结构体字段推荐加
dbtag 映射列名,比如Name *string `db:"user_name"`,反射时用v.Tag.Get("db")读取
用 struct 还是 map 传参给 SQL 拼接逻辑?
用 map[string]interface{} 看似灵活,但运行时丢失类型,容易把 nil 和空字符串 "" 混为一谈:前者应生成 IS NULL,后者是 = '',语义完全不同。
实操建议:
- 优先用带
db或jsontag 的结构体,类型安全、可校验、IDE 支持好 - 如果必须用
map,封装一个预处理函数统一转换:nil → sql.NullString{Valid: false},"" → sql.NullString{String: "", Valid: true} - 避免在 map 中混用零值(如
0,"",nil)而不做区分,否则生成的 WHERE 条件可能永远不命中
sqlx.Named 和原生 database/sql.Query 兼容性怎么处理?
反射生成的 SQL 字符串若含命名占位符(如 :name),直接喂给 db.Query 会报错:sql: expected 0 arguments, got 1。因为原生驱动只认 []interface{} 位置参数。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
实操建议:
- 反射阶段就要决定目标驱动:选
sqlx就保持参数为 struct/map;选原生驱动,就得同步构建两个切片 ——args存值,placeholders存"?"(数量由v.Len()决定) - 别在 SQL 字符串里硬拼
:name占位符,又用原生驱动执行 —— 占位符会被当字面量,查不到数据且无报错 - 对
IN (?)场景,用v.Kind() == reflect.Slice判断,再用sqlx.In或手动展开?占位符
性能陷阱:每次查询都重做 reflect.TypeOf 遍历?
反复调用 reflect.TypeOf + 遍历字段是典型性能杀手,尤其高频查询场景。gorp、sqlx 等库内部已缓存 reflect.Type 和字段元信息,自己手写反射逻辑时也得效仿。
实操建议:
- 把
reflect.Type和字段索引(含dbtag、是否指针、是否忽略)提前缓存到包级变量或结构体字段中 - 用
sync.Once或sync.Map控制初始化时机,避免并发重复计算 - 别在
func (s *Service) List(filter Filter) ([]*User, error)这类方法里每次进都reflect.ValueOf(filter).Type()—— 一次初始化,长期复用
真正难的不是拼出 SQL,而是让反射逻辑既安全又不拖慢请求。缓存元信息、严格区分 nil/zero/empty、提前约定驱动类型——这三点漏掉任一,上线后都会变成半夜告警的源头。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










