最近有朋友问我一个很实际的需求:远程机器上有一张业务表,想实时同步到本地库,不要整库,就要这一张表。问了一圈,有人推荐用定时任务跑mysqldump增量,有人说用binlog解析工具,还有人直接说“干脆两边都连同一个中间件”。其实这个场景最正统、最省事的方案就是MySQL主从复制,而且只配置一张表的过滤规则就能满足“只要这一张表”的要求。主从复制不是只在机房容灾、读写分离里才有用,像这种远程单表实时同步的需求,它一样是首选。
这篇文章我会把“怎么使用MySQL主从复制,把远程库的这张表同步到本地”这个需求完整拆开:先讲原理和选型,再说环境准备与参数配置,然后给出从主库到从库的详细操作步骤,最后把运行中我实际踩过的坑和排查命令整理出来。适合刚接触复制的DBA、后端开发,也适合自己手上有几台机器想快速搭数据同步的同学。
1. 先搞清楚:你要的到底是“备份”还是“实时同步”
1.1 常见同步方案怎么选
很多人一听到“同步”这两个字,第一反应是写个脚本定时把数据导过来,这确实是最容易想到的办法。但在远程单表同步这个场景下,定时任务有很明显的问题:数据是“批量滞后”的,不是实时的,而且每次全量导出对主库的压力都不小,跑在业务高峰期容易把线上拖垮。用定时任务做增量,又得自己去维护binlog位点、处理重复数据,做几次就知道有多痛苦。
下面这张表是我在实际方案选型时习惯做的对比:
| 方案 | 实时性 | 主库影响 | 运维复杂度 | 是否适合远程单表 |
|---|---|---|---|---|
| mysqldump定时全量 | 分钟级甚至小时级 | 高,全量扫描 | 低 | 不适合,数据滞后严重 |
| binlog增量解析工具 | 秒级 | 较低 | 高,需要自己管理位点 | 可以,但要额外部署工具链 |
| MySQL主从复制 | 秒级 | 低,只接收binlog | 低,MySQL自带机制 | 非常适合,天然支持 |
这里的逻辑很简单:MySQL本身就内置了复制能力,把主库的binlog实时传输到从库并执行,底层机制成熟稳定,不需要额外引入中间件,也不用自己处理位点推进和断点续传问题。如果你只需要一张表,那我就在从库上加过滤规则,其他表一概不收,完全满足需求。
1.2 主从复制的核心运行原理
主从复制之所以能实现“远程库的表实时同步到本地”,本质是主库把每一次数据变更记录在binlog里,从库的IO线程远程拉取这些日志,写入自己的relay log(中继日志),再由SQL线程把中继日志里的变更重放到本地表上。整个过程是单向的,从库不会反过来影响主库。
我习惯把这套流程类比成“寄快递”:主库是发货方,binlog就是发货单,IO线程是快递员——它负责把发货单从主库拉到从库仓库,SQL线程是分拣员——按单据把货物重新摆放到货架上。任何一个环节中断,比如快递员网络断了,或者分拣员遇到无法处理的单据卡住了,数据同步就会停在那里。理解了这两个线程的分工,后面排查问题就很好定位了。
这里还有一个关键点要记住:从库默认会开启relay log的自动清理,但binlog是否记录从库自己的操作,取决于log_slave_updates参数。单层复制场景下它不影响主链路,但如果你以后还想把从库继续作为下一层主库,这个参数就必须打开。
2. 搭建前的准备与参数设计
2.1 版本差异与复制账号准备
动手之前,先确认主从两边的MySQL版本。官方推荐从库版本不低于主库版本,比如主库是5.7,从库最好也是5.7或更高;如果主库是8.0,从库用5.7就会出现兼容问题。另一个容易踩的坑是8.0默认的身份认证插件是caching_sha2_password,老版本客户端和部分连接方式不认。我建议为复制专门创建一个账号,并用mysql_native_password作为8.0下的认证插件,避免复制链路建立时提示认证失败。
创建账号和授权的SQL如下:
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'YourStrongPass@123'; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES;有人会问:为什么需要REPLICATION CLIENT权限?因为SHOW MASTER STATUS、SHOW SLAVE STATUS这类监控命令需要它。如果不加,你在从库上查不了主库状态,排障时少了一只眼睛。还有一点,复制账号的host不要只给localhost,因为从库在远程,要用%或者主库能解析到的具体地址。当然,生产环境建议收窄到从库IP。
2.2 主库参数:binlog是复制的地基
主从复制的地基是binlog,所以主库必须先确认binlog已经开启。最常见的检查方式:
SHOW VARIABLES LIKE 'log_bin'; SHOW VARIABLES LIKE 'binlog_format';如果log_bin是OFF,需要修改主库配置文件并重启MySQL。同时,我强烈建议把binlog_format设置为ROW。原因很简单:在表级复制过滤场景下,ROW格式记录的是“哪一行发生了变更”,能精确到具体表;STATEMENT格式记录的是SQL语句本身,执行时如果不小心跨库操作,容易让过滤规则失效。我们把server_id也在这里确认一下,主从两台机器必须用不同的值。
主库配置示例:
[mysqld] server_id = 100 log_bin = mysql-bin binlog_format = ROW expire_logs_days = 7expire_logs_days是日志保留策略。同步任务如果中断超过7天,从库可能因为binlog已被清理而无法续传。你要根据同步的重要程度调整这个值,最好设置为至少保留72小时以上,留足排查和修复的时间。如果用的是MySQL 8.0,expire_logs_days已经废弃,改用binlog_expire_logs_seconds,按秒设置。
2.3 从库参数:只同步一张表,过滤规则怎么配
从库这边,除了设置独立的server_id,还要配置复制过滤规则。常用的过滤参数有三个:
| 参数 | 作用 | 适用场景 |
|---|---|---|
replicate-do-db | 只复制指定数据库 | 按库过滤,规则最简单 |
replicate-do-table | 只复制指定表 | 精确到表,但有一个跨库坑 |
replicate-wild-do-table | 按通配符复制表 | 可以匹配多个表,支持%通配符 |
既然需求是“把远程库的这张表同步到本地”,那直接使用replicate-do-table=remote_db.target_table是一般人会想到的方案。但这里我要重点提醒一个坑:replicate-do-table在ROW格式下,如果主库执行更新前没有USE目标数据库,从库可能会判定这条事件不属于指定表,导致不同步。更多时候我会直接推荐用replicate-wild-do-table,并在主库侧固定USE目标库,双保险。
从库配置示例:
[mysqld] server_id = 200 read_only = ON replicate-wild-do-table = remote_db.target_tableread_only = ON是为了防止本地误写入数据,导致复制和本地修改产生主键冲突。注意:如果从库还要承担别的写入任务,这个参数不能直接打开,需要配合专门的管理账号。
3. 详细操作步骤:把远程库的这张表同步到本地
3.1 步骤一:主库开启binlog并确认当前状态
如果主库已经开启了binlog,直接跳到下一步;如果没开,先修改配置文件,重启MySQL再继续。注意重启前先确认没有长时间运行的大事务,否则重启过程会等事务回滚或提交,业务会受影响。
在操作主机上执行:
mysql -h remote_master_ip -u root -p然后确认主库状态:
SHOW MASTER STATUS;看到类似下面的结果,说明binlog文件已经存在,而且当前有坐标:
+------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +------------------+----------+--------------+------------------+-------------------+ | mysql-bin.000007 | 154 | | | | +------------------+----------+--------------+------------------+-------------------+这个坐标是后面CHANGE MASTER TO的关键。记下File和Position,如果开了GTID,还要留意Executed_Gtid_Set。
3.2 步骤二:在主库创建复制账号
用前面第一节的SQL创建账号和授权。强调一下:不要在从库上执行这条SQL,去主库执行。很多新手把主从的职责搞反了,复制账号必须在主库创建,因为从库要主动连主库来拉日志。
3.3 步骤三:做单表初始数据同步
主从复制只会复制“从某个时间点之后”的增量数据,但远程库的这张表里大概率已经有存量数据了。如果不先同步存量,从库复制启动后会发现目标表不存在,或者数据对不上。
最稳妥的单表初始化命令:
mysqldump -h remote_master_ip -u root -p \ --single-transaction \ --routines=false \ --triggers=false \ --set-gtid-purged=OFF \ --databases remote_db \ --tables target_table \ > target_table.sql--single-transaction的作用是导出过程中不加表锁,利用InnoDB的一致性读保证导出的是一个快照,不会锁住主库的业务写入。--set-gtid-purged=OFF是为了避免导出的SQL里带上GTID信息,否则导入到从库时容易干扰复制链路的GTID匹配。
然后把SQL导入本地从库:
mysql -h local_slave_ip -u root -p < target_table.sql如果从库上本来就有这张表,建议先确认表结构定义一致,再决定是清空导入还是直接覆盖。
3.4 步骤四:配置从库过滤规则并执行CHANGE MASTER
在从库配置文件里加上replicate-wild-do-table,重启MySQL让参数生效。如果你不想重启数据库,8.0可以用CHANGE REPLICATION FILTER动态设置,5.7配置起来稍麻烦。我建议直接用配置文件,规则清晰,重启后也不会失效。
然后执行复制链路的指定。两种方式,任选其一:
方式A,传统的binlog文件名和位点:
CHANGE MASTER TO MASTER_HOST='remote_master_ip', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='YourStrongPass@123', MASTER_LOG_FILE='mysql-bin.000007', MASTER_LOG_POS=154;方式B,GTID自动定位模式:
CHANGE MASTER TO MASTER_HOST='remote_master_ip', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='YourStrongPass@123', MASTER_AUTO_POSITION=1;GTID模式是从MySQL 5.6开始支持的,如果两边都开启了GTID,强烈建议用方式B。它的好处是位点不用人工维护,复制断开了重连会自动跳转到正确位置,不会因为手动填错坐标而重复报错。注意GTID模式下主从两边都必须开启gtid_mode和enforce_gtid_consistency。
3.5 步骤五:启动从库复制并验证
启动复制:
START SLAVE;在8.0里命令可以写成START REPLICA;,两者兼容。启动后马上检查状态:
SHOW SLAVE STATUS\G重点看这几项:
Slave_IO_Running: Yes Slave_SQL_Running: Yes Seconds_Behind_Master: 0 Last_IO_Error: Last_SQL_Error:只要IO和SQL线程都是Yes,Last的错误为空,说明链路已经跑起来了。此时在主库往这张表插入一条测试数据,等一两秒,再从库查询,如果能查到,就说明“远程库的这张表同步到本地”已经完成。
实操时我会再验证一件事:主库往“非目标表”里写一条数据,确认从库不会同步。这样能确认过滤规则真的生效。
4. 运行中的问题排查与避坑清单
4.1 最常见错误对照表
主从复制跑起来不难,真正麻烦的是跑起来之后出现了异常。我把实际运维中最常遇到的错误整理成一张速查表,方便直接对照:
| 错误码 | 现象 | 常见原因 | 处理思路 |
|---|---|---|---|
| 1236 | IO线程报错,拉取binlog失败 | 主库binlog已被清理,或位点超出范围 | 重新做一次全量初始化,再CHANGE MASTER |
| 1062 | SQL线程报主键冲突 | 从库已有相同主键,或重复初始化 | 定位冲突行,在从库删除后让复制跳过冲突,重新执行 |
| 1594 | relay log损坏 | 从库异常断电或磁盘故障 | 重新初始化复制链路 |
| 1872 | 从库回放失败,找不到临时表 | 使用临时表操作且复制中断后 | 在从库重建同名临时表,或跳过该事务 |
| 1208 | 从库内存不足,无法建立连接 | 主库连接数打满 | 检查主库max_connections,调整从库重连策略 |
遇到1062这种主键冲突时,我的处理方式是先看主库端对应行的数据,确认从库冲突数据没有保留价值,然后让SQL线程跳过冲突。命令如下:
STOP SLAVE; SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1; START SLAVE;注意这只能跳过一条错误,不适合连续报错。如果错误很多,大概率是初始数据没对齐,最好重新从头初始化。
4.2 实操中我踩过的三个坑
第一个坑是过滤规则没按预期生效。我帮一个朋友排查时发现,主库执行更新时用了USE 另一个库; UPDATE remote_db.target_table SET ...,在ROW格式下,replicate-do-table直接就不同步了。后来我改成replicate-wild-do-table并且约定主库业务连接固定USE remote_db,问题才彻底解决。千万不要小看 “SQL语句在哪个库下执行” 这件事。
第二个坑是mysqldump导出时默认带了GTID信息。之前我用mysqldump默认参数把单表导入从库,结果START SLAVE后SQL线程一直处于异常状态,报错说GTID不连续。用SHOW VARIABLES LIKE 'gtid_mode'一查才发现从库的GTID集合和主库不一致。加了--set-gtid-purged=OFF之后复制才正常,这个参数不值钱,但很多新手会漏掉。
第三个坑是表结构不一致导致的复制中断。主库那张表有个字段是varchar(255),从库因为建表时疏忽建成了varchar(100),主库插入一条超长字符串后,从库直接报“Data too long for column”。排查半天怎么都没想到是这个低级问题。所以初始化前务必对比SHOW CREATE TABLE,结构不一致的同步链路迟早要出问题。
4.3 延迟与性能问题怎么处理
Seconds_Behind_Master如果持续增大,说明从库回放速度跟不上主库写入速度。最典型的原因是主库出现大事务,比如一次性更新几百万行,binlog总量巨大,从库只能串行回放。解决办法是优化主库写入逻辑,把大事务拆成小批次;如果实在拆不掉,考虑主库业务低谷期再执行。
还有一个经常被忽略的因素:目标表没有主键或唯一索引。从库回放ROW格式的binlog时,每条变更都要通过索引定位那行,没有索引就只能全表扫,性能差距是数量级的。所以建表一定给主键,这不仅是业务规范问题,直接决定复制能不能跟上。
参数层面,从库可以适当调大relay_log_space_limit和 IO线程的缓冲区。多数情况下,把slave_parallel_workers打开,让SQL线程并行回放,也能缓解延迟。不过并行复制依赖主库的binlog格式和事务粒度,不是所有版本都默认支持,5.7以上一般没问题。
5. 收尾前的几点体会
做了这么多年MySQL运维,主从复制在我手里解决的问题非常多:远程单表同步、读写分离、临时分析库的数据抽取、甚至做容灾演练。但我不建议你把生产环境当成第一次试验场,先在测试环境完整跑一遍这套流程,确认过滤规则、权限、端口、表结构都没问题,再应用到线上。
如果条件允许,给从库的复制链路加一个监控脚本,定时检查Slave_IO_Running和Slave_SQL_Running,一旦不是Yes就告警。很多复制故障都是晚上静默发生的,撑到第二天早上发现时,数据已经差了一大截。监控脚本不复杂,用Python或者Shell定时执行SHOW SLAVE STATUS解析结果就行,这比临时抱佛脚要靠谱得多。
最后再分享一个小技巧:初始化完成后,保留好当时的SHOW MASTER STATUS输出和mysqldump生成的文件。以后万一出现1236这种断老日志的问题,你至少知道当前从库是基于哪个位点搭出来的,能大幅缩短排查时间。