MySQL Variables--slave_exec_mode参数

基础信息

MySQL提供参数slave_exec_mode来控制主从复制中遇到的数据冲突和错误,有严格模式(STRICT)和冥等模式(IDEMPOTENT)两种选项,默认为严格模式。
在严格模式下,MySQL会严格检查每次需要APPLY的BINLOG和当前节点数据是否匹配,并在下列场景中抛出错误并停止复制:

  • 插入操作,要插入的记录在从节点上存在主键或唯一键冲突。
  • 更新操作,要更新的记录在从节点上不存在和不匹配,严格模式需在所有列上进行全部匹配。
  • 删除操作,要删除的记录在从节点上不存在和不匹配,严格模式需在所有列上进行全部匹配。

参数slave_exec_mode的官网解释:
Controls how a replication thread resolves conflicts and errors during replication. IDEMPOTENT mode
causes suppression of duplicate-key and no-key-found errors; STRICT means no such suppression
takes place.

如果发生主从节点数据不一致触发复制异常,可采用:

  • 通过设置参数sql_slave_skip_counter来跳过复制错误,注意参数sql_slave_skip_counter与GTID复制不兼容。
  • 通过设置参数slave-skip-errors来跳过复制错误,注意参数slave-skip-errors不能动态设置需重启MySQL服务。
  • 通过设置参数slave_exec_mode=IDEMPOTENT来跳过复制。

本文主要针对slave_exec_mode=IDEMPOTENT来进行验证以下场景:

  • 场景1:主节点插入数据,从节点上已存在该数据,从节点数据和主节点数据完全相同。
  • 场景2:主节点插入数据,从节点上已存在该数据,从节点数据和主节点数据仅主键相同,其他列不同。
  • 场景3:主节点插入数据,从节点上已存在该数据,从节点数据和主节点数据仅主键相同和唯一键相同,其他列不同。
  • 场景4:主节点插入数据,从节点上已存在该数据,从节点数据和主节点数据主键和唯一键相互冲突,其他列相同。
  • 场景5:主节点删除数据,但从节点上的数据与主节点上数据不匹配,主键和唯一键相同,其他列不同。
  • 场景6:主节点删除数据,但从节点上的数据与主节点上数据不匹配,主键相同,唯一键和其他列不同。
  • 场景7:主节点删除数据,但从节点上的数据与主节点上数据不匹配,从节点在主键维度没有匹配的记录。
  • 场景8:主节点更新数据,但从节点上的数据与主节点上数据不匹配,从节点在主键维度存在匹配的记录,但其他数据列不匹配。
  • 场景9:主节点更新数据,但从节点上的数据与主节点上数据不匹配,从节点在主键维度没有匹配的记录。
  • 场景10:主节点在一个事务中更新多条记录,但从节点上的数据与主节点上数据不匹配,从节点在主键维度存在部分没有匹配的记录。

测试结论:
冥等模式能保证主从节点数据在"主键维度"的冥等,在应用主节点传递过来的BINLOG时:

  • 插入操作,将操作改写为删除(按照主键删除)+插入(整条记录插入),如果从节点按照主键不存在对应记录则跳过删除操作,能保证相同主键的记录在主从节点上保持一致。
  • 删除操作,将操作改写为删除(按照主键删除),如果从节点按照主键不存在对应记录则跳过删除操作,保证相同主键的记录在主从节点上保持一致(都不存在)。
  • 更新操作,将操作改写为更新(按照主键更新),如果从节点按照主键不存在对应记录则跳过更新操作,不能保证相同主键的记录在主从节点上保持一致,存在"主节点有"但"从节点没有"的情况。

PS1: 如果冥等模式能将更新操作改写为删除(按照主键删除)+插入(整条记录插入),则能保证主从节点上保持一致。
PS2: 冥等模式仅考虑"主键维度"的冥等,不会考虑数据在"唯一键维度"的冥等,因此会导致从节点数据在"唯一键维度"出现冲突。
PS3: 在冥等模式下,从节点会根据APPLY BINLOG的实际情况生成新的BINLOG,主从节点BINLOG可能存在巨大差异。
PS4: 如果能确认主从节点在那些表存在数据差异,可以先设置slave_exec_mode=IDEMPOTENT来保证复制正常运行,再通过pt-osc在主节点上重建表,从而保证主从节点真正的数据一致。
PS5: 冥等模式需要在ROW复制模式下运行,且针对每行记录进行操作,在GTID复制中,如果从节点无对应记录操作,则会生成空事务保证GTID复制正常运行。

当遇到主从数据不一致时,使用XtraBackup备份重做复制节点是最稳妥的办法,跳过复制错误可作为临时解决方案,但不建议作为最终解决方案。

测试环境

MySQL 版本: 5.7.26-29-log Percona Server (GPL)
将MySQL从节点设置为:slave_exec_mode=IDEMPOTENT
在MySQL主节点准备测试数据:

## 删除测试表1
DROP TABLE IF EXISTS `repl_test1`;

## 创建新测试表1
CREATE TABLE `repl_test1` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `c1` int(11) DEFAULT NULL,
  `c2` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uqi_c1` (`c2`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

## 插入测试数据
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 1,1,1;

## 删除测试表2
DROP TABLE IF EXISTS `repl_test2`;

## 创建新测试表2
CREATE TABLE `repl_test2` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `c2` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

## 插入测试数据
INSERT INTO `repl_test2`(`id`,`c2`)SELECT 1,1;

## 刷新BINLOG日志,方便解析
FLUSH LOGS;

查看BINLOG操作日志:

 mysqlbinlog -vvv mysql-bin.000xxx |egrep '###|COMMIT|BEGIN|SESSION.GTID_NEXT'

场景1

场景:主节点插入数据,从节点上已存在该数据,从节点数据和主节点数据完全相同。

先在从节点执行(制造主从数据差异):

INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,2;
INSERT INTO `repl_test2`(`id`,`c2`)SELECT 2,2;

再在主节点执行:

INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,2;
INSERT INTO `repl_test2`(`id`,`c2`)SELECT 2,2;

复制无异常报错,主从节点数据相同。

主节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:76'/*!*/;
BEGIN
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:77'/*!*/;
BEGIN
### INSERT INTO `test`.`repl_test2`
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

从节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:76'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:77'/*!*/;
BEGIN
COMMIT

主节点插入从节点已存在数据,从节点数据和主节点数据完全相同:

  • 如果表中存在唯一索引,从节点会将INSERT操作转换为DELETE操作+INSERT操作,并保证最终主从数据相同。
  • 如果表中不存在唯一索引,从节点上不会执行任何操作,并生成一个空事务,并保证最终主从数据相同。

场景2

场景:主节点插入数据,从节点上已存在该数据,从节点数据和主节点数据仅主键相同,其他列不同。

先在从节点执行(制造主从数据差异):

INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,22,22;
INSERT INTO `repl_test2`(`id`,`c2`)SELECT 2,22;

再在主节点执行:

INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,2;
INSERT INTO `repl_test2`(`id`,`c2`)SELECT 2,2;

复制无异常报错,主从节点数据相同。

主节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:84'/*!*/;
BEGIN
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:85'/*!*/;
BEGIN
### INSERT INTO `test`.`repl_test2`
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

从节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:84'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=22 /* INT meta=0 nullable=1 is_null=0 */
###   @3=22 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;


/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:85'/*!*/;
BEGIN
### UPDATE `test`.`repl_test2`
### WHERE
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=22 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

主节点插入从节点已存在数据,从节点数据和主节点数据仅主键相同,其他列不同:

  • 如果表中存在唯一索引,从节点会将INSERT操作转换为DELETE操作+INSERT操作,并保证最终主从数据相同。
  • 如果表中不存在唯一索引,从节点将INSERT操作转换为UPDATE操作,并保证最终主从数据相同。

场景3

场景:主节点插入数据,从节点上已存在该数据,从节点数据和主节点数据仅主键相同和唯一键相同,其他列不同

先在从节点执行(制造主从数据差异):

INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,22;

再在主节点执行:

INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,2;

复制无异常报错,主从节点数据相同。

主节点产生BINLOG:

SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:92'/*!*/;
BEGIN
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

从节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:92'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=22 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

主节点插入从节点已存在数据,从节点数据和主节点数据仅主键相同和唯一键相同,其他列不同:

  • 如果表中存在唯一索引,从节点会将INSERT操作转换为DELETE操作+INSERT操作,并保证最终主从数据相同。

场景4

场景:主节点插入数据,从节点上已存在该数据,从节点数据和主节点数据主键和唯一键相互冲突,其他列相同。

在主节点插入数据,模拟更多情况:

## 插入测试数据
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,2;

先在从节点执行(制造主从数据差异):

UPDATE `repl_test1` SET `c1`=3 WHERE `id`=2;
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 3,4,3;
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 4,2,4;

主从当前状态数据为:

## 主节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    2 |
+----+------+------+
2 rows in set (0.00 sec)

## 从节点数据
mysql>  select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    3 |    2 |
|  3 |    4 |    3 |
|  4 |    2 |    4 |
+----+------+------+
4 rows in set (0.00 sec)

再在主节点执行:

INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 3,3,3;
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 4,4,4;

复制无异常报错,主从节点数据在所操作的记录上保持相同。

## 主节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    2 |
|  3 |    3 |    3 |
|  4 |    4 |    4 |
+----+------+------+
4 rows in set (0.00 sec)

## 从节点数据
mysql>  select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    3 |    2 |
|  3 |    3 |    3 |
|  4 |    4 |    4 |
+----+------+------+
4 rows in set (0.00 sec)

注意:从节点上在唯一键上出现数据冲突。

主节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:139'/*!*/;
BEGIN
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=3 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=3 /* INT meta=0 nullable=1 is_null=0 */
###   @3=3 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:140'/*!*/;
BEGIN
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=4 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=4 /* INT meta=0 nullable=1 is_null=0 */
###   @3=4 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

从节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:139'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=3 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=4 /* INT meta=0 nullable=1 is_null=0 */
###   @3=3 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=3 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=3 /* INT meta=0 nullable=1 is_null=0 */
###   @3=3 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:140'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=4 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=4 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=4 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=4 /* INT meta=0 nullable=1 is_null=0 */
###   @3=4 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

主节点插入数据,从节点上已存在该数据,从节点数据和主节点数据主键和唯一键相互冲突,其他列相同:

  • 如果表中存在唯一索引,从节点会将INSERT操作转换为DELETE操作+INSERT操作,并保证主从节点在所操作的列上相同。

场景5

场景:主节点删除数据,但从节点上的数据与主节点上数据不匹配,主键和唯一键相同,其他列不同

先在从节点执行(制造主从数据差异):

UPDATE `repl_test1` SET `c2`=11 WHERE `id`=1;
UPDATE `repl_test1` SET `c2`=11 WHERE `id`=1;

再在主节点执行:

DELETE FROM `repl_test1` WHERE `id`=1;
DELETE FROM `repl_test2` WHERE `id`=1;

复制无异常报错,主从节点数据相同,数据都被删除。

主节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:99'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=1 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:100'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test2`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

从节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:99'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=11 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:100'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test2`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=11 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

主节点删除数据,但从节点上的数据与主节点上数据不匹配,主键和唯一键相同,其他列不同:

  • 无论表上是否有唯一索引键,从节点会将主键来进行删除,保证和主节点有相同主键的记录被删除。

场景6

场景:主节点删除数据,但从节点上的数据与主节点上数据不匹配,主键相同,唯一键和其他列不同。

在主节点插入数据,模拟更多情况:

## 插入测试数据
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,2;
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 3,3,4;

先在从节点执行(制造主从数据差异):

UPDATE `repl_test1` SET `c1`=11,`c2`=11 WHERE `id`=1;
UPDATE `repl_test1` SET `c1`=4,`c2`=22 WHERE `id`=2;
UPDATE `repl_test1` SET `c1`=2,`c2`=33 WHERE `id`=3;
UPDATE `repl_test1` SET `c1`=3,`c2`=22 WHERE `id`=2;

再在主节点执行:

DELETE FROM `repl_test1` WHERE `id`=1;
DELETE FROM `repl_test1` WHERE `id`=2;

复制无异常报错,主从节点数据相同,数据都被删除。

主节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:115'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=1 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:116'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

从节点产生BINLOG:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:115'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=11 /* INT meta=0 nullable=1 is_null=0 */
###   @3=11 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:116'/*!*/;
BEGIN
### DELETE FROM `test`.`repl_test1`
### WHERE
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=3 /* INT meta=0 nullable=1 is_null=0 */
###   @3=22 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

主节点删除数据,但从节点上的数据与主节点上数据不匹配,主键相同,唯一键和其他列不同:

  • 从节点会将主键来进行删除,保证和主节点有相同主键的记录被删除。

场景7

场景:主节点删除数据,但从节点上的数据与主节点上数据不匹配,从节点在主键维度没有匹配的记录。

在主节点插入数据,模拟更多情况:

## 插入测试数据
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,2;
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 3,3,3;

先在从节点执行(制造主从数据差异):

DELETE FROM `repl_test1` WHERE `id` = 1;
DELETE FROM `repl_test2` WHERE `id` = 1;
DELETE FROM `repl_test1` WHERE `id` = 3;
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 4,3,4;

此时主从数据为:

## 主节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    2 |
|  3 |    3 |    3 |
+----+------+------+
3 rows in set (0.00 sec)

## 从节点数据
mysql>  select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  2 |    2 |    2 |
|  4 |    3 |    4 |
+----+------+------+
2 rows in set (0.00 sec)

再在主节点执行:

DELETE FROM `repl_test1` WHERE `id`=1;
DELETE FROM `repl_test2` WHERE `id`=1;
DELETE FROM `repl_test1` WHERE `id`=3;

复制无异常报错,主从节点数据为:

## 主节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  2 |    2 |    2 |
+----+------+------+
1 row in set (0.00 sec)

## 从节点数据
mysql>  select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  2 |    2 |    2 |
|  4 |    3 |    4 |
+----+------+------+
2 rows in set (0.00 sec)

主节点删除数据,但从节点上的数据与主节点上数据不匹配,从节点在主键维度没有匹配的记录:

  • 从节点会将主键来进行删除,保证和主节点有相同主键的记录被删除。

场景8

场景:主节点更新数据,但从节点上的数据与主节点上数据不匹配,从节点在主键维度存在匹配的记录,但其他数据列不匹配。

在主节点插入数据,模拟更多情况:

## 插入测试数据
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,2;
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 3,3,3;

先在从节点执行(制造主从数据差异):

UPDATE `repl_test1` SET `c2`=111 WHERE `id` = 1;
UPDATE `repl_test2` SET `c2`=111 WHERE `id` = 1;
UPDATE `repl_test1` SET `c1`=222,`c2`=222 WHERE `id` = 2;

此时主从数据为:

## 主节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    2 |
|  3 |    3 |    3 |
+----+------+------+
3 rows in set (0.00 sec)

## 从节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |  111 |
|  2 |  222 |  222 |
|  3 |    3 |    3 |
+----+------+------+
3 rows in set (0.00 sec)

再在主节点执行:

UPDATE `repl_test1` SET c2=11 WHERE `id`=1;
UPDATE `repl_test2` SET c2=11 WHERE `id`=1;
UPDATE `repl_test1` SET c2=22 WHERE `id`=2;

复制无异常报错,主从节点数据为:

## 主节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |   11 |
|  2 |    2 |   22 |
|  3 |    3 |    3 |
+----+------+------+
3 rows in set (0.00 sec)

## 从节点数据
mysql>  select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |   11 |
|  2 |    2 |   22 |
|  3 |    3 |    3 |
+----+------+------+
3 rows in set (0.00 sec)

主节点上BINLOG为:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:190'/*!*/;
BEGIN
### UPDATE `test`.`repl_test1`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=1 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=11 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:191'/*!*/;
BEGIN
### UPDATE `test`.`repl_test2`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=11 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:192'/*!*/;
BEGIN
### UPDATE `test`.`repl_test1`
### WHERE
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=22 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

从节点BINLOG为:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:190'/*!*/;
BEGIN
### UPDATE `test`.`repl_test1`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=111 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=11 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:191'/*!*/;
BEGIN
### UPDATE `test`.`repl_test2`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=111 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=11 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:192'/*!*/;
BEGIN
### UPDATE `test`.`repl_test1`
### WHERE
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=222 /* INT meta=0 nullable=1 is_null=0 */
###   @3=222 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=22 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

主节点更新数据,但从节点上的数据与主节点上数据不匹配,从节点在主键维度没有匹配的记录:

  • 在应用主节点传递来的日志时,如果按照主键在从节点上找到对应记录,则直接按照主键来进行更新,保证主从节点在操作记录上数据一致。

场景9

场景:主节点更新数据,但从节点上的数据与主节点上数据不匹配,从节点在主键维度没有匹配的记录。

在主节点插入数据,模拟更多情况:

## 插入测试数据
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,2;
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 3,3,3;

先在从节点执行(制造主从数据差异):

DELETE FROM `repl_test1` WHERE `id` = 1;
DELETE FROM `repl_test2` WHERE `id` = 1;
DELETE FROM `repl_test1` WHERE `id` = 3;
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 4,3,4;

此时主从数据为:

## 主节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    2 |
|  3 |    3 |    3 |
+----+------+------+
3 rows in set (0.00 sec)

## 从节点数据
mysql>  select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  2 |    2 |    2 |
|  4 |    3 |    4 |
+----+------+------+
2 rows in set (0.00 sec)

再在主节点执行:

UPDATE `repl_test1` SET c2=111 WHERE `id`=1;
UPDATE `repl_test2` SET c2=111 WHERE `id`=1;
UPDATE `repl_test1` SET c2=333 WHERE `id`=3;

复制无异常报错,主从节点数据为:

## 主节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |  111 |
|  2 |    2 |    2 |
|  3 |    3 |  333 |
+----+------+------+
3 rows in set (0.00 sec)

## 从节点数据
mysql>  select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  2 |    2 |    2 |
|  4 |    3 |    4 |
+----+------+------+
2 rows in set (0.00 sec)

主节点上BINLOG为:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:168'/*!*/;
BEGIN
### UPDATE `test`.`repl_test1`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=1 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=111 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:169'/*!*/;
BEGIN
### UPDATE `test`.`repl_test2`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=111 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:170'/*!*/;
BEGIN
### UPDATE `test`.`repl_test1`
### WHERE
###   @1=3 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=3 /* INT meta=0 nullable=1 is_null=0 */
###   @3=3 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=3 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=3 /* INT meta=0 nullable=1 is_null=0 */
###   @3=333 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

从节点BINLOG为:

SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:168'/*!*/;
BEGIN

COMMIT

SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:169'/*!*/;
BEGIN
COMMIT

SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:170'/*!*/;
BEGIN
COMMIT

主节点更新数据,但从节点上的数据与主节点上数据不匹配,从节点在主键维度没有匹配的记录:

  • 在应用主节点传递来的日志时,如果按照主键在从节点上无法找到对应记录,则跳过更新,并生成空事务的BINLOG日志。

场景10

场景:主节点在一个事务中更新多条记录,但从节点上的数据与主节点上数据不匹配,从节点在主键维度存在部分没有匹配的记录。

先在从节点执行(制造主从数据差异):

DELETE FROM `repl_test1` WHERE `id` = 1;

此时主从数据为:

## 主节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |    1 |
+----+------+------+
1 row in set (0.00 sec)

## 从节点数据
mysql> select * from `repl_test1`;
Empty set (0.00 sec)

再在主节点执行:

BEGIN;
UPDATE `repl_test1` SET c2=111 WHERE `id`=1;
INSERT INTO `repl_test1`(`id`,`c1`,`c2`)SELECT 2,2,2;
COMMIT;

复制无异常报错,主从节点数据为:

## 主节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  1 |    1 |  111 |
|  2 |    2 |    2 |
+----+------+------+
2 rows in set (0.00 sec)

## 从节点数据
mysql> select * from `repl_test1`;
+----+------+------+
| id | c1   | c2   |
+----+------+------+
|  2 |    2 |    2 |
+----+------+------+
1 row in set (0.00 sec)

主节点上BINLOG为:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:199'/*!*/;
BEGIN
### UPDATE `test`.`repl_test1`
### WHERE
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=1 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=1 /* INT meta=0 nullable=1 is_null=0 */
###   @3=111 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;

从节点上BINLOG为:

/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'c1387dd9-be50-11ed-844c-5cb9019a75d6:199'/*!*/;
BEGIN
### INSERT INTO `test`.`repl_test1`
### SET
###   @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */
###   @2=2 /* INT meta=0 nullable=1 is_null=0 */
###   @3=2 /* INT meta=0 nullable=1 is_null=0 */
COMMIT/*!*/;
posted @ 2023-03-14 15:39  TeyGao  阅读(30)  评论(0编辑  收藏  举报