MySQL replace into导致的自增id问题

时间:2022-07-22
本文章向大家介绍MySQL replace into导致的自增id问题,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

//

MySQL replace into导致的自增id问题

//

今天线上遇到一个问题,挺有意思,这里记录一下希望对大家有所帮助。某个表中,只有一条记录,发生高可用切换之后,自增id的值发生了变化,主从的自增id值不一致,导致数据写入报主键冲突的错误。

我们知道,在MySQL中,是支持replace语法的,当你执行replace into的时候,如果该条记录存在,那么replace会删除这条记录,然后重新insert一条新记录。这种操作在主从复制的场景下,可能会带来问题,这里我们简单模拟一下,建表语句如下:

CREATE TABLE `test1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_age` (`age`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8

可以看到,表中的id是主键,age是唯一索引,我们先插入(2,2)和(3,3)两条数据。然后,

我们先来看主库上

mysql >>select * from test1;
+----+------+
| id | age  |
+----+------+
|  2 |    2 |
|  3 |    3 |
+----+------+
2 rows in set (0.00 sec)

mysql >>replace into test1 values (6,3);
Query OK, 2 rows affected (0.00 sec)

mysql >>select * from test1;
+----+------+
| id | age  |
+----+------+
|  2 |    2 |
|  6 |    3 |
+----+------+
2 rows in set (0.00 sec)

mysql >>show create table test1G
*************************** 1. row ***************************
       Table: test1
Create Table: CREATE TABLE `test1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_age` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

主库上进行replace之后,返回值是2 rows affected,其中之所以返回影响2行,是因为replace into的值是(6,3),而age=3这条记录已经存在,所以会先删除id=3,age=3这条记录,然后插入id=6,age=3这条记录,自增值变为7.

再来看从库上:

mysql >>select * from test1;
+----+------+
| id | age  |
+----+------+
|  2 |    2 |
|  3 |    3 |
+----+------+
2 rows in set (0.00 sec)

mysql >>select * from test1;
+----+------+
| id | age  |
+----+------+
|  2 |    2 |
|  6 |    3 |
+----+------+
2 rows in set (0.00 sec)

mysql >>show create table test1G
*************************** 1. row ***************************
       Table: test1
Create Table: CREATE TABLE `test1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_age` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

可以看到,从库上的自增值变成了5,跟主库不同。此时如果主从库发生切换,那么新插入到从库中的id=6的值就会发生主键冲突了,显示插入不进去,这是我们不想看到的。

那么为什么从库上的自增值和主库不一致呢?这个问题还是要从binlog中的内容分析。解析binlog中的内容,看到如下:

BEGIN
/*!*/;
# at 752364060
#200723 23:54:27 server id 325  end_log_pos 752364115 CRC32 0x21c1d689  Rows_query


# replace into test1 values (6,3)

# at 752364115
#200723 23:54:27 server id 325  end_log_pos 752364164 CRC32 0x6e4046ab  Table_map: `test`.`t
est1` mapped to number 126
# at 752364164
#200723 23:54:27 server id 325  end_log_pos 752364218 CRC32 0x59c2da4e  Update_rows: table i
d 126 flags: STMT_END_F


### UPDATE `test`.`test1`
### WHERE
###   @1=3 /* INT meta=0 nullable=0 is_null=0 */
###   @2=3 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=6 /* INT meta=0 nullable=0 is_null=0 */
###   @2=3 /* INT meta=0 nullable=1 is_null=0 */

# at 752364218
#200723 23:54:27 server id 325  end_log_pos 752364249 CRC32 0x5bb56848  Xid = 100798481
COMMIT/*!*/;

可以看到,MySQL将replace into的在binlog中保存的格式是update语句,那么update语句本质上不会对自增值进行修改,所以就导致了主从的表自增id不一致,这样虽然看着没有什么问题,从库的自增id比主库的小,当主从发生切换的时候,这个问题就比较严重了,有些数据写入的时候,就会报错了。

replace into是MySQL的特有语法,建议不要在线上使用,使用delete和insert来代替比较好。