反射拼SQL前必须检查指针是否为nil,否则v.Interface()会panic;需用v.Kind()==reflect.Ptr&&v.IsNil()判断并跳过或生成"IS NULL";结构体tag是列名映射唯一依据;须缓存reflect.Type元信息提升性能。

反射拼SQL前必须检查指针是否为nil
直接对结构体字段调用 v.Interface() 会 panic,尤其当字段是 *string、*int64 等指针类型且值为 nil 时。Go 反射不会自动跳过或转成 NULL,它会硬崩。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 对每个字段先判断
v.Kind() == reflect.Ptr && v.IsNil(),为真则跳过该字段,或显式生成"col IS NULL" - 别依赖
v.IsValid()—— 它对 nil 指针返回true,但后续v.Interface()仍 panic - 若字段是
interface{}类型,需先v.Elem()解一层再判断,否则v.Interface()可能返回空接口而非底层值
结构体 tag 是字段名与列名映射的唯一可靠依据
用 map[string]interface{} 拼 WHERE 条件看似灵活,但丢失类型和语义:无法区分 nil(应生成 IS NULL)和空字符串(应生成 = ''),也无法做参数绑定校验。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 优先使用带
dbtag 的结构体,例如Name *string `db:"user_name"` - 反射时统一读
v.Tag.Get("db")获取列名,没 tag 则 fallback 到字段名(可加配置开关) - 遇到匿名嵌入字段(如
Extra Info `db:",inline"`),需递归展开并拼接前缀,不能只取字段名
动态生成 INSERT/UPDATE 语句时,字段顺序必须与 []interface{} 严格一致
原生 database/sql 只认位置参数,而 sqlx.Named 支持命名参数(:name)。若反射生成的 SQL 含命名占位符,却喂给 db.Query,会报错:sql: expected 0 arguments, got 1。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- INSERT/UPDATE 语句一律按结构体字段顺序生成
VALUES (?, ?, ?)形式,对应[]interface{}切片 - 若要用命名参数,必须全程走
sqlx.NamedExec,且反射生成的参数 map 必须键名与 SQL 中的:key完全匹配 - 字段遍历顺序不可依赖
range—— 必须用v.NumField()+v.Field(i)保证与结构体定义顺序一致
反射性能开销大,元信息必须缓存
每次调用 reflect.TypeOf(u) 和遍历字段都会触发类型系统查询,反复解析同一结构体类型是典型浪费。实测高频调用场景下,未缓存比缓存慢 3–5 倍。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
sync.Map或全局 map 缓存reflect.Type对应的字段名列表、tag 映射、可写字段索引等 - 缓存 key 推荐用
t.String()(如"main.User"),避免用unsafe.Pointer带来 GC 风险 - 不缓存
reflect.Value—— 它是运行时值快照,无法复用;只缓存类型层面的静态元信息


















