MySQL Replace Into & Replication

问题

数据库主从切换后,应用使用Replace into Statement 更新插入抛出异常

Duplicate entry 'xxxx' for key 'PRIMARY'

原因解释

  1. Replace into 当唯一键存在(且同时存在自增主键ID,但SQL Statement 中不包含自增主键ID)时,主库上执行会将这条记录更新,同时主键ID会变(同时影响AUTO_INCREMENT的值),但是表现在binlog中,是一条UPDATE Statement
  2. MySQL 5.6 innodb-auto-increment-handling

Modifying AUTO_INCREMENT column values in the middle of a sequence of INSERT statements
In all lock modes (0, 1, and 2), modifying an AUTO_INCREMENT column value in the middle of a sequence of INSERT statements could lead to “Duplicate entry” errors. For example, if you perform an UPDATE operation that changes an AUTO_INCREMENT column value to a value larger than the current maximum auto-increment value, subsequent INSERT operations that do not specify an unused auto-increment value could encounter “Duplicate entry” errors. This behavior is demonstrated in the following example.

以上两点,简单归纳:Replace into 会导致slave表的 AUTO_INCREMENT值 <= Max(自增主键ID) ,如果此时slave提升为主,插入Statement(包括Insert、Replace into)如果不包含自增字段,就会导致主键冲突Duplicate entry,直到当AUTO_INCREMENT值 > Max(自增主键ID) 后,才会恢复正常。

换一句话说,Replace在主库上的行为类似(先Delete,在Insert),Binlog中记录的却是Update。

场景复现

CREATE TABLE `test_replace_into` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `uniq_col` int(10) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_col` (`uniq_col`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
>select * from test_replace_into;
+----+----------+
| id | uniq_col |
+----+----------+
|  1 |        1 |
|  2 |        2 |
|  3 |        3 |
+----+----------+

上面原始数据已经准备完成,现在模拟执行一条replace into

>replace into test_replace_into (uniq_col) value(3);
Query OK, 2 rows affected (0.00 sec)

>select * from test_replace_into;
+----+----------+
| id | uniq_col |
+----+----------+
|  1 |        1 |
|  2 |        2 |
|  4 |        3 |
+----+----------+

解析BinLog,看到该条Replace into Statement 实际上在BinLog中是Update Statement,而且能看到自增主键ID会被更新。

BINLOG '
udP0WxOOcp0JOQAAAAp5ohMAAJAAAAAAAAEABHRlc3QAEXRlc3RfcmVwbGFjZV9pbnRvAAIDAwAA
udP0WxiOcp0JMAAAADp5ohMAAJAAAAAAAAEAAv///AMAAAADAAAA/AQAAAADAAAA
'/*!*/;
### UPDATE `test`.`test_replace_into`
### WHERE
###   @1=3 /* INT meta=0 nullable=0 is_null=0 */
###   @2=3 /* INT meta=0 nullable=0 is_null=0 */
### SET
###   @1=4 /* INT meta=0 nullable=0 is_null=0 */
###   @2=3 /* INT meta=0 nullable=0 is_null=0 */

此时主从表结构,slave AUTO_INCREMENT 等于 Max(自增主键ID)

# master
       Table: test_replace_into
CREATE TABLE `test_replace_into` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `uniq_col` int(10) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_col` (`uniq_col`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8

# slave
       Table: test_replace_into
Create Table: CREATE TABLE `test_replace_into` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `uniq_col` int(10) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_col` (`uniq_col`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

现在直接将slave提升为master 插入数据,就会复现问题

>replace into test_replace_into (uniq_col) value(5);
ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'

临时解决办法

  1. 直接调整AUTO_INCREMENT值,使其大于 Max(自增主键ID)
  2. 业务持续报错,AUTO_INCREMENT 会不断增加,也能自我恢复

“Lost” auto-increment values and sequence gaps
In all lock modes (0, 1, and 2), if a transaction that generated auto-increment values rolls back, those auto-increment values are “lost”. Once a value is generated for an auto-increment column, it cannot be rolled back, whether or not the “INSERT-like” statement is completed, and whether or not the containing transaction is rolled back. Such lost values are not reused. Thus, there may be gaps in the values stored in an AUTO_INCREMENT column of a table.

以上测试均基于 MySQL 5.6.25-73.1, BinLog Format 是Raw模式下;实测在5.7版本中也会存在上述问题

根本解决

不使用Replace into,可以考虑 Insert into ... On Duplicate Key Update 或 业务逻辑上解决。

你可能感兴趣的:(MySQL Replace Into & Replication)