直接用 time.Sleep 轮询会漏数据或延迟,因无法应对写入密集与延迟不均;可靠增量同步需依赖可比较、单调递增、可定位的位点,如 MySQL 的 binlog position、PG 的 LSN 或自增 ID(单写场景)。

为什么直接用 time.Sleep 做轮询同步会出问题
因为数据库写入可能密集、延迟不均,time.Sleep 固定间隔会导致漏数据或重复拉取。比如上游 1 秒内插入 5 条,但你的轮询周期是 3 秒,中间那批就可能被跳过;若刚查完就插入一条,下一轮才查,这条就“滞留”了 3 秒。
真正可用的增量同步必须依赖**可比较、单调递增、能定位位置**的位点。常见选择有:
-
MySQL:用binlog position或带索引的updated_at+id复合条件(注意时钟漂移) -
PostgreSQL:用logical replication slot或LSN(推荐) - 通用兜底:用自增
id(仅限单写、无删改场景)
用 go-mysql-org/go-mysql 实现 MySQL binlog 增量监听
这个库封装了 MySQL 的 binlog dump 协议,比自己解析 SHOW BINLOG EVENTS 稳定得多,也支持断线重连和位点续传。
关键配置和易错点:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 启动前必须确保 MySQL 开启
binlog,且binlog_format = ROW(否则拿不到行级变更) -
server_id必须全局唯一,不能和其他复制节点冲突,否则 MySQL 会拒绝连接 - 首次启动建议从
SHOW MASTER STATUS拿最新File和Position,存到本地文件或 etcd;重启时读该位点,调用Syncer.StartSync(pos) - 收到
*replication.RowsEvent后,event.Table和event.Action(Insert/Update/Delete)要判空再用,有些 event 可能不含行数据(如 DDL)
示例片段:
syncer := replication.NewBinlogSyncer(&replication.BinlogSyncerConfig{
ServerID: 1001,
Flavor: "mysql",
Host: "127.0.0.1",
Port: 3306,
User: "repl",
Password: "xxx",
})
streamer, _ := syncer.StartSync(mysql.Position{File: "mysql-bin.000001", Pos: 4})
for {
ev, _ := streamer.GetEvent(context.Background())
switch x := ev.Event.(type) {
case *replication.RowsEvent:
if x.Action == replication.DeleteAction {
// 处理删除
}
}
}用 pglogrepl 实现 PostgreSQL 逻辑复制同步
pglogrepl 是目前最轻量、最贴近协议层的 Go PG 逻辑复制客户端,它不依赖 libpq,纯 Go 实现,适合嵌入长期运行的服务中。
和 MySQL 不同,PG 的逻辑复制需要先创建 replication slot,并指定输出插件(如 pgoutput 或 wal2json)。如果你只关心结构化变更,wal2json 插件更友好。
- 首次连接前,手动执行:
SELECT * FROM pg_create_logical_replication_slot('my_slot', 'wal2json'); - 连接时传入 slot 名和初始 LSN(可用
pg_current_wal_lsn()获取),断线重连后从上次保存的 LSN 继续 -
pglogrepl.ReceiveMessage()返回的是原始字节流,需用wal2json.ParseMessages()或自己按协议解析;注意CommitMessage里含FinalLSN,这才是本次事务真正的结束位点 - 别在处理单条消息时直接更新下游——PG 允许大事务分多包发送,必须等
CommitMessage才能确认事务完成
如何安全地保存和恢复同步位点
位点存储不是“记个数字”那么简单。写失败、顺序错、没刷盘,都会导致重复或丢数据。
- MySQL
Position:用os.WriteFile写临时文件 +os.Rename原子替换,避免写一半崩溃;不要用 JSON 格式存,直接写"mysql-bin.000001\t12345"字符串,解析快、容错强 - PostgreSQL
LSN:推荐存在外部协调服务(如 etcd),用CompareAndSwap确保不会被其他实例覆盖;如果只能用本地文件,每次写前先flock加锁(Go 可用gofrs/flock) - 无论哪种,位点更新必须发生在「下游成功提交」之后,而不是收到事件就更新——否则下游写失败,位点已进,下次就跳过这批数据
最常被忽略的一点:没有做位点校验。上线前应人工构造一笔测试数据,验证从位点 A 同步到 B,再从 B 重启,是否能继续、不重复、不遗漏。这步跳过,90% 的“同步稳定”都是假象。

















