xupeng@diggle7:3600(dba_m) [dba] mysql> REPLACE INTO auto (k, v) VALUES (1, '1-1'); Query OK, 2rows affected (0.01 sec) xupeng@diggle7:3600(dba_m) [dba] mysql>SELECT*FROM auto; +----+---+------+---------+ | id | k | v | extra | +----+---+------+---------+ |2|2|2| extra 2| |3|3|3| extra 3| |4|1|1-1|NULL| +----+---+------+---------+ 3rowsinset (0.00 sec) xupeng@diggle7:3600(dba_m) [dba] mysql>SHOWCREATETABLE auto\G ***************************1.row*************************** Table: auto CreateTable: CREATETABLE `auto` ( `id` int(10) unsigned NOTNULL AUTO_INCREMENT, `k` int(10) unsigned NOTNULL, `v` varchar(100) DEFAULTNULL, `extra` varchar(200) DEFAULTNULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_k` (`k`) ) ENGINE=InnoDB AUTO_INCREMENT=5DEFAULT CHARSET=latin1 1rowinset (0.00 sec)
可以看到 MySQL 说 “2 rows affected”,可是明明是只写一条记录,为什么呢?这是因为 MySQL 在执行 REPLACE INTO auto (k) VALUES (1) 时首先尝试 INSERT INTO auto (k) VALUES (1),但由于已经存在一条 k=1 的记录,发生了 duplicate key error,于是 MySQL 会先删除已有的那条 k=1 即 id=1 的记录,然后重新写入一条新的记录。
可以知道,当前表内数据 id 字段的最大值是 4,AUTO_INCREMENT 应该为 5,但在 slave 上 AUTO_INCREMENT 却并未更新,这会有什么问题呢?把这个 slave 提升为 master 之后,由于 AUTO_INCREMENT 比实际的 next id 还要小,写入新记录时就会发生 duplicate key error,每次冲突之后 AUTO_INCREMENT += 1,直到增长为 max(id) + 1 之后才能恢复正常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
xupeng@diggle8:3600(dba_s) [dba] mysql> REPLACE INTO auto (k, v) VALUES (4, '4'); ERROR 1062 (23000): Duplicate entry '4'for key 'PRIMARY' xupeng@diggle8:3600(dba_s) [dba] mysql> REPLACE INTO auto (k, v) VALUES (5, '5'); Query OK, 1row affected (0.00 sec) xupeng@diggle8:3600(dba_s) [dba] mysql>SELECT*FROM auto; +----+---+------+---------+ | id | k | v | extra | +----+---+------+---------+ |2|2|2| extra 2| |3|3|3| extra 3| |4|1|1-1|NULL| |5|5|5|NULL| +----+---+------+---------+ 4rowsinset (0.00 sec)
没有预料到 MySQL 在数据冲突时实际上是删掉了旧记录,再写入新记录,这是使用 REPLACE INTO 时最大的一个误区,拿之前的例子来说,执行完 REPLACE INTO auto (k,v) VALUES (1,‘1-1’) 之后,由于新写入记录时并未给 extra 字段指定值,原记录 extra 字段的值就「丢失」了,而通常这并非是业务上所预期的,更常见的需求实际上是,当存在 k=1 的记录时,就把 v 字段的值更新为 ‘1-1’,其他未指定的字段则保持原状,而满足这一需求的 MySQL 方言是 INSERT INTO auto (k,v) VALUES (1,‘1-1’) ON DUPLICATE KEY UPDATE v=VALUES(v);
鉴于此,很多使用 REPLACE INTO 的场景,实际上需要的是 INSERT INTO … ON DUPLICATE KEY UPDATE,在正确理解 REPLACE INTO 行为和副作用的前提下,谨慎使用 REPLACE INTO。