不能直接在 TableName() 里拼接时间,因为 GORM 会缓存首次调用结果并复用,导致跨月后仍操作旧表;必须用 Table() 显式指定表名且需紧邻 Model() 调用,同时校验表名合法性、保持归档表结构一致。

为什么不能直接在 TableName() 里拼接时间?
因为 TableName() 方法会被 GORM 缓存——首次调用后,返回的表名会绑定到该 struct 的反射类型上,后续所有操作(哪怕跨月)都复用这个值。你看到“跨月后还查旧表”,本质是缓存没刷新,不是逻辑写错了。
常见错误现象:user_202407 表数据正常写入,但 8 月 1 日零点后仍往 user_202407 写,日志无报错,Create() 返回 success,但数据实际进了旧表。
- 必须配合
Session(&gorm.Session{NewDB: true})每次新建 session,绕过缓存 - 或者彻底弃用
TableName(),改用显式Table()调用——这是目前最稳妥的路径 - 别在 struct 上写
func (u *User) TableName() string做动态逻辑,除非你能保证每次调用都走新实例
db.Table() 必须紧邻 Model() 或作为首操作
Table() 是 GORM 显式覆盖表名的唯一安全入口,但它不是“设置一个全局变量”,而是链式调用中的状态快照。一旦中间穿插了 Where()、Joins() 等操作,GORM 可能已根据原始模型推导出 SQL,再调 Table() 就晚了。
正确顺序:db.Table("user_202408").Where("id > ?", 100).Find(&users)
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
错误顺序:db.Where("id > ?", 100).Table("user_202408").Find(&users)(此时 Table() 无效,仍查默认表)
- 如果用了
Scopes,确保 scope 内部不提前触发查询或预编译,否则Table()会被跳过 -
Table()不影响事务上下文,db.Transaction()内仍可安全使用 - 若目标表结构与原模型不一致(比如归档表去掉了某些字段),
Create()会静默忽略缺失字段,建议用Select()显式指定列
白名单校验和表名构造必须同步做
动态拼表名时,"user_" + time.Now().Format("200601") 看似简单,但上线后可能因时区、NTP 同步偏差、手动改系统时间导致生成非法表名(如 user_202413)。GORM 不校验表名合法性,错误会延迟到执行 SQL 时才暴露为 ERROR 1146 (42S02): Table 'xxx.user_202413' doesn't exist。
- 务必用正则白名单校验:例如
^user_\d{6}$,且\d{4}部分需额外判断是否为有效年份(避免 9999) - 推荐把表名生成和校验封装成独立函数,例如
func getMonthlyTableName(base string, t time.Time) (string, error) - 别在 handler 里直接拼字符串传给
Table(),容易漏校验;更别把格式化逻辑分散在多个地方
软删除字段和归档表结构一致性最容易被忽略
如果你的主表启用了 GORM 软删除(DeletedAt 字段),而归档表没加这个字段,Create() 或 Save() 会静默失败:GORM 尝试写入 DeletedAt,但目标表不存在该列,MySQL 报 Unknown column 'deleted_at' in 'field list',而 GORM 默认不透传这个错误,.Error 可能为 nil。
- 归档表必须和主表结构完全一致,包括字段名、类型、约束、索引,尤其是
DeletedAt、CreatedAt这类 GORM 钩子字段 - 用
AutoMigrate()初始化归档表时,传入的是同一个 model struct,但要确认它没被其他TableName()干扰 - 如果归档表只读,建议显式禁用钩子:
db.Unscoped().Table(...).Find(),避免SoftDelete条件污染查询


















