暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

MySQL 主从复制错误跳过

适用版本:MySQL 5.7 / 8.0 / 8.0.26+ / 8.4
工作模式:主从复制(传统点位模式 / GTID 模式限制说明见正文)
核心场景:主从复制中断错误跳过与幂等恢复


一、环境与版本说明

项目 说明
主库版本 MySQL 5.7 / 8.0 / 8.4(参数名有差异,见各章节)
复制模式 传统点位模式(binlog + position),GTID 模式限制见各节
binlog 格式 ROW(推荐;部分测试场景适用)
测试表引擎 InnoDB(事务表)

版本差异说明(参数名变更)

MySQL 8.0.26 起,多个复制相关参数被重命名(旧名称保留为废弃别名,仍可使用,但建议迁移至新名称):

旧名称(≤ 8.0.25,已废弃) 新名称(≥ 8.0.26) 是否可动态修改
slave_skip_errors replica_skip_errors ❌ 否(静态参数)
sql_slave_skip_counter sql_replica_skip_counter ✅ 是(动态参数)
slave_exec_mode replica_exec_mode ✅ 是(动态参数)
SHOW SLAVE STATUS SHOW REPLICA STATUS
START SLAVE START REPLICA
STOP SLAVE STOP REPLICA

二、参数速览对比

参数 作用范围 动态修改 GTID 支持 跳过粒度 风险等级
replica_skip_errors 全局,持久生效 ❌ 静态 ✅ 支持 按错误码 ⚠️ 中(可能累积数据差异)
sql_replica_skip_counter 全局,下次 START REPLICA 生效 ✅ 动态 ❌ 不支持 按 event group 数 ⚠️ 中(可能跳多个 event)
replica_exec_mode=IDEMPOTENT 全局,立即生效 ✅ 动态 ✅ 支持 按错误类型智能处理 ✅ 低(有覆写逻辑,保持一致性)

三、slave_skip_errors(replica_skip_errors)

3.1 参数说明

slave_skip_errors(MySQL 8.0.26+ 新名称:replica_skip_errors)是全局静态参数,只能写入配置文件后重启 MySQL 生效,不支持在线修改

作用:配置从库复制过程中可自动跳过的错误号列表,遇到对应错误后继续执行后续 binlog 事件,不停止 SQL 线程。

3.2 可选值

含义
OFF 默认值,不跳过任何错误
错误码列表 逗号分隔的错误号,如 1062,1032
all 跳过所有错误(生产环境谨慎使用
ddl_exist_errors MySQL 5.6+ 支持,包含以下错误码的预设集合

ddl_exist_errors 包含的错误码:

错误码 含义
1007 数据库已存在,CREATE DATABASE 失败
1008 数据库不存在,DROP DATABASE 失败
1050 数据表已存在,CREATE TABLE 失败
1051 数据表不存在,DROP TABLE 失败
1054 字段不存在,或 schema 不一致
1060 字段重复,无法插入
1061 重复键名
1068 定义了多个主键
1094 无效线程 ID
1146 数据表不存在

其他常用错误码:

错误码 含义
1032 记录未找到(UPDATE/DELETE 对应行不存在)
1053 复制过程中主库宕机
1062 主键冲突(Duplicate entry for key PRIMARY)

3.3 配置方式

在 my.cnf(my.ini)中配置(重启生效):

# MySQL 5.7 / 8.0(≤ 8.0.25) [mysqld] slave_skip_errors = 1062,1032 # MySQL 8.0.26+(推荐使用新参数名) [mysqld] replica_skip_errors = 1062,1032 # 跳过所有 DDL 存在性错误 replica_skip_errors = ddl_exist_errors # 跳过所有错误(生产慎用) replica_skip_errors = all

作为启动参数:

# MySQL 8.0.26+ 新参数名 mysqld --replica-skip-errors=1062,1032 # 兼容旧参数名 mysqld --slave-skip-errors=1062,1032

查看当前参数值:

-- MySQL 8.0.26+ 新参数名 SHOW VARIABLES LIKE 'replica_skip_errors'; -- 兼容旧参数名 SHOW VARIABLES LIKE 'slave_skip_errors';

输出示例:

+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| replica_skip_errors | 1062  |
+---------------------+-------+

3.4 实战示例:跳过主键冲突 1062

场景说明: 从库提前写入了某行数据,主库同步时发生主键冲突,导致 SQL 线程停止。

第一步:在从库上人为制造主键冲突(模拟误操作)

-- 在主库上建表 CREATE TABLE replication (c1 INT NOT NULL PRIMARY KEY, c2 VARCHAR(10)); -- 主库插入基础数据(会同步到从库) INSERT INTO replication VALUES (1, 'test1'); INSERT INTO replication VALUES (2, 'test2');
-- 从库上手动插入 id=3 的记录(破坏主从一致性) INSERT INTO replication VALUES (3, 'test3');
-- 主库再次插入 id=3(会生成 binlog 同步到从库,导致冲突) INSERT INTO replication VALUES (3, 'test3');

第二步:在从库上查看复制状态,确认错误

-- MySQL 8.0.26+ SHOW REPLICA STATUS\G -- MySQL 5.7 / 8.0(≤ 8.0.25) SHOW SLAVE STATUS\G

关键输出字段:

Slave_SQL_Running: No
Last_Errno:        1062
Last_Error: Error 'Duplicate entry '3' for key 'PRIMARY'' on query.
            Default database: 'test'.
            Query: 'insert into replication values (3, 'test3')'

第三步:在 my.cnf 中加入跳过配置,重启 MySQL

[mysqld] # MySQL 8.0.26+ replica_skip_errors = 1062
# 重启 MySQL 服务 systemctl restart mysqld

⚠️ 注意: 启用此参数后,从库将自动跳过所有 1062 错误,可能导致主从数据长期不一致。建议恢复后及时关闭参数,并使用 pt-table-checksum + pt-table-sync 校验数据。


四、sql_slave_skip_counter(sql_replica_skip_counter)

4.1 参数说明

sql_slave_skip_counter(MySQL 8.0.26+ 新名称:sql_replica_skip_counter)是全局动态参数,可在线设置,但从下一次 START REPLICA 开始生效

作用:跳过来自主库的指定数量的 event group(事务组),每次手动触发,不持久保存。

⚠️ GTID 模式不适用: 在 GTID 复制模式下,不支持 sql_replica_skip_counter,应改用 GTID 空事务注入方式。

4.2 GTID 模式下的替代方案

-- GTID 模式下,注入空事务跳过指定 GTID -- 1. 查看从库当前报错的 GTID(从 SHOW REPLICA STATUS 的 Last_Error 中获取) SHOW REPLICA STATUS\G -- 2. 注入空事务(将 <gtid_value> 替换为报错的 GTID) SET GTID_NEXT = '<source_uuid>:<transaction_id>'; BEGIN; COMMIT; SET GTID_NEXT = 'AUTOMATIC'; -- 3. 启动复制 START REPLICA;

4.3 使用方式

-- Step 1:停止 SQL 线程 STOP REPLICA SQL_THREAD; -- MySQL 8.0.26+ -- STOP SLAVE SQL_THREAD; -- MySQL ≤ 8.0.25 -- Step 2:设置跳过 N 个 event group SET GLOBAL sql_replica_skip_counter = 1; -- MySQL 8.0.26+ -- SET GLOBAL sql_slave_skip_counter = 1; -- MySQL ≤ 8.0.25 -- Step 3:启动 SQL 线程 START REPLICA SQL_THREAD; -- MySQL 8.0.26+ -- START SLAVE SQL_THREAD; -- MySQL ≤ 8.0.25 -- Step 4:验证复制状态 SHOW REPLICA STATUS\G

4.4 关于跳过粒度的重要说明

sql_replica_skip_counter = N 的含义是跳过 N 个 event group(事务组),而不是 N 个 SQL 语句。

  • 单条 SQL 组成的事务= 1 跳过该条事务
  • 多条 SQL 组成的事务= 1 跳过整个事务(包含所有 SQL)

关键区别: 对于多条 SQL 组成的事务,设置 = 1 会跳过整个 event group(从 BEGIN 到 COMMIT 之间的全部 event),而不是只跳过报错的那一条 SQL。这意味着事务中其他正常的 SQL 也会被跳过,可能造成数据丢失。

4.5 实战示例一:跳过单条 SQL 事务(1032 错误)

场景说明: 从库缺少某些行,主库对这些行执行 UPDATE / DELETE 时,从库找不到记录,报 1032 错误。

从库手动删除测试数据(模拟数据不一致):

-- 从库上删除 id=9 和 id=11 的数据 DELETE FROM edusoho_e.t1 WHERE id = 9; DELETE FROM edusoho_e.t1 WHERE id = 11;

主库执行变更(会同步到从库):

INSERT INTO edusoho_e.t1 (xname, address, hobby) VALUES ('孙权', '吴国', '妹妹'); UPDATE edusoho_e.t1 SET xname = '游戏' WHERE id = 7; UPDATE edusoho_e.t1 SET age = 40 WHERE id = 11; -- 从库无此行,报 1032 DELETE FROM edusoho_e.t1 WHERE age = 40; -- 从库同样报 1032 INSERT INTO edusoho_e.t1 (xname, address, hobby) VALUES ('曹丕', '魏国', '甄姬');

从库查看复制状态:

SHOW REPLICA STATUS\G

关键输出:

Slave_IO_Running:  Yes
Slave_SQL_Running: No
Last_Errno:        1032
Last_Error: Could not execute Update_rows event on table edusoho_e.t1;
            Can't find record in 't1', Error_code: 1032
Exec_Master_Log_Pos: 874

在主库上查看 binlog,确认 position 对应的事务内容:

-- 以 position 874 为起点查看 binlog 事件 SHOW BINLOG EVENTS IN 'mysql-bin.000002' FROM 874;

输出:

+------------------+------+-------------+-----------+-------------+---------------------------------+
| Log_name         | Pos  | Event_type  | Server_id | End_log_pos | Info                            |
+------------------+------+-------------+-----------+-------------+---------------------------------+
| mysql-bin.000002 |  874 | Query       |         2 |         956 | BEGIN                           |
| mysql-bin.000002 |  956 | Table_map   |         2 |        1017 | table_id: 213 (edusoho_e.t1)    |
| mysql-bin.000002 | 1017 | Update_rows |         2 |        1127 | table_id: 213 flags: STMT_END_F |
| mysql-bin.000002 | 1127 | Xid         |         2 |        1158 | COMMIT /* xid=437 */            |
| mysql-bin.000002 | 1158 | Query       |         2 |        1240 | BEGIN                           |
| mysql-bin.000002 | 1240 | Table_map   |         2 |        1301 | table_id: 213 (edusoho_e.t1)    |
| mysql-bin.000002 | 1301 | Delete_rows |         2 |        1407 | table_id: 213 flags: STMT_END_F |
| mysql-bin.000002 | 1407 | Xid         |         2 |        1438 | COMMIT /* xid=446 */            |
+------------------+------+-------------+-----------+-------------+---------------------------------+

可以看到,Pos 874 是一个 单条 SQL 的 UPDATE 事务(BEGIN → Update_rows → COMMIT),Pos 1158 是另一个 单条 SQL 的 DELETE 事务

在从库上逐个跳过报错事务:

-- 跳过第一个 Update_rows event(id=11 更新报错) STOP REPLICA SQL_THREAD; SET GLOBAL sql_replica_skip_counter = 1; START REPLICA SQL_THREAD; SHOW REPLICA STATUS\G -- 预期:Exec_Master_Log_Pos 推进到 1158,SQL 线程仍停止(第二个报错) -- 跳过第二个 Delete_rows event(age=40 删除报错) STOP REPLICA SQL_THREAD; SET GLOBAL sql_replica_skip_counter = 1; START REPLICA SQL_THREAD; SHOW REPLICA STATUS\G -- 预期:Slave_SQL_Running: Yes,Last_SQL_Errno: 0,复制恢复

4.6 实战示例二:多条 SQL 组成的事务 —— 整个事务被跳过

场景说明: 从库缺少 id=7 的数据,主库提交了一个包含两条 SQL 的事务,报 1032 错误。

从库手动删除数据:

DELETE FROM edusoho_e.t1 WHERE id = 7;

主库提交多条 SQL 事务:

BEGIN; DELETE FROM edusoho_e.t1 WHERE id = 7; -- 从库无此行,会报 1032 INSERT INTO edusoho_e.t1 (xname, address, hobby) VALUES ('懒死', '不知道', '吃了睡睡了吃'); COMMIT;

主库查看 binlog 事件:

SHOW BINLOG EVENTS IN 'mysql-bin.000002' FROM 6840;

输出:

+------------------+------+-------------+-----------+-------------+---------------------------------+
| Log_name         | Pos  | Event_type  | Server_id | End_log_pos | Info                            |
+------------------+------+-------------+-----------+-------------+---------------------------------+
| mysql-bin.000002 | 6840 | Query       |         2 |        6922 | BEGIN                           |
| mysql-bin.000002 | 6922 | Table_map   |         2 |        6983 | table_id: 213 (edusoho_e.t1)    |
| mysql-bin.000002 | 6983 | Delete_rows |         2 |        7049 | table_id: 213 flags: STMT_END_F |
| mysql-bin.000002 | 7049 | Table_map   |         2 |        7110 | table_id: 213 (edusoho_e.t1)    |
| mysql-bin.000002 | 7110 | Write_rows  |         2 |        7188 | table_id: 213 flags: STMT_END_F |
| mysql-bin.000002 | 7188 | Xid         |         2 |        7219 | COMMIT /* xid=825 */            |
+------------------+------+-------------+-----------+-------------+---------------------------------+

该事务包含两个 event:Delete_rows(Pos 6983)+ Write_rows(Pos 7110),共同组成一个 event group。

从库跳过该事务:

STOP REPLICA SQL_THREAD; SET GLOBAL sql_replica_skip_counter = 1; START REPLICA SQL_THREAD; SHOW REPLICA STATUS\G -- 预期:Exec_Master_Log_Pos = 7219,复制恢复

⚠️ 注意: sql_replica_skip_counter = 1 跳过了整个 event group,包括 Pos 7110 处的 Write_rows(INSERT 操作)。因此从库不会插入该条数据,主从数据处于不一致状态,需及时补齐。


五、slave_exec_mode(replica_exec_mode)

5.1 参数说明

slave_exec_mode(MySQL 8.0.26+ 新名称:replica_exec_mode)是全局动态参数,可在线修改,立即对所有复制通道生效,无需重启 SQL 线程

参数值 说明
STRICT 默认值(非 NDB 引擎)。严格模式,遇到错误立即停止复制
IDEMPOTENT 幂等模式。忽略 1032(记录未找到)和 1062(主键/唯一键冲突)错误,并按覆写逻辑处理

GTID 模式支持: replica_exec_mode=IDEMPOTENT 在 GTID 模式下同样有效,是三个参数中适用性最广的一个。

5.2 幂等模式的内部处理逻辑

IDEMPOTENT 模式并非简单跳过,而是根据场景智能转换 SQL:

1. INSERT 场景(1062 主键/唯一键冲突)

情形 内部处理
自动提交(autocommit)+ 主键冲突 DELETE 旧行 + INSERT 新行
自动提交(autocommit)+ 唯一键冲突 UPDATE 已有行
显式事务(BEGIN…COMMIT)内的 INSERT 冲突 DELETE 旧行 + INSERT 新行

前提条件: 表必须有主键,否则 IDEMPOTENT 对 INSERT 冲突的覆写处理无效,可能出现重复数据。

2. UPDATE 场景(1032 记录未找到)

从库不存在要更新的记录时,直接跳过该 UPDATE,不执行

3. DELETE 场景(1032 记录未找到)

从库不存在要删除的记录时,直接跳过该 DELETE,不执行

局限性说明:

  • 对 DDL 操作无幂等效果
  • 字段长度不一致(如主库 CHAR(20) vs 从库 CHAR(10))导致的错误无法通过幂等模式解决
  • 表无主键时,INSERT 的幂等覆写逻辑失效

5.3 使用方式

查看当前值:

-- MySQL 8.0.26+ SHOW VARIABLES LIKE 'replica_exec_mode'; -- MySQL ≤ 8.0.25 SHOW VARIABLES LIKE 'slave_exec_mode';

在线切换为幂等模式(立即生效,无需重启 SQL 线程):

-- MySQL 8.0.26+ SET GLOBAL replica_exec_mode = 'IDEMPOTENT'; -- MySQL ≤ 8.0.25 SET GLOBAL slave_exec_mode = 'IDEMPOTENT';

恢复严格模式(排除风险后执行):

SET GLOBAL replica_exec_mode = 'STRICT';

在 my.cnf 中持久化配置:

[mysqld] # MySQL 8.0.26+ replica_exec_mode = IDEMPOTENT

5.4 实战示例一:多条 SQL 事务中仅跳过报错 event,保留后续 event

场景说明: 从库缺少 id=17 的行,主库提交了包含两条 SQL 的事务,需要跳过报错的 DELETE,但保留后续的 INSERT。

从库手动删除数据:

DELETE FROM edusoho_e.t1 WHERE id = 17;

主库提交事务:

BEGIN; DELETE FROM edusoho_e.t1 WHERE id = 17; -- 从库无此行,报 1032 INSERT INTO edusoho_e.t1 (xname, address, hobby) VALUES ('我是谁', '不知道', '吃了睡睡了吃'); COMMIT;

主库用 mysqlbinlog 查看 binlog 详情:

# 查看 binlog 详细内容(解码 base64,从 position 120 开始) mysqlbinlog -v --base64-output=decode-rows --start-position=120 mysql-bin.000004

关键片段:

# at 263
Delete_rows: table id 216 flags: STMT_END_F
### DELETE FROM `edusoho_e`.`t1`
### WHERE @1=17  <-- 需要跳过的报错行

# at 402
Write_rows: table id 216 flags: STMT_END_F
### INSERT INTO `edusoho_e`.`t1`
### SET @1=21    <-- 需要保留的 INSERT

从库启用幂等模式(仅跳过 1032,保留 INSERT):

-- 切换为幂等模式,对所有复制通道立即生效 SET GLOBAL replica_exec_mode = 'IDEMPOTENT'; -- MySQL 8.0.26+ -- SET GLOBAL slave_exec_mode = 'IDEMPOTENT'; -- MySQL ≤ 8.0.25 -- 启动 SQL 线程 START REPLICA SQL_THREAD; -- 验证复制恢复 SHOW REPLICA STATUS\G -- 预期:Slave_SQL_Running: Yes,Exec_Master_Log_Pos = 514

效果对比:

  • sql_replica_skip_counter = 1:跳过整个事务(DELETE + INSERT 均跳过,id=21 的数据丢失)
  • replica_exec_mode = IDEMPOTENT:仅跳过报错的 DELETE,保留 INSERT(id=21 的数据成功同步)

5.5 实战示例二:跳过 1032 错误(DELETE 记录不存在)

场景说明: 从库已提前删除部分数据,主库同步 DELETE 操作时从库找不到对应行,报 1032 错误。

从库构造数据差异:

-- 从库提前删除 id > 5 的数据 DELETE FROM testdb1.student WHERE id > 5; COMMIT;

主库执行删除(会同步到从库):

-- 主库删除 id > 7 的数据 DELETE FROM testdb1.student WHERE id > 7;

从库查看复制状态(报 1032):

SHOW REPLICA STATUS\G

关键输出:

Slave_IO_Running:  Yes
Slave_SQL_Running: No
Last_Errno:        1032
Last_Error: Could not execute Delete_rows event on table testdb1.student;
            Can't find record in 'student', Error_code: 1032

方案 A:用 sql_replica_skip_counter 跳过(不推荐多次操作)

STOP REPLICA; SET GLOBAL sql_replica_skip_counter = 1; START REPLICA; SHOW REPLICA STATUS\G -- 验证复制恢复

方案 B:用 replica_exec_mode=IDEMPOTENT 直接忽略(推荐)

-- 切换为幂等模式,无需停止 SQL 线程 SET GLOBAL replica_exec_mode = 'IDEMPOTENT'; -- 主库再次执行删除(模拟后续操作) -- 从库同步时 1032 错误自动忽略,复制不中断 -- 验证状态 SHOW REPLICA STATUS\G -- 预期:Slave_SQL_Running: Yes,Seconds_Behind_Master: 0

六、恢复主从数据一致性

重要提示: 以上任何跳过错误的方式,都只是临时恢复主从复制通道的运行状态,并不代表数据已经一致。必须及时进行数据补齐,否则后续同步仍可能出现相同错误。

6.1 小批量数据差异(推荐)

使用 Percona Toolkit 进行在线检查和修复:

# 第一步:检查主从数据差异(不锁表,低影响) pt-table-checksum \ --host=[主库IP] \ --port=3306 \ --user=root \ --password=[密码] \ --databases=testdb1 \ --tables=student # 第二步:同步差异数据到从库 pt-table-sync \ --execute \ --sync-to-master \ h=[从库IP],P=3306,u=root,p=[密码] \ --databases=testdb1

⚠️ pt-table-sync 会对表加短暂行锁,建议在业务低峰期执行,或先用 --print 确认影响范围。

6.2 大量数据差异(重做从库)

当数据差异较大时,建议直接重建从库,成本更低:

# 在主库上导出全量备份(推荐使用 xtrabackup) xtrabackup --backup --user=root --password=[密码] --target-dir=/backup/full/ # 在从库上恢复,重新配置主从复制

七、三种方案选型建议

场景 推荐方案 原因
明确知道某类错误可忽略(如 DDL 冲突) replica_skip_errors 持久配置,无需人工介入,适合同步初始化场景
偶发的点位复制中断,需要临时跳过 1~2 个事务 sql_replica_skip_counter 精准控制跳过数量,适合单次排障
复杂事务中只想跳过报错 event,保留其他 event replica_exec_mode=IDEMPOTENT 智能处理,保留最多数据,适合高数据价值场景
GTID 模式下的复制中断 GTID 空事务注入 + replica_exec_mode sql_replica_skip_counter 在 GTID 下不可用

八、总结 & 注意事项

核心要点:

  1. replica_skip_errors静态参数,需写配置文件并重启,适合持久豁免某类已知错误码。

  2. sql_replica_skip_counter 跳过的是整个 event group(事务组),不是单条 SQL,对多 SQL 事务会跳过所有 event,注意数据丢失风险。

  3. replica_exec_mode=IDEMPOTENT动态参数,可在线切换,立即生效,是三者中最"温和"的方案,能在跳过错误的同时尽量保留数据一致性,但表必须有主键

  4. 任何跳过错误的操作都不等于数据一致,必须事后使用 pt-table-checksum/pt-table-sync 或重做从库进行数据修复。

  5. MySQL 8.0.26+ 参数名已更新:旧名称(slave_*)仍可用但已废弃,新项目建议统一使用 replica_* 前缀的新名称。

  6. replica_exec_mode=IDEMPOTENT 不适用于 DDL 操作,也无法处理字段长度不一致等 schema 差异类错误。

避坑清单:

  • ❌ 不要在主从数据高度一致性要求的场景下使用 replica_skip_errors=all
  • ❌ 不要在 GTID 模式下使用 sql_replica_skip_counter(无效,应注入空事务)
  • ❌ 不要跳过错误后忘记修复数据差异,否则相同错误还会再次出现
  • ✅ 跳过错误后,务必用 SHOW REPLICA STATUS 确认复制线程状态
  • ✅ 建议在配置文件中同时保留新旧参数名注释,便于版本迁移时参考
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论