AutoMigrate仅管理表结构,不处理数据同步;增量同步需自行设计触发点、位点管理、变更捕获与写入策略,GORM仅作为SQL执行器。

增量同步不能靠 AutoMigrate 实现,它只管结构,不管数据。 真正做增量同步,得自己设计触发点、位点管理、变更捕获和写入策略——GORM 只是执行 SQL 的工具,不是 CDC 组件。
为什么 AutoMigrate 对增量同步完全无效
AutoMigrate 的职责边界非常清晰:它只对比当前结构体定义与数据库表结构,补全缺失字段、索引、外键,但绝不会读一行数据、也不会判断某条记录是否“新”或“已变更”。它不感知时间戳、不看 updated_at、不查 binlog、不维护 offset。哪怕你给结构体加了 gorm:"index"` 或 gorm:"default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP",它也只建索引或设默认值,不会帮你捞出“过去 5 分钟改过的所有用户”。
常见误用现象:
- 调用
db.AutoMigrate(&User{})后发现目标库没多出任何数据 → 正常,它根本不操作数据行 - 以为加了
UpdatedAt time.Time `gorm:"index"`就能自动同步修改记录 → 不会,索引只是加速查询,不是触发器 - 在生产环境反复跑
AutoMigrate导致连接池打满或锁表 → 它每次都会检查所有字段,高并发下可能触发元数据锁
用 GORM 做增量同步的三个可行路径
核心思路:把 GORM 当成“带链式语法的 SQL 执行器”,自己控制查询条件、分批逻辑、冲突处理。
-
基于时间戳字段轮询:要求源表有
created_at/updated_at且严格递增(不依赖数据库时钟一致性)。例如:db.Where("updated_at > ?", lastSyncTime).Find(&users),同步完更新lastSyncTime -
基于自增主键分段拉取:适合无时间字段或时钟不可靠场景。先查
MAX(id),再按id BETWEEN ? AND ?分页拉取,避免 OFFSET 深度分页问题 -
结合数据库原生变更日志(如 MySQL binlog)+ GORM 写入:用
go-mysql-elasticsearch或gh-ost类工具捕获变更,解析后用db.Create()/db.Updates()写目标库;GORM 在这里只负责安全写入,不做读取
性能关键点:
- 时间戳字段必须建索引,否则
WHERE updated_at > ?全表扫描 - 批量写入优先用
CreateInBatches而非循环Create,减少 round-trip - 目标库写入前建议加
ON DUPLICATE KEY UPDATE(MySQL)或ON CONFLICT DO UPDATE(PostgreSQL),GORM 通过Save()或显式Exec()实现,避免唯一键冲突报错中断
db.Save() 和 db.Omit().Updates() 在增量更新中的实际差异
二者都可用于“存在则更新,不存在则插入”,但行为不同,容易踩坑:
-
db.Save(&user):如果user.ID == 0,走 INSERT;否则走 UPDATE。但会更新所有非零/非空字段(取决于零值判断逻辑),可能误覆写未变更字段 -
db.Omit("created_at").Updates(&user):只更新传入结构体中非零值字段,且跳过created_at;但如果user.ID为 0,它不会 INSERT,而是静默失败(RowsAffected == 0) - 更安全的做法是显式判断:
if user.ID == 0 { db.Create(&user) } else { db.Select("name", "email", "updated_at").Updates(&user) }
注意:Updates 默认不触发钩子(BeforeUpdate 等),如需钩子,改用 Save 并确保主键非零。
外键关联数据的增量同步必须手动拆解
GORM 的 Preload 或 Joins 无法直接用于增量同步场景。比如要同步“最近更新的用户及其订单”,不能写 db.Preload("Orders").Where("users.updated_at > ?", t).Find(&users) —— 这只会加载那些用户本身被更新过的记录,而订单的更新不会被触发。
正确做法是分两步:
- 先查出变更的用户 ID 列表:
db.Model(&User{}).Where("updated_at > ?", t).Select("id").Rows() - 再查这些用户的所有订单:
db.Where("user_id IN (?)", userIDSlice).Where("updated_at > ?", t).Find(&orders)
中间表(如多对多)更要单独处理:先同步关联关系变更(如 user_tags 表的 INSERT/DELETE),再分别同步 users 和 tags 主表数据。GORM 不会自动推导“哪条中间记录是新增的”。
最易被忽略的一点:增量同步的位点(last_sync_time 或 max_id)必须持久化到可靠存储(如独立配置表或 etcd),不能只存在内存变量里;否则服务重启就丢进度,导致重复或漏同步。


















