真正可用的定时数据同步方案必须分全量初始化和增量续传两阶段,位点持久化严格绑定目标库事务提交;MySQL 用 binlog position(非 updated_at),PG 用 LSN + replication slot,均需位点对齐以避免丢数据。

定时数据同步不能只靠 cron + time.Sleep 拉表,否则漏删、跨事务不一致、位点漂移全是常态。真正可用的方案必须区分「全量初始化」和「增量续传」两个阶段,且位点持久化时机必须严格绑定目标库事务提交。
MySQL 增量同步必须用 binlog position,别碰 updated_at
用 SELECT * FROM t WHERE updated_at > ? 拉数据,看似简单,实则在高并发下必然丢数据:事务提交顺序 ≠ 时间戳写入顺序;同一秒内多事务可能共享毫秒级时间戳;删除操作根本不会更新 updated_at。go-mysql 的 BinlogSyncer 才是正解。
- 启动前确认 MySQL 已开
binlog_format = ROW,server_id全局唯一 - 首次同步从
SHOW MASTER STATUS拿File和Pos,存到本地文件或 etcd;重启时读该位点调Syncer.StartSync(pos) -
RotateEvent必须处理——它表示 binlog 切文件,不更新位点会导致下个文件读错 -
QueryEvent(如 DDL)默认被跳过,需显式设IncludeDDL: true才能同步建表语句
PostgreSQL 增量同步必须用 LSN + replication slot
PG 没有“查某个字段大于 X”的可靠替代方案。pglogrepl 是目前最轻量、协议层最稳的选择,但配置门槛比 MySQL 高:它不接受时间戳或自增 ID 作为起点,只认 LSN。
- 必须提前执行
SELECT * FROM pg_create_logical_replication_slot('my_slot', 'wal2json') -
wal_level必须设为logical,连接用户需有REPLICATION权限 - 收到
pglogrepl.Message后,必须用pglogrepl.ParseMessage解析二进制字段,不能直接读msg.Bytes - LSN 提交(
pglogrepl.SendStandbyStatusUpdate)必须在目标库事务COMMIT成功后才调用,否则主库 WAL 可能被回收导致断连
全量同步不是“一次 SELECT + 循环 SET”,而是位点对齐的兜底动作
全量同步常被误认为“把源表 dump 出来塞进目标库”,但生产环境业务持续写入,你 dump 的那一刻,新数据已在路上。真正的全量必须和增量通道对齐位点,否则必丢数据。
立即学习“go语言免费学习笔记(深入)”;
- 小表(database/sql 分页查,
ORDER BY id防游标偏移,用rdb.Pipelined批量写 Redis - 中大表必须用工具链:如
go-mysql-transfer的full_init功能,它先做全量 dump,再自动切到 binlog 增量监听,并保证两者间无 gap - 全量期间禁止直接清空目标表——应写入临时表,全量完成后再
RENAME TABLE原子切换
定时任务失败后补同步,关键在 checkpoint 持久化时机
失败重试不是“重新跑一遍”,而是从上次成功提交的位点继续。很多人把 checkpoint 存在内存或日志里,服务一挂就回到解放前。
- MySQL 的 binlog position 必须在目标库事务提交后,才写入本地文件或 etcd
- PostgreSQL 的 LSN 必须在
pglogrepl.SendStandbyStatusUpdate成功返回后,才更新本地 checkpoint 表 - 不要依赖定时器本身的“上一次执行时间”——网络抖动、DB 临时不可用、上游限流都会让某次执行延迟甚至失败
- 建议加一层状态表(如
sync_checkpoint),字段含source_type、source_name、checkpoint、updated_at,每次同步完成即UPSERT
最易被忽略的是:全量同步与增量通道之间没有原子协调机制。手写脚本几乎无法可靠实现位点对齐,硬上只会放大风险。工具链不是偷懒,是把已知坑踩过一遍后的封装。


















