分表与动态sql必须分离处理:分表靠业务层计算表名并显式调用db.table(),动态查询依赖参数化+白名单校验;混用易遗漏table_name或order by等不可参数化部分。

分表和动态SQL必须拆开处理:分表靠业务层计算表名并显式调用 db.Table(),动态查询靠参数化 + 白名单校验,两者混在一起就容易漏掉 table_name 或 ORDER BY 这类无法参数化的部分。
分表必须在 DAO 层显式指定表名
分表不是 ORM 自动识别的,GORM 的 Table() 方法是唯一可控入口。一旦漏调,查询就落到默认表(比如 users),而不是实际的 users_001。
- 分片键(如
user_id)必须参与表名生成逻辑,推荐用取模:tableName := fmt.Sprintf("users_%03d", userID%1000) -
db.Table(tableName).Where(...).Find(&u)是标准写法;db.Session(&gorm.Session{NewDB: true}).Table(...)这种写法在 GORM v2 中不继承Table()设置,每次都要重设 - 禁止在 WHERE 条件里省略分表字段——比如查
users_001却没带WHERE user_id BETWEEN 1 AND 999,后续运维无法判断数据归属
动态 WHERE 条件只能靠参数化 + 增量构建
所有值(WHERE 右侧、IN 列表、LIKE 模糊值)必须走 ? 或 占位符,不能拼字符串。但占位符索引必须和参数顺序严格对齐,否则 PostgreSQL 报 pq: invalid parameter reference。
- 用
[]interface{}累积参数,每加一个条件就 append 一次,占位符按fmt.Sprintf("AND name = $%d", len(params)+1)动态生成 -
IN查询不能传 slice 直接进去:db.Query("SELECT * FROM u WHERE id IN (?)", ids)会崩;得先生成?, ?, ?字符串,再把ids转成[]interface{}展开 - 空字符串
""和nil语义不同:前者应生成= '',后者应跳过或生成IS NULL,反射提取时必须用自定义IfZero()判断,不能只看v.Interface() != nil
ORDER BY / GROUP BY / 表名这些绝对不能参数化
数据库协议根本不支持把标识符当参数绑定,硬塞 $1 会直接报语法错误,或者被攻击者注入 ASC; DROP TABLE users。
- 排序字段必须白名单精确比对:
validFields := map[string]bool{"created_at": true, "score": true},if !validFields[field] { return err } - 方向只允许
"ASC"或"DESC",别用fmt.Sprintf("ORDER BY %s %s", f, d)拼,d是用户传的就得先校验 - 表名拼接前,要么查
INFORMATION_SCHEMA,要么硬编码映射:switch tableInput { case "order": return "orders_202406" },正则或 trim + replace 都防不住零宽字符
真正难的不是写出能跑的代码,而是让分表键、表名、WHERE 条件、排序字段四者始终对齐——少校验一环,线上就可能读到空结果或跨表污染。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











