如果你手上管着好几套 MySQL,隔三差五要把一张表从生产库同步到分析库、从主库复制到从库、或者在不同环境之间做数据迁移,那你大概率经历过手写脚本的痛苦。Dbsyncer 这个开源数据同步中间件,我之前也只是在 Gitee 上刷到过,后来真拿来配了一次 MySQL 到 MySQL 的全量、增量同步,发现它比我想象中省事不少。这篇就记录一下我从下载到配置、从全量到增量的完整过程,顺便把踩过的坑也一并写出来。
Dbsyncer 的核心思路是把“数据同步”这件事界面化、配置化:你不需要写 Java 代码,不需要自己解析 binlog,更不需要维护一堆定时任务脚本,只需要在它的管理后台里把数据源、同步映射、同步模式配好,剩下的解析、抓取、写入、断点续传它都帮你处理。
先说清楚这篇文章适合谁看:正在为 MySQL 之间数据同步发愁的运维、后端开发、数据分析同学,以及手里管着十几个库但不想为每个库写一遍同步脚本的人。如果你是那种“只想抄作业、不想研究原理”的也可以直接看配置步骤,但我会尽量把关键原理也讲明白,毕竟增量同步这块不懂 binlog 机制,出了问题你连日志都看不懂。
1. 为什么我最终选了 Dbsyncer,而不是继续手写脚本
1.1 三种常见 MySQL 同步方案的对比
我用过不少同步方案,各有各的痛。
第一种是手写脚本。写一个 Python 或 Shell 脚本,定时去源库 SELECT 增量数据,再 UPDATE/INSERT 到目标库。这种方式小表还好,表一多、字段一改、代码一多,维护成本就上来了。更难受的是,如果源库有 DELETE 操作,增量脚本基本没法很好处理,你只能靠软删除标记来“曲线救国”。还有那种凌晨跑批的定时任务,一旦某天数据量大了跑不完,后续任务全堵住。
第二种是Canal + MQ + 消费端自研。Canal 伪装成 MySQL 从库去拉 binlog,然后丢到 Kafka/RabbitMQ,最后自己写消费逻辑写入目标库。这套方案扩展性确实强,一个 Canal 实例可以监听多个库,消息也能被多个下游消费。但它的复杂度也摆在那里:你需要维护 Canal 的集群、消息队列的 topic 规划、消费者的幂等和顺序问题。为了“同步几张表”就上一整套消息队列,多少有点杀鸡用牛刀。
第三种就是我这次用的Dbsyncer 这类开源同步中间件。它直接替你把 binlog 监听、日志解析、数据写入、失败重试这些脏活累活干了,你只需要在界面上点一点、填一填。它不需要额外部署消息队列,单个服务就能跑;也支持界面化管理多个同步任务,每个任务带着自己的独立配置;更重要的是它天然支持全量+增量两种模式,不像手写脚本那样全量是一次性代码、增量又是另一套逻辑。
拿 MySQL 到 MySQL 这种最基础的场景来说,Dbsyncer 的优势特别明显:源库无需改表结构、目标库不用装额外插件,中间件通过 JDBC 连接两边的库,增量则通过读取 binlog 实现。
1.2 Dbsyncer 解决的核心问题与适用场景
我实际用下来,Dbsyncer 解决的其实就三个核心问题:
- 同步任务可视化:同步到哪张表、过滤哪些字段、按什么条件同步,全部在界面配置,新同事接手也看得懂。
- 全量和增量覆盖同一套配置体系:你要全量同步,创建一个全量任务;要增量同步,创建一个增量任务;也可以组合成一个任务先把存量数据拉完、再持续监听新变更。它的数据源、表映射、字段映射这些配置是通用的。
- 断点续传和失败记录:增量任务不会因为中间件重启就丢数据,它会记录 binlog 读取位点,重启后接着上次的位置继续消费。
适用场景上,我总结了几类是非常匹配的:
- 生产库到报表库/OLAP 库的实时同步,比如把订单表、用户表同步到统计分析用的 MySQL 实例。
- 跨环境的数据复制,比如从测试环境同步一份数据到预发环境,省得导 SQL 文件。
- 做 MySQL 之间的容灾备份,虽然不是完整的主从复制,但至少保证某些核心表有第二份副本。
- 多套业务库的数据汇聚,比如把 A 库的用户表、B 库的订单表汇聚到同一个数据中心库。
如果你的需求是“把几十张表持续同步到一个中心库,字段要能改名,表名要能加前缀,删库这种操作要能自动跳过”,那 Dbsyncer 这类中间件比你自己写一套解析 binlog 的工具靠谱得多。
2. 先把环境跑起来:下载、启动与 MySQL 侧准备
2.1 快速启动:解压即用
Dbsyncer 的发行包是免安装的,去 Gitee 的 releases 页面下载对应系统的压缩包就可以。我这边环境是 Linux,所以下的 tar.gz 包。
步骤很直接:
# 下载之后解压 tar -zxvf dbsyncer-x.x.x.tar.gz cd dbsyncer-x.x.x # 启动服务 ./bin/startup.sh启动脚本跑完,服务默认监听1860端口,浏览器访问http://服务器IP:1860就能打开管理界面。第一次登录需要初始化管理员账号,这个和大多数开源软件一样,会让你设置用户名密码。
整个部署过程没有任何依赖的外部组件——不用装 Redis、不用装 Nginx、不用单独配置注册中心,一个 Java 进程搞定一切。这点对只想“快速同步”的场景非常重要。
2.2 启动过程的几个基础检查
启动看起来简单,但有几个前置条件不满足会直接失败或者界面异常:
- JDK 版本:Dbsyncer 基于 Java 开发,需要 JDK 8 及以上。如果你服务器上装的是 JDK 11 或 17 也没问题,我实测过。
- 端口占用:1860 端口一旦被占用,启动日志会直接报端口绑定失败。改端口的话需要去配置文件里找 server 相关的参数改掉。
- 磁盘空间:这个可能很多人忽略。增量同步模式下,如果短时间内 binlog 解析跟不上数据变更,中间件会积压,日志和临时文件会膨胀,磁盘空间不足会引发各种暗病。
启动完成之后,我建议先去“系统管理”里面看一眼版本号和运行状态,确认进程正常,再开始接入数据源。
2.3 MySQL 源库侧必需的 binlog 与账号权限
这一步非常关键。增量同步的基础是 MySQL 的 binlog,所以源库必须开启 binlog,而且格式必须是对增量同步最友好的ROW格式。
检查当前 MySQL 是否开启 binlog,在源库执行:
SHOW VARIABLES LIKE 'log_bin'; SHOW VARIABLES LIKE 'binlog_format';如果log_bin是OFF,就得去my.cnf/my.ini里面加配置然后重启 MySQL:
[mysqld] server_id=101 log_bin=mysql-bin binlog_format=ROW binlog_row_image=FULL expire_logs_days=7这里解释一下几个参数:
server_id是必须的,因为复制场景下的 binlog 需要源实例有唯一标识。binlog_format=ROW让 binlog 记录每一行数据变更前后的完整值,而不是只记录 SQL 语句。增量同步中间件需要拿到“哪一行变了、变成什么样”才能精准同步。binlog_row_image=FULL保证 binlog 里记录行数据的所有列。如果你之前设置过MINIMAL,某些字段的旧值可能拿不到,同步到目标库时会出现意外的数据差异。
MySQL 账号方面的授权,同步账号至少要有对源库的 SELECT 权限,增量模式下还需要 REPLICATION SLAVE、REPLICATION CLIENT,这样才能拉取 binlog 和查询主库状态。示例:
CREATE USER 'sync_user'@'%' IDENTIFIED BY 'YourPassword123'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'sync_user'@'%'; FLUSH PRIVILEGES;目标库那边的账号就不需要 binlog 相关权限了,给个对目标库的 INSERT、UPDATE、DELETE、CREATE、ALTER 权限就够用。如果你要全量同步过来的时候连表结构一起建,那 DDL 权限也就是CREATE、ALTER得给上。
提示:增量同步能不能跑起来,90% 的问题都出在 binlog 配置上。如果你配好任务之后日志一直提示无法读取 binlog、找不到位点、或者连接被拒绝,先回头检查这两项:binlog_format 是不是 ROW、账号有没有 REPLICATION 权限。
2.4 我部署时踩过的小坑
这里记录两个我实际遇到的问题。
第一个是驱动问题。Dbsyncer 管理界面里,接入 MySQL 数据源之前,需要先装 JDBC 驱动。我第一次直接在“添加数据源”里填了连接信息,结果一直提示连不上,后来才发现要先在“驱动管理”里把 MySQL 驱动上传/启用。界面上的步骤顺序是:先配置驱动,再创建数据源。这个顺序别颠倒。
第二个是源库为云数据库时 binlog 保留时长。我有一台源库是云厂商的 RDS,默认 binlog 只保留 1~3 天。如果同步任务停了超过这个时间再恢复,Dbsyncer 记录的 binlog 位点已经失效了,它会报错要求重置任务。如果你用云数据库做源,最好把 binlog 保留时间调长一点,比如 7 天,别让位点过期。
3. 全量同步配置:从连接数据源到跑通第一个任务
3.1 添加数据源与驱动管理的正确顺序
进入 Dbsyncer 管理界面,左侧菜单一般能看到“驱动”“数据源”“同步任务”这几块。正确操作顺序是:
- 在“驱动管理”里确认 MySQL 驱动已经存在。如果没有,上传对应的 JDBC 驱动 jar 包,或者用界面自带的下载功能装一个。
- 在“数据源管理”里添加源库和目标库。这里填的就是常规 JDBC 连接信息:数据库类型、主机、端口、数据库名、用户名、密码。
- 添加完可以在数据源列表里点“测试连接”,能通过才说明网络、账号、驱动都正常。
URL 填法要注意。Dbsyncer 界面上一般会让你填一个地址串,MySQL 的格式类似jdbc:mysql://192.168.1.101:3306/source_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false。
这里几个参数解释一下:
characterEncoding=utf8避免中文乱码。serverTimezone指定时区,不加的话 JDBC 和 MySQL 服务端时区不一致会报错。useSSL=false因为我环境是内网,不开 SSL 省去证书配置。
3.2 创建全量同步任务的参数细节
数据源都添加好之后,进入“同步任务”创建任务。
任务名称自己起一个看得懂的,比如“订单表全量同步”。选择同步方式时,我选了全量同步模式。接下来是表映射关系配置。
这里涉及几个关键参数,挨个说:
源表和目标表的选择
源表可以直接从源库列表里选,目标表如果不存在,可以直接在配置里填一个新的表名。全量同步模式下,如果选择自动建表,中间件会读取源表的 CREATE TABLE 语句在目标库执行,字段类型、索引基本都能保留。这个功能对第一次搭建同步特别友好,省得你先跑一遍 mysqldump。
字段映射
如果你源表有 20 个字段,但目标表只需要 15 个,可以在映射配置里去勾选字段。更细节的是,源字段的表可以映射到目标字段的不同名字。比如源库叫user_id,目标库的表里叫uid,映射关系就是user_id -> uid。
过滤条件
如果不想同步全表数据,可以加过滤条件。比如只同步create_time >= '2024-01-01'的订单,或者只同步status = 1的数据。全量同步时这个条件会拼到 SELECT 语句里,很实用。特别是对于那种“历史数据不要,只要近三个月”的业务场景,加个时间过滤能省不少存储空间。
批量大小
全量同步是一批一批读、一批一批写的,这个参数控制每批读取多少行。默认值我忘了具体多少,但我调过几次之后的体感是:小表设 1000、大表设 5000 都不错。如果批量太大,一次性读入内存的行太多,容易造成 GC 压力;批量太小,又会导致网络往返太多,同步速度上不去。
同步线程数
同批次并行写入的线程数。如果目标库性能还行,把线程数从 1 调到 4,全量导入速度会有明显提升。但这个也不能盲目加,目标库本身 CPU、IO 有瓶颈,线程开太多反而把目标库写挂了。我是先 1 线程跑了几分钟看速度,再慢慢往上加。
这些参数在任务创建之后也能在任务详情里改,不是一锤子买卖。
3.3 全量任务执行验证
配置完成,保存并启动任务。
启动后,任务详情的右侧一般会有运行状态、处理记录数、耗时这些信息。第一次跑的时候我建议盯一下日志输出,确认没有 SQL 语法错误、没有字段长度溢出。
验证是否同步成功,最直接的办法就是对比两边表的行数:
SELECT COUNT(*) FROM source_db.order_table; SELECT COUNT(*) FROM target_db.order_table;如果数量对得上,再抽查几条数据,看关键字段是否一致。文本类型、时间类型最容易出问题,重点看这两类字段。我自己第一次跑全量任务时,源库有一个datetime字段默认值是CURRENT_TIMESTAMP,目标库建表后默认值丢失,导致新插入的行时间字段为空,后来在字段映射里针对这个字段做了一次默认值处理才解决。
还有一个点值得单独拎出来说:全量同步是可以重复执行的。如果你先跑了一次全量,源表又新增了一批数据,再手动触发一次全量同步,通常不会重复插入或者是会覆盖更新,具体行为取决于你有没有在映射里配置主键关联。如果你配置了相同主键,Dbsyncer 会按主键做更新;如果没有主键映射,重复全量可能会导致重复数据。这一点在同步没有主键的表时尤其需要注意。
4. 增量同步配置:binlog 原理与核心参数
4.1 增量同步的底层逻辑:为什么先讲 binlog
增量同步和全量同步最本质的区别是:全量是“对着当前表快照去读”,增量是“持续监听数据库的变更日志”。
MySQL 的变更日志就是 binlog。当你在 MySQL 里执行INSERT、UPDATE、DELETE,只要开了 binlog,这些操作都会以事件的形式写到 binlog 文件里。binlog 有三种格式:
| 格式 | 记录内容 | 优缺点 |
|---|---|---|
| STATEMENT | 记录原始 SQL 语句 | 日志量小,但某些函数结果在目标库重放会不一致 |
| ROW | 记录每一行数据变更前后的值 | 日志量大,但最精准,同步到目标库不需要重新执行 SQL |
| MIXED | 自动选择 STATEMENT 或 ROW | 折中方案,但对解析器要求更高 |
Dbsyncer 这类中间件做增量同步,本质就是一个“binlog 消费端”。它伪装成 MySQL 从库,从主库拉取 binlog 事件,解析出每一行变更的字段值,再组装成 INSERT/UPDATE/DELETE 语句写到目标库。
所以源库的 binlog 格式必须设置成ROW,因为这个格式记录了字段级别的旧值和新值。如果你用的STATEMENT格式,解析器拿不到“哪一行变了”,只能拿到“执行了什么 SQL”,那同步到目标库很多场景都没法做。
理解这个机制之后,你会发现增量同步的一些天然限制也随之而来:
- binlog 里没有的数据拿不到。比如源库在开启 binlog 之前就存在的数据,增量同步是管不了的。
- 同样一条 UPDATE 语句,在 ROW 格式下产生多少个 binlog 事件,取决于实际影响多少行。
- DDL 操作在 ROW 格式下也会产生事件,但中间件一般不做 DDL 同步,它通常会把 DDL 事件跳过,或者根据配置提示任务需要重新初始化。
4.2 增量任务的配置要点
创建增量同步任务时,和全量任务类似的地方是表映射、字段映射、过滤条件这些配置都还在。除此之外有幾個增量特有的配置:
binlog 读取位点
Dbsyncer 会让你选择从什么位置开始读:
- 从当前最新的 binlog 位置开始:只同步任务启动之后的新变更。
- 从最早保留的 binlog 位置开始:会把 binlog 里还保留的所有历史变更都读一遍,等于补数据。
- 手动指定位点:如果你知道自己要从某个 binlog 文件和 offset 开始,可以手动填。
我实际中默认选的是“从当前位点开始”。因为 binlog 保留时间有限,从最早的位点开始拉可能拉到些过期数据,还会因为位点文件已经被清理而报错。如果你要补一段历史窗口的数据,建议先确认 binlog 文件还在不在,别选了个不存在的起点。
断点续传
这是中间件增量同步的看家本领。任务运行过程中,Dbsyncer 会周期性地把当前消费到的 binlog 位点记录下来。如果进程崩溃、网络闪断,重启任务后它会自动从上次记录的位点继续,不用重新跑全量。这就解决了手写脚本最头疼的“同步到一半失败,怎么续上”的问题。
我特意测试过:启动增量任务,插入 100 条数据,看到界面计数涨上去了,然后直接 kill 掉 Dbsyncer 进程,再往源库插入 50 条数据,重启 Dbsyncer,任务自动从 kill 之前的位点开始拉,最终目标库的数据一条不多一条不少。
过滤器
增量同步里的过滤条件通常针对字段值。比如:
- 只同步
type = 1的用户变更,那么配置type = 1作为过滤条件。 - 不同步
is_deleted = 1的软删除数据。
这里要注意一个容易理解偏差的点:过滤器在增量模式下,影响的是写入目标库的行为,而不是影响 binlog 的采集。binlog 里的事件其实都被解析了,只是不符合过滤条件的不写入目标库。所以如果你过滤条件选得很严格,但任务日志里依然显示“解析了 1000 条,写入 0 条”,这是正常的。
4.3 增量任务实测:插入、更新、删除
配置好之后启动增量任务,我做了三个操作来验证:
-- 插入一条 INSERT INTO source_db.user_table (id, name, age) VALUES (1001, '张三', 20); -- 更新一条 UPDATE source_db.user_table SET age = 30 WHERE id = 1001; -- 删除一条 DELETE FROM source_db.user_table WHERE id = 1001;每执行一个操作,去目标库查一次:
SELECT * FROM target_db.user_table WHERE id = 1001;在任务详情里也同步观察处理量变化。实测下来,插入在秒级就过去了,更新、删除也一样。这里如果你发现更新没有同步,大概率是映射关系里没有以主键作为关联条件,中间件不知道“更新哪一行”;如果你发现删除没有同步,检查一下是否配置了“不同步删除”之类的过滤选项,或者目标表是否有外键约束拦住删除操作。
还有一点我特别提一下:增量同步的时效性。它不是实时,而是准实时。因为中间件是线程批量去拉 binlog 并批量写入目标库的,默认一批攒够一定条数或者间隔一定时间就刷一次。正常情况下延迟在 1 秒以内,肉眼看起来就是“刚插入就同步过去了”。在大事务场景下会增加延迟,但不会丢数据。
5. 全量+增量组合使用与踩坑记录
5.1 组合模式:先拉存量,再持续追增量
遇到最常见的情况是:目标表是空的,你要把源表的历史数据全量过去,同时之后源表的新增变更也要实时同步过来。如果只做全量,那全量跑完之后的新增数据就漏了;如果只做增量,那历史存量又不存在。
Dbsyncer 针对这个场景提供了组合模式:全量+增量。
组合模式下的执行逻辑是:
- 任务启动,先执行全量同步,把当前表里的存量数据全部拉到目标库。
- 全量完成之后,自动切换到增量模式,开始从 binlog 拉取新增变更。
- 增量期间的更新、删除同样持续同步。
这样你从头到尾只需要创建一个任务,不用在全量和增量之间手动切换,也不用担心全量跑完到增量启动之间的时间窗口里产生的新数据被漏掉。这个模式我强烈推荐在首次搭建同步链路时用。
有个要注意的细节:全量同步的耗时如果很长,比如几个亿的大表,全量跑一两个小时,那这段时间内产生的 binlog 变更会在全量完成之后被增量部分消费到。Dbsyncer 不是“全量跑完再开始记增量”,而是任务创建时就开始基于 binlog 记录变更了。如果你用的是组合模式,不用担心全量期间的增量丢失。
5.2 我遇到的实际问题与排查过程
这里说三个真的会让人卡半天的坑,我把排查过程写出来,你遇到了可以照着查。
第一个坑:源表有主键但目标表建表后主键丢了
现象:增量任务启动正常,插入、更新都同步了,但删除不同步,日志里频繁出现“未找到目标行”之类的提示。
排查:我去目标库看表结构,发现表确实建起来了,但主键索引没建。Dbsyncer 在自动建表时,对某些特殊主键类型的映射不完整,导致主键丢失。没有主键,中间件没办法定位到具体要删除的行,删除事件就被丢弃了。
解决:手动给目标表补上主键,然后重启任务。如果你在下游还需要做更新、删除,源表和目标表的主键映射是刚需。
第二个坑:字符集不一致导致中文乱码
现象:全量同步后,目标库的中文全部变成乱码。
排查:我先怀疑 Dbsyncer 的连接字符串没配characterEncoding=utf8,检查之后发现配了。再看目标库的表结构,发现表的默认字符集是latin1,而源表是utf8mb4。问题不在中间件,在目标库建表语句继承的库级字符集。
解决:在创建数据源连接的时候,URL 里把characterEncoding=utf8加上还不够,还需要在 Dbsyncer 的映射或者建表配置里显式指定字符集,或者在目标库里把库、表的默认字符集改成utf8mb4。我最后是在目标库里把整张表的字符集改掉再重新同步的。
第三个坑:大事务导致增量延迟飙升
现象:业务侧跑了一个批量更新脚本,一次性更新了 50 万行的状态字段,增量任务日志显示连续处理了很长时间,界面上看到的同步延迟从 1 秒飙到几十分钟。
排查:这是 binlog 机制决定的,不是 Dbsyncer 的问题。一条 UPDATE 语句影响 50 万行,在 ROW 格式下对应的 binlog 事件就是 50 万个行变更事件,中间件必须逐条解析、逐条写入。加上我配置的批量写入是攒批的,攒够一批才提交,单批写入量太大也拖慢了整体速度。
解决:把批量大小调低,比如从 5000 降到 1000,让写入更频繁但单次压力更小;同时把同步线程数调高一点,让写入阶段有更多并发。大事务这种场景延迟感依然存在,但不会拿不到数据。最核心的思路是让中间件“小步快跑”,别让单批积压太大。
5.3 几个调优建议:线程数、批量大小、定时策略
基于实测,我给出几组参考值,具体可以根据机器配置调整:
| 参数 | 小表(万级以下) | 大表(百万以上) | 说明 |
|---|---|---|---|
| 全量批量大小 | 1000 | 5000 | 单位是行/批 |
| 全量同步线程数 | 2 | 4~8 | 太高容易压垮目标库 |
| 增量批量大小 | 500 | 1000 | 增量是小步快跑,不宜过大 |
| 增量线程数 | 1 | 2~4 | 线程多了对大事务延迟改善明显 |
定时策略这里多说一句。Dbsyncer 本身是支持定时触发全量同步的,有些场景比如“每天凌晨把生产库的配置表刷一份到分析库”,你确实可以配置每天几点跑一次全量。但如果你已经开了增量同步,就不需要每天都全量跑一遍了,增量会把所有变更都送过去。全量定时更适合“定期重置一份快照”的场景,比如每天晚上把数据重置回凌晨的状态。
顺带说一个我自己觉得好用的习惯:在大版本变更之前,手动跑一次全量同步做基准比对。比如业务表调整了字段字典、改了枚举值含义,这种变更靠增量同步很难发现语义变化,手动全量跑一次,然后对比源表和目标表的数据,能帮你快速发现那些增量链路覆盖不到的数据漂移。
5.4 维护期容易忽略的细节
同步链路建好只是开始,维护期有几件事我建议你刻在脑子里:
- 源库 binlog 清理策略:
expire_logs_days设得太短会让断点续传失效。我建议按业务容忍度设置,至少保 3 天以上。 - 版本升级前看释放说明:Dbsyncer 偶尔发布新版本,升级前最好先备份配置和当前同步任务的位点信息,万一升级后界面配置格式有变化,你还能回滚。
- 磁盘和日志监控:中间件日志增长量不能忽视,特别是增量任务长期跑的情况下,日志文件如果不做轮转,迟早把磁盘吃满。建议定期清理或者交给 logrotate 处理。
- 同步账号最小化:源库账号不需要 DDL 权限,目标库账号不需要 REPLICATION 权限。权限给太大,万一账号泄露影响面也大。
我个人的操作习惯是,同步任务上线初期,每天去看一次任务详情的增量延迟和错误计数;跑了一周稳定之后,改成每周看一次;稳定一个月后,除非有新表加入,否则基本不用管它。中间件这东西,配置好之后最大的价值就是让你“忘记它存在”,但前提是监控告警得先做好,一旦任务停了你能第一时间知道,而不是三个月后才发现目标库的数据已经落后几万条了。