适用版本: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_errorsreplica_skip_errors❌ 否(静态参数) sql_slave_skip_countersql_replica_skip_counter✅ 是(动态参数) slave_exec_modereplica_exec_mode✅ 是(动态参数) SHOW SLAVE STATUSSHOW REPLICA STATUS— START SLAVESTART REPLICA— STOP SLAVESTOP 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会对表加短暂行锁,建议在业务低峰期执行,或先用
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 下不可用 |
八、总结 & 注意事项
核心要点:
-
replica_skip_errors是静态参数,需写配置文件并重启,适合持久豁免某类已知错误码。 -
sql_replica_skip_counter跳过的是整个 event group(事务组),不是单条 SQL,对多 SQL 事务会跳过所有 event,注意数据丢失风险。 -
replica_exec_mode=IDEMPOTENT是动态参数,可在线切换,立即生效,是三者中最"温和"的方案,能在跳过错误的同时尽量保留数据一致性,但表必须有主键。 -
任何跳过错误的操作都不等于数据一致,必须事后使用 pt-table-checksum/pt-table-sync 或重做从库进行数据修复。
-
MySQL 8.0.26+ 参数名已更新:旧名称(
slave_*)仍可用但已废弃,新项目建议统一使用replica_*前缀的新名称。 -
replica_exec_mode=IDEMPOTENT不适用于 DDL 操作,也无法处理字段长度不一致等 schema 差异类错误。
避坑清单:
- ❌ 不要在主从数据高度一致性要求的场景下使用
replica_skip_errors=all - ❌ 不要在 GTID 模式下使用
sql_replica_skip_counter(无效,应注入空事务) - ❌ 不要跳过错误后忘记修复数据差异,否则相同错误还会再次出现
- ✅ 跳过错误后,务必用
SHOW REPLICA STATUS确认复制线程状态 - ✅ 建议在配置文件中同时保留新旧参数名注释,便于版本迁移时参考




