GORM Where条件必须用?占位符绑定参数,禁用字符串拼接;结构体仅非零值生效,Map不忽略零值;Or/Not需嵌套Where防括号错误;动态字段、排序、表名等须白名单校验。

Where 条件拼写必须带参数绑定,别直接拼字符串
直接写 db.Where("name = '" + name + "'") 是高危操作,SQL 注入风险拉满。GORM 的 Where 支持问号占位符和结构体/Map 两种安全方式,优先用前者。
- 单条件:
db.Where("status = ?", "active").Find(&users) - 多条件 AND:
db.Where("age > ? AND city = ?", 18, "Beijing").Find(&users) - IN 查询:
db.Where("id IN (?)", []uint{1, 2, 3}).Find(&users)(注意括号(?),不是?) - LIKE 模糊:
db.Where("name LIKE ?", "%"+keyword+"%").Find(&users)(keyword 必须先校验非空、长度限制) - Struct 条件:
db.Where(&User{Status: "active", DeletedAt: nil}).Find(&users)(仅非零值字段参与 WHERE)
常见错误现象:db.Where("id = " + strconv.Itoa(id)) 在 id 为负数或含 SQL 片段时直接崩库;db.Where("name IN ?", ids) 少了括号会报 “Operand should contain 1 column(s)” 错误。
动态字段过滤必须白名单校验,禁止用户控制列名
如果接口支持 field=name&value=xxx 这类动态查询,field 值绝不能原样进 SQL —— 否则 field=password 就能绕过权限查出敏感字段。
- 定义白名单:
allowedFields := map[string]bool{"name": true, "email": true, "status": true, "created_at": true} - 校验后再拼:
if !allowedFields[field] { return errors.New("invalid field") } - 再构造条件:
db.Where(field+" = ?", value).Find(&users)
特别注意:deleted_at、password_hash、tenant_id 等字段必须排除在白名单外;若需按租户隔离,应统一用 Where("tenant_id = ?", tenantID) 硬编码,而非让用户传字段名。
分页前必须 Order(),且排序字段要有确定性
没有 Order() 的 Limit().Offset() 分页是不可靠的。MySQL/PostgreSQL 不保证无序 LIMIT 的返回顺序,同一页刷新两次可能漏数据或重复。
- ✅ 正确写法:
db.Order("id DESC").Limit(20).Offset(40).Find(&users) - ❌ 错误写法:
db.Limit(20).Offset(40).Order("id DESC")(Order()在Limit/Offset后,排序只作用于已截取的 20 行) - 避免用
created_at单独排序:高并发下毫秒级时间可能重复,建议补二级键:Order("created_at DESC, id DESC") - 绝对禁用:
Order("RAND()")——无法翻页,全表扫描,性能雪崩
排序字段必须有数据库索引,否则 ORDER BY created_at DESC 会触发 filesort,分页越往后越慢。
复杂条件必须用子查询,别硬套 Where 链式拼接
当需求涉及“查销售额高于平均值的用户”“从未登录过的用户”“近 7 天下单 ≥3 次的买家”时,单纯 Where + Join 无法表达逻辑,必须上子查询。
- 用
SubQuery构建可复用对象:sub := db.Table("orders").Select("user_id").Where("created_at > ?", weekAgo).Group("user_id").Having("COUNT(*) >= ?", 3) - 主查询引用:
db.Where("id IN (?)", sub).Find(&users)(括号(?)是语法强制要求) - 禁止字符串拼接子查询:
db.Where("id IN (SELECT user_id FROM orders ... )")会绕过参数绑定,导致注入 - PostgreSQL 对子查询更严格,务必用
EXPLAIN看执行计划;MySQL 可能自动转 JOIN,但行为不一致
子查询不是银弹——NOT IN (?) 遇到子查询结果含 NULL 会整个失效(SQL 三值逻辑),此时应改用 NOT EXISTS 或预处理去 NULL。


















