分表必须由应用层显式计算表名,orm不自动路由;id取模用abs(id)%n生成表名,时间分表用format("200601");gorm需db.table()动态指定,跨表查询须union all或循环合并。

分表必须由 Go 应用层显式计算表名,ORM 不会自动帮你切分——写错表名、查不到数据、JOIN 报错,90% 都是因为漏了这步。
怎么动态生成分表名(ID 取模 / 时间格式)
所有 INSERT、SELECT、UPDATE、DELETE 都得先算出目标表名,再拼 SQL 或传给 ORM。不能依赖 struct 标签里的固定表名。
- 按
user_id分表:封装纯函数GetTableNameByUserID(id int64) string,返回"user_" + strconv.FormatInt(abs(id)%100, 10);注意负数要先abs(),否则取模结果异常 - 按时间分表(如订单):用
order.CreatedAt.Format("200601")得到"orders_202409";别用"2006-01",横杠在某些备份工具里会被误判为分隔符 - 表名映射必须确定:不要用
tables[id%len(tables)]直接索引切片——切片顺序一变,所有数据就路由错位
GORM 怎么正确切换表名(不是 Model 绑定)
db.Model(&User{}) 永远走 struct 上 gorm:"table:user" 定义的表名,和分表无关。真正生效的是 db.Table("user_42")。
- 每次查询/写入前都得显式调用:
db.Table(GetTableNameByUserID(uid)).Where("id = ?", uid).First(&u) - GORM v2 的
Session不继承Table()设置,所以db.Session(...).Table(...)后续链式操作仍需再调一次Table() - 如果用
TableName()方法动态返回表名,它只在AutoMigrate和部分初始化场景触发,运行时 CRUD 不会自动调用
跨时间范围查询怎么写(不能只查一张表)
用户查“近三个月订单”,数据可能分布在 orders_202404、orders_202405、orders_202406 三张表里——GORM 原生不支持跨表 UNION ALL。
- 方案一:循环查每张表,用 Go 合并结果(适合结果集不大、分表数可控)
- 方案二:手写
db.Raw("SELECT * FROM orders_202404 UNION ALL SELECT * FROM orders_202405 ..."),注意参数要手动拼或预处理防注入 - 禁止写
SELECT * FROM orders WHERE created_at BETWEEN ...—— 这种查法永远只扫原表,数据根本不在那儿 - WHERE 条件里必须带分表键(如
created_at),否则无法推断查哪几张表,容易漏数据
为什么不能用数据库原生分区代替应用层分表
MySQL 的 PARTITION BY RANGE 是数据库层能力,Go 只负责执行 DDL 和发对的查询。但它和应用层分表是两套逻辑,混用会出问题。
- GORM
AutoMigrate完全忽略分区语法,建表语句得你手写db.Exec()执行,且驱动版本要匹配(如 MySQL 8.0.29+ 已弃用TO_DAYS()) - 分区裁剪依赖查询条件严格匹配分区字段类型:
WHERE created_at = '2024-09-01'可命中分区,但WHERE DATE(created_at) = '2024-09-01'就会全扫 - 分区表不能直接
ALTER TABLE ... ADD COLUMN跨所有分区生效,得逐个ALTER TABLE ... PARTITION,运维成本高
真正的难点不在算表名,而在所有 DAO 接口、所有 WHERE 条件、所有迁移脚本,都得显式携带分表逻辑——漏一处,数据就不可见。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











