replace into语法浅析
- 2019 年 11 月 6 日
- 筆記
replace into语法浅析
今天有个开发问我replace into和insert into哪个效率高,就我了解,replace是会首先判断这个值在不在,如果在的话,则进行更新操作,否则进行插入操作。拍脑门一想,当然是insert into的效率高,不过replace into确实可以避免一些问题出现,比如duplicate key的问题。
于是我对这个语法做了个试验,大概分为下面几种情况,介绍如下:
当表中存在主键但是不存在唯一建的时候:
CREATE TABLE `yyz` ( `id` bigint(20) NOT NULL, `name` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; root@test >insert into yyz values(1,'abc'); Query OK, 1 row affected (0.00 sec) root@test >replace into yyz values(2,'bbb'); Query OK, 1 row affected (0.00 sec) root@test >select * from yyz; +----+------+ | id | name | +----+------+ | 1 | abc | | 2 | bbb | +----+------+ 2 rows in set (0.00 sec) root@test >replace into yyz values(1,'ccc'); Query OK, 2 rows affected (0.00 sec)
我们看看binlog当中记录的内容,首先是:
### INSERT INTO test.yyz ### SET ### @1=2 /* LONGINT meta=0 nullable=0 is_null=0 */ ### @2='bbb' /* VARSTRING(60) meta=60 nullable=1 is_null=0 */ # at 623 #190617 9:55:42 server id 136403306 end_log_pos 650 Xid = 6090885569
可以看到,针对不存在的id=2,replace做的是insert的操作,而对于已经存在的id=1的记录,replace一个(1,'ccc')的记录是,二进制日志中的结果是:
### UPDATE test.yyz ### WHERE ### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */ ### @2='abc' /* VARSTRING(60) meta=60 nullable=1 is_null=0 */ ### SET ### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */ ### @2='ccc' /* VARSTRING(60) meta=60 nullable=1 is_null=0 */
可以看到,直接是一个update的操作。
当表中主键和唯一键同时存在时
CREATE TABLE `yyz` ( `id` int(11) NOT NULL DEFAULT '0', `b` int(11) DEFAULT NULL, `c` int(11) DEFAULT NULL PRIMARY KEY (`a`), UNIQUE KEY `uk_bc` (`b`,`c`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
root@test >replace into yyz values(1,2,3); Query OK, 1 row affected (0.00 sec) root@test >replace into yyz values(2,2,4); Query OK, 1 row affected (0.00 sec) root@test >select * from yyz; +----+------+------+ | id | b | c | +----+------+------+ | 1 | 2 | 3 | | 2 | 2 | 4 | +----+------+------+ 2 rows in set (0.00 sec) root@test >replace into yyz values(1,2,5); Query OK, 2 rows affected (0.00 sec) root@test >select * from yyz; +----+------+------+ | id | b | c | +----+------+------+ | 2 | 2 | 4 | | 1 | 2 | 5 | +----+------+------+ 2 rows in set (0.00 sec)
上面的情况,发生了主键冲突,我们看看binlog,结果如下:
BINLOG ' Io5hVROWYHC+KwAAAEICAAAAAMoMAAAAAAEABHRlc3QAAnl5AAMDAwMABg== Io5hVRmWYHC+KgAAAGwCAAAAAMoMAAAAAAAAA//4AQAAAAIAAAADAAAA ### DELETE FROM test.yyz ### WHERE ### @1=1 /* INT meta=0 nullable=0 is_null=0 */ ### @2=2 /* INT meta=0 nullable=1 is_null=0 */ ### @3=3 /* INT meta=0 nullable=1 is_null=0 */ Io5hVReWYHC+KgAAAJYCAAAAAMoMAAAAAAEAA//4AQAAAAIAAAAFAAAA '/*!*/; ### INSERT INTO test.yyz ### SET ### @1=1 /* INT meta=0 nullable=0 is_null=0 */ ### @2=2 /* INT meta=0 nullable=1 is_null=0 */ ### @3=5 /* INT meta=0 nullable=1 is_null=0 */ # at 662 #190617 19:38:58 server id 3195035798 end_log_pos 689 Xid = 22962508 COMMIT/*!*/
也就是说,主键冲突时,数据库对表做先删除然后插入的操作,也即先删除id=1的记录,然后插入新的id=1 的记录(1,2,5).
如果在这种情况下,唯一键发生冲突呢?再来看:
root@test >select * from yyz; +----+------+------+ | id | b | c | +----+------+------+ | 1 | 2 | 4 | | 2 | 2 | 5 | | 3 | 3 | 5 | | 4 | 3 | 6 | +----+------+------+ 4 rows in set (0.00 sec) root@test >replace into yyz values(5,3,6); Query OK, 2 rows affected (0.00 sec) root@test >select * from yyz; +----+------+------+ | id | b | c | +----+------+------+ | 1 | 2 | 4 | | 2 | 2 | 5 | | 3 | 3 | 5 | | 5 | 3 | 6 | +----+------+------+ 4 rows in set (0.00 sec)
再来看看binlog中的内容:
BINLOG ' lJFhVROWYHC+KwAAANoAAAAAAMoMAAAAAAEABHRlc3QAAnl5AAMDAwMABg== lJFhVRiWYHC+OAAAABIBAAAAAMoMAAAAAAEAA///+AQAAAADAAAABgAAAPgFAAAAAwAAAAYAAAA= '/*!*/; ### UPDATE test.yyz ### WHERE ### @1=4 /* INT meta=0 nullable=0 is_null=0 */ ### @2=3 /* INT meta=0 nullable=1 is_null=0 */ ### @3=6 /* INT meta=0 nullable=1 is_null=0 */ ### SET ### @1=5 /* INT meta=0 nullable=0 is_null=0 */ ### @2=3 /* INT meta=0 nullable=1 is_null=0 */ ### @3=6 /* INT meta=0 nullable=1 is_null=0 */ # at 274 #190617 19:53:40 server id 3195035798 end_log_pos 301 Xid = 22962872 COMMIT/*!*/
也就是说, 主键不冲突,唯一键冲突时,数据库对表 唯一键为(3,6)的行做update操作,将主键修改为要插入的值,id=4 改为id=5。
主键和唯一键同时冲突,如果需要插入的值的主键 和唯一和表中已经存在记录同时冲突。
4 rows in set (0.00 sec)root@test >replace into yyz values(1,3,6);
可以看到,replace中id=1和第一条记录冲突,而b=3,c=6和最后一天记录冲突,我们可以看看binlog中的结果:
BINLOG ' B5JhVROWYHC+KwAAAJwBAAAAAMoMAAAAAAEABHRlc3QAAnl5AAMDAwMABg== B5JhVRmWYHC+KgAAAMYBAAAAAMoMAAAAAAAAA//4AQAAAAIAAAAEAAAA ### DELETE FROM test.yyz ### WHERE ### @1=1 /* INT 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 */ B5JhVRiWYHC+OAAAAP4BAAAAAMoMAAAAAAEAA///+AUAAAADAAAABgAAAPgBAAAAAwAAAAYAAAA= '/*!*/; ### UPDATE test.yyz ### WHERE ### @1=5 /* INT meta=0 nullable=0 is_null=0 */ ### @2=3 /* INT meta=0 nullable=1 is_null=0 */ ### @3=6 /* INT meta=0 nullable=1 is_null=0 */ ### SET ### @1=1 /* INT meta=0 nullable=0 is_null=0 */ ### @2=3 /* INT meta=0 nullable=1 is_null=0 */ ### @3=6 /* INT meta=0 nullable=1 is_null=0 */ # at 510 #190617 19:55:35 server id 3195035798 end_log_pos 537 Xid = 22962904 COMMIT/*!*/
我们可以看到,相当于删除了id=1的记录,然后将最后一条记录的id进行了一个update的操作,改为了1。
最终结论总结如下:
对表进行replace into操作的时候, 如果表只包含主键: 当不存在冲突时,replace into 相当于insert操作。 当存在冲突时,replace into 相当于update操作。 如果表包含主键和唯一性索引: 当不存在冲突时,replace into 相当于insert操作。 当存在主键冲突的时候是先delete再insert,如果主键是自增的,则自增主键会做 +1 操作。 当存在唯一性索引冲突的时候是直接update。,如果主键是自增的,则自增主键会做 +1 操作。