【MySQL篇|第五篇】数据同步

本文系统讲解MySQL数据同步的四大主流方案:基于binlog的原生主从复制、异构系统CDC(如Canal)、定时批量同步及业务层双写。重点剖析binlog机制、ROW格式优势、半同步与MGR复制模式、Canal工作原理、增量同步策略(时间戳/主键)、主从延迟与数据一致性保障措施,并给出选型建议,强调位点管理、监控告警与异常恢复等运维关键点。

一、什么是数据同步

数据同步是指把一份数据从一个存储位置复制到另一个存储位置,并保证两端数据在内容上保持一致的过程。在真实业务中,数据同步几乎无处不在:主从复制保证高可用、读写分离分摊压力、异地机房做容灾、数据仓库做离线分析、缓存与数据库之间保持一致,背后都离不开数据同步。

从同步方式上看,常见的数据同步可以分为几类:

  • 全量同步:一次性把源端的全部数据复制到目标端,适合初始化场景,但数据量大时耗时长、对源库压力大。我们在做全量同步的时候,尽量的选择到一个用户使用量较低的时段进行一个同步,或者对于一些大型的项目,比如我们市面上常见的一些大型游戏,他们会选择一个时间进行停服处理,在处理完毕后会重新开放给用户。
  • 增量同步:只同步发生变化的数据,通常依赖日志或时间戳,是生产环境的主流方式。这是我们常见的一个方案,通常在那些集群中会经常用到,用于同步各个集群之间的数据,以及主节点从节点之间的数据。
  • 实时同步:变化发生后毫秒到秒级即可到达目标端,依赖 binlog 等变更日志。
  • 准实时/定时同步:按固定周期批量拉取,实现简单,但存在延迟。

从一致性上看,又可以分为强一致与最终一致。绝大多数互联网业务选择最终一致,用可接受的延迟换取吞吐和可用性。

二、MySQL 实现数据同步的核心基础:binlog

MySQL 实现数据同步的根基是 binlog(二进制日志)。它记录了数据库中所有引起数据变更的语句或行变更,是主从复制、数据订阅、CDC(Change Data Capture)的统一数据源。

2.1 binlog 的三种格式

格式记录内容优点缺点
STATEMENT记录原始 SQL 语句日志量小含 NOW()、UUID()、LIMIT 等不确定函数时主从不一致
ROW记录每一行变更前后的值一致性最好,可精确还原日志量大,批量更新时膨胀明显
MIXED由 MySQL 自动在两者间选择兼顾体积与一致性行为不够直观,排查稍复杂

生产环境一般推荐 ROW 格式,尤其是需要做数据订阅、审计或跨异构系统同步时,ROW 格式能提供最完整的变更信息。

2.2 开启 binlog

在 my.cnf 中配置并重启 MySQL:

[mysqld]
server-id = 1
log-bin = mysql-bin
binlog_format = ROW
binlog_row_image = FULL
expire_logs_days = 7

其中 binlog_row_image = FULL 表示记录变更行的全部列,便于下游还原完整数据;expire_logs_days 控制日志保留天数,避免磁盘被写满。

配置完成后可以通过以下命令确认:

SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_format';
SHOW BINARY LOGS;

三、方案一:MySQL 原生主从复制

主从复制是 MySQL 最经典、最常用的数据同步方式,本质上就是基于 binlog 的异步复制。

3.1 复制流程

flowchart LR
    A[主库 Master] -- 写入变更 --> B[binlog]
    B -- dump 线程推送 --> C[从库 IO 线程]
    C -- 写入 --> D[relay log 中继日志]
    D -- SQL 线程回放 --> E[从库数据]
    F[从库] -- 上报位点 --> A

整个过程分为三步:

  1. 主库把数据变更写入 binlog。
  2. 从库的 IO 线程连接主库,由主库的 dump 线程把 binlog 事件推送给从库,从库写入本地 relay log。
  3. 从库的 SQL 线程读取 relay log 并回放,最终数据与主库保持一致。

3.2 搭建步骤

第一步:主库创建复制账号

CREATE USER 'repl'@'%' IDENTIFIED BY 'Repl@123456';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

第二步:主库获取当前 binlog 位点

FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;
-- 记录 File 和 Position,例如 mysql-bin.000003 / 154
UNLOCK TABLES;

第三步:导出主库数据并导入从库

mysqldump -uroot -p --single-transaction --master-data=2 \
  --databases demo > demo.sql
mysql -uroot -p < demo.sql

--single-transaction 保证 InnoDB 表在一致性快照下导出,不锁表;--master-data=2 会把 binlog 位点以注释形式写入备份文件。

第四步:从库配置并启动复制

CHANGE MASTER TO
  MASTER_HOST = '192.168.1.10',
  MASTER_PORT = 3306,
  MASTER_USER = 'repl',
  MASTER_PASSWORD = 'Repl@123456',
  MASTER_LOG_FILE = 'mysql-bin.000003',
  MASTER_LOG_POS = 154;
START SLAVE;
SHOW SLAVE STATUS\G

当 Slave_IO_Running 和 Slave_SQL_Running 都为 Yes,且 Seconds_Behind_Master 逐步归零时,说明复制已经正常工作。

3.3 复制模式的选择

  • 异步复制:主库提交后立即返回,不等待从库确认,性能最好,但主库宕机可能丢数据。
  • 半同步复制:主库至少等待一个从库确认收到 binlog 后才返回,兼顾性能与数据安全。
  • 组复制(MGR):基于 Paxos 协议的多节点一致性复制,支持自动选主,适合对一致性要求高的场景。

开启半同步复制示例:

INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;
SET GLOBAL rpl_semi_sync_master_timeout = 1000;

四、方案二:基于 binlog 的异构数据同步(CDC)

当目标端不是 MySQL,而是 Elasticsearch、Kafka、ClickHouse、Redis 等系统时,主从复制就不适用了。这时通常采用 CDC 方案,直接订阅 binlog 并转换为目标端可消费的消息。

4.1 常见工具

工具特点适用场景
Canal阿里开源,伪装成从库拉取 binlogMySQL 到 ES/Redis 的实时同步
Maxwell轻量,输出 JSON 格式中小规模数据订阅
Debezium基于 Kafka Connect,生态完善企业级 CDC 平台
Flink CDC流式计算 + CDC,支持全增量一体实时数仓、复杂加工

4.2 Canal 工作原理

flowchart LR
    A[MySQL Master] --> B[binlog]
    B --> C[Canal Server 伪装从库]
    C --> D[Canal Client]
    D --> E[Elasticsearch]
    D --> F[Kafka]
    D --> G[Redis]

Canal 把自己伪装成一个 MySQL 从库,向主库发送 dump 协议请求,主库把 binlog 推送给它。Canal 解析 binlog 后转换成结构化数据,客户端再根据业务逻辑写入目标端。

4.3 一个简单的 Canal 客户端示例

public class CanalClientDemo {
    public static void main(String[] args) throws Exception {
        CanalConnector connector = CanalConnectors.newSingleConnector(
                new InetSocketAddress("127.0.0.1", 11111),
                "example", "", "");
    connector.connect();
    connector.subscribe("demo\\..*");
    connector.rollback();
while (true) {
    Message message = connector.getWithoutAck(100);
    long batchId = message.getId();
    if (batchId == -1 || message.getEntries().isEmpty()) {
        Thread.sleep(1000);
        continue;
    }
    for (CanalEntry.Entry entry : message.getEntries()) {
        if (entry.getEntryType() != CanalEntry.EntryType.ROWDATA) {
            continue;
        }
        CanalEntry.RowChange rowChange =
                CanalEntry.RowChange.parseFrom(entry.getStoreValue());
        for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
            System.out.println("表:" + entry.getHeader().getTableName()
                    + ",事件:" + rowChange.getEventType()
                    + ",变更后:" + rowData.getAfterColumnsList());
        }
    }
    connector.ack(batchId);
}
}
}

注意 connector.ack(batchId) 必须在业务处理成功之后调用,否则消息会重复消费;处理失败时调用 rollback 让 Canal 重新投递。

五、方案三:定时任务批量同步

对于实时性要求不高的场景,比如每天凌晨把业务库数据同步到报表库,用定时任务做增量拉取是最简单直接的方式。

5.1 基于时间戳的增量同步

-- 每次只拉取上次同步之后更新的数据
SELECT id, name, amount, update_time
FROM orders
WHERE update_time > #{lastSyncTime}
  AND update_time <= #{currentTime}
ORDER BY update_time
LIMIT 1000;

这种方式实现简单,但有两个坑需要注意:

  • 如果业务表没有可靠的 update_time,或者更新时没有刷新该字段,就会漏数据。
  • 物理删除的数据无法通过时间戳感知,需要配合逻辑删除或定期全量校对。

5.2 基于自增主键的分页同步

SELECT id, name, amount
FROM orders
WHERE id > #{lastMaxId}
ORDER BY id
LIMIT 1000;

用自增主键做游标可以避免深分页问题,但同样无法感知更新和删除,通常只适合只增不改的日志类数据。

六、方案四:双写与业务层同步

有些场景下,同步逻辑直接写在业务代码里,比如写 MySQL 的同时写一份到 Redis 或 ES。这种方式实现直观,但问题也很明显:

  • 两个写操作不在同一个事务里,任何一个失败都会造成数据不一致。
  • 业务代码与同步逻辑耦合,后续新增目标端需要改代码。
  • 并发写同一行时容易出现覆盖顺序错乱。

如果确实需要双写,建议采用「先写数据库,再发消息,由消费者异步写目标端」的模式,并配合本地消息表或事务消息保证最终一致。

七、数据同步中的常见问题与应对

7.1 主从延迟

主从延迟是最常见的问题,表现为从库读到的数据比主库旧。常见原因包括大事务、从库单线程回放、从库硬件较弱等。应对手段有:

  • 开启并行复制:slave_parallel_workers = 8,并设置 slave_parallel_type = LOGICAL_CLOCK。
  • 拆分大事务,避免一次更新几十万行。
  • 关键读请求走主库,或使用半同步复制降低延迟风险。

7.2 数据不一致

可以通过 pt-table-checksum 定期校验主从数据一致性,发现差异后用 pt-table-sync 修复。对于 CDC 链路,则需要在消费端做幂等处理,避免重复消费导致数据错乱。

7.3 同步中断

常见的中断原因包括主库 binlog 被清理、从库执行出错、网络抖动等。建议:

  • 合理设置 binlog 保留时间,确保从库或 CDC 消费端不会因为位点过期而无法恢复。
  • 监控 Seconds_Behind_Master 和复制线程状态,异常时及时告警。
  • 对 CDC 消费端做位点持久化,重启后能从上次位置继续。

7.4 大表全量初始化

首次同步大表时,直接 mysqldump 可能锁表或耗时过长。可以采用「全量快照 + 增量追平」的方式:先记录 binlog 位点,再用一致性快照导出全量数据,导入完成后从记录的位点开始消费增量,最终追平。

八、方案选型建议

场景推荐方案说明
高可用、读写分离原生主从复制 / MGR成熟稳定,运维成本低
同步到 ES、Redis、KafkaCanal / Debezium / Flink CDC基于 binlog,实时性好
离线报表、T+1 分析定时任务批量同步实现简单,对源库压力可控
跨机房容灾主从复制 + 半同步兼顾数据安全与延迟
实时数仓Flink CDC全增量一体,支持复杂加工

九、总结

MySQL 实现数据同步的核心是 binlog。围绕它衍生出三条主流路径:原生主从复制解决高可用与读写分离,CDC 工具解决异构系统实时同步,定时任务解决离线批量同步。选型时先明确实时性要求和目标端类型,再考虑一致性与运维成本。无论采用哪种方案,都要重点关注主从延迟、数据校验、位点管理和异常监控,才能让同步链路长期稳定运行。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值