replace into与insert into ...on duplicate key update的区别以及陷阱

 

对于replaceinto以及insertinto...onduplicatekeyupd...



对于replace into 以及insert into ...on  duplicate key update 语句,上一篇以及分析了其在数据库中的执行过程,接下来,我们将讨论这两个语句在实际应用中,需要注意的区别以及"陷阱“。该篇文章对实际应用有非常重要的指导作用,请仔细看完

首先我们来分析replace into 语句。

CREATE TABLE "reptest" (

"a" int(11) NOT NULL AUTO_INCREMENT,

"b" int(11) DEFAULT NULL,

"c" varchar(200) DEFAULT NULL,

PRIMARY KEY ("a")

) ENGINE=InnoDBAUTO_INCREMENT=16 DEFAULT CHARSET=utf8 ;

mysql> select * from reptest;

+---+------+------+

| a | b    | c    |

+---+------+------+

| 1 |    1 | NULL |

| 2 |    2 | NULL |

| 3 |    4 | NULL |

| 4 |    5 | NULL |

| 5 |    6 | NULL |

| 6 |    7 | NULL |

| 7 |    8 | NULL |

| 8 | NULL | 8    |

| 9 | NULL | 9    |

+---+------+------+

9 rows in set (0.02 sec)

mysql> replace into   reptest(a,b) values(9,9);

Query OK, 2 rows affected (0.04 sec)

mysql> select * from reptest;

+---+------+------+

| a | b    | c    |

+---+------+------+

| 1 |    1 | NULL |

| 2 |    2 | NULL |

| 3 |    4 | NULL |

| 4 |    5 | NULL |

| 5 |    6 | NULL |

| 6 |    7 | NULL |

| 7 |    8 | NULL |

| 8 | NULL | 8    |

| 9 |    9 | NULL |

+---+------+------+

9 rows in set (0.01 sec)

mysql> show create table  reptest;

CREATE TABLE "reptest" (

"a" int(11) NOT NULL AUTO_INCREMENT,

"b" int(11) DEFAULT NULL,

"c" varchar(200) DEFAULT NULL,

PRIMARY KEY ("a")

) ENGINE=InnoDBAUTO_INCREMENT=16 DEFAULT CHARSET=utf8

我们看到,对于replace into语句,如果没有指定某字段的值,则以默认值代替。这个逻辑也是跟insert语句完全一样, 但当出现键值冲突时,如果没有指定某个字段的值,则这个字段就会修改成默认值----如果不注意这个细节,将导致原本不想修改的字段却被”意外“改变。

接下来我们来看看Insert into .. on duplicate key update 语句。依然是对a=9的这行操作。

mysql> select * from reptest;

+---+------+------+

| a | b    | c    |

+---+------+------+

| 1 |    1 | NULL |

| 2 |    2 | NULL |

| 3 |    4 | NULL |

| 4 |    5 | NULL |

| 5 |    6 | NULL |

| 6 |    7 | NULL |

| 7 |    8 | NULL |

| 8 | NULL | 8    |

| 9 |    9 | NULL |

+---+------+------+

9 rows in set (0.01 sec)

mysql> insert into  reptest (a,c) values(9,'9') on duplicate key update c=values(c);

Query OK, 2 rows affected (0.04 sec)

mysql> select * from reptest;

+---+------+------+

| a | b    | c    |

+---+------+------+

| 1 |    1 | NULL |

| 2 |    2 | NULL |

| 3 |    4 | NULL |

| 4 |    5 | NULL |

| 5 |    6 | NULL |

| 6 |    7 | NULL |

| 7 |    8 | NULL |

| 8 | NULL | 8    |

| 9 |    9 | 9    |

+---+------+------+

9 rows in set (0.02 sec)

我们看到,采用insert into ...on duplicate key update 语句,对于不指定修改的列,则不修改。

通过上面的分析,我们知道了这两个语句之间的区别。 接下来,我们会演示这两个语句的另外的一个”陷阱“。

我们将这个reptest在b列上增加一个唯一性索引,然后继续进行相关测试。

mysql> alter table reptest add  unique key (b);

Query OK, 0 rows affected (0.14 sec)

Records: 0  Duplicates: 0  Warnings: 0

mysql> select * from reptest;

+---+------+------+

| a | b    | c    |

+---+------+------+

| 1 |    1 | NULL |

| 2 |    2 | NULL |

| 3 |    4 | NULL |

| 4 |    5 | NULL |

| 5 |    6 | NULL |

| 6 |    7 | NULL |

| 7 |    8 | NULL |

| 8 | NULL | 8    |

| 9 |    9 | 9    |

+---+------+------+

9 rows in set (0.01 sec)

本来的数据如上,然后我们执行下面的replace into语句。

mysql> replace into reptest(b) values(9);

Query OK, 2 rows affected (0.03 sec)

结果发现原来的a=9的行,完全消失。新行是a=16,b=9,c=null.

mysql> select * from reptest;

+----+------+------+

| a  | b    | c    |

+----+------+------+

|  1 |    1 | NULL |

|  2 |    2 | NULL |

|  3 |    4 | NULL |

|  4 |    5 | NULL |

|  5 |    6 | NULL |

|  6 |    7 | NULL |

|  7 |    8 | NULL |

|  8 | NULL | 8    |

| 16 |    9 | NULL |

+----+------+------+

9 rows in set (0.03 sec)

这个完全符合replace into 语句发现冲突时,把原来的行删除,然后插入新行的逻辑。

咱们再来做一次replace into 操作,语句如下:

replace into reptest(a,b) values(16,7);   请问一下,再操作完之后,不去查询表的记录,哪些朋友会知道最后的结果?最后表中是否有a=16,b=7的行?我们还是执行之后,查看表的数据然后再做确认。

mysql> select * from reptest;

+----+------+------+

| a  | b    | c    |

+----+------+------+

|  1 |    1 | NULL |

|  2 |    2 | NULL |

|  3 |    4 | NULL |

|  4 |    5 | NULL |

|  5 |    6 | NULL |

|  6 |    7 | NULL |

|  7 |    8 | NULL |

|  8 | NULL | 8    |

| 16 |    9 | NULL |

+----+------+------+

9 rows in set (0.03 sec)

mysql> replace into reptest(a,b) values(16,7);

Query OK, 3 rows affected (0.05 sec)

mysql> select * from  reptest where a=16 and b=7;

+----+------+------+

| a  | b    | c    |

+----+------+------+

| 16 |    7 | NULL |

+----+------+------+

1 row in set (0.02 sec)

mysql> select * from reptest;

+----+------+------+

| a  | b    | c    |

+----+------+------+

|  1 |    1 | NULL |

|  2 |    2 | NULL |

|  3 |    4 | NULL |

|  4 |    5 | NULL |

|  5 |    6 | NULL |

|  7 |    8 | NULL |

|  8 | NULL | 8    |

| 16 |    7 | NULL |

+----+------+------+

8 rows in set (0.01 sec)

确实存在a=16,b=7的数据,但有没有发现,执行完replace into语句之后,表中的记录反而少了一行? 原来是9行,执行完replace into语句之后,只有了8行。从replace into 语句返回了3 rows affected  的结果来看,猜测是删除了2行,插入了1行,导致表的总行数少了1行。仔细观察结果发现,少了原来a=6,b=7的那一行数据。

我们来仔细分析一下replace into reptest(a,b) values(16,7) 的执行过程:

第一步: 首先在主键(表)上进行插入,发现已经存在a=16,b=9的记录,即键值冲突,然后调用ha_update_row(table->record[1],table->record[0]))将其更新为a=16,b=7的记录。原来a=16,b=9的记录消失。

第二步:在主键上插入(实际上最后因为key冲突而了更新)完毕之后,就去b列索引上插入,在b列索引上插入时,同样发现键值冲突,但在唯一性索引上发现冲突后,不能跟在表上以同样的方式处理----因为仅仅替换唯一性索引上的记录是不够的,还得把表上的记录也更新了(实际上是删除)。所以得把表上的b=7记录的删除----即对应的就是a=6的记录删除。 因此a=6的记录完全消失。

因此,对于有主键,且有唯一性索引的情况下,执行replace into语句,最终在数据库中产生的影响,需要遵循先操作主键(表),然后再操作唯一性索引的顺序。假如有多个唯一性索引,则一个replace语句,可能导致多条记录被删除。这个陷阱,对于研发人员来说,是务必知晓的细节,否则可能酿成大错。

讲完了replace into 语句,我们再来分析一下insert into .on duplicate key update 语句的影响。

mysql>  select * from reptest;

+----+------+------+

| a  | b    | c    |

+----+------+------+

|  1 |    1 | NULL |

|  2 |    2 | NULL |

|  3 |    4 | NULL |

|  4 |    5 | NULL |

|  5 |    6 | NULL |

|  7 |    8 | NULL |

|  8 | NULL | 8    |

| 16 |    7 | NULL |

+----+------+------+

8 rows in set (0.01 sec)

mysql> insert  into reptest(a,b) values(16,7) on duplicate  key  update b=values(b);

Query OK, 0 rows affected (0.01 sec)

mysql> select * from reptest;

+----+------+------+

| a  | b    | c    |

+----+------+------+

|  1 |    1 | NULL |

|  2 |    2 | NULL |

|  3 |    4 | NULL |

|  4 |    5 | NULL |

|  5 |    6 | NULL |

|  7 |    8 | NULL |

|  8 | NULL | 8    |

| 16 |    7 | NULL |

+----+------+------+

8 rows in set (0.03 sec)

当我们执行insert  into reptest(a,b) values(16,7) on duplicate  key  update b=values(b); 语句时,返回的结果是 0 rows affected ,很好,完全符合逻辑,因为数据库已经存在a=16,b=7的行,所以没有行发生变更。

我们再来执行跟上面的replace into的类似insert into .. on duplicate key update  。。。的sql,冲突既发生在主键上,又发生在唯一性索引上面的的。

mysql> insert  into reptest(a,b) values(16,8) on duplicate  key  update b=values(b);

ERROR 1062 (23000): Duplicate entry '8' for key 'b'

出现的结果是操作失败。没有像replace insert 那样,来删除一行来成全自己的事务。

作者依然不甘心, 还想继续测试。。。。。。

再来试试下面这个sql .

mysql> insert  into reptest(a,b,c) values(16,8,'8') on duplicate  key  update c=values(c);

Query OK, 2 rows affected (0.05 sec)

执行成功了,perfect , 表中最后的记录会是什么样的呢?是否会像replace into那样的操作,最后少一行?如果少,少的是哪一行?

咱们还是来查查表中的数量吧。

mysql> select * from reptest;

+----+------+------+

| a  | b    | c    |

+----+------+------+

|  1 |    1 | NULL |

|  2 |    2 | NULL |

|  3 |    4 | NULL |

|  4 |    5 | NULL |

|  5 |    6 | NULL |

|  7 |    8 | NULL |

|  8 | NULL | 8    |

| 16 |    7 | 8    |

+----+------+------+

8 rows in set (0.01 sec)

然后还是8行,数据没有少。到此,你是否觉得insert into ... on  duplicate  key 比replace into 完美呢? 既然这样,replace into 还有毛用? 好像是多余的。。。。。

再此,作者可以批评亲对实验现象观察得不够仔细莫?(。。。哈哈哈,纯属玩笑。IT屌丝太闷,开开玩笑也蛮好,请见谅。。。 )

我们再来看看我们执行的语句。insert  into reptest(a,b,c) values(16,8,'8') on duplicate  key  update c=values(c);  我们执行语句的目的是想插入(16,8,8)的行,sql也执行成功了,但数据库有这样的记录不?

mysql> select * from  reptest where a=16 and b=8 and c=8;

Empty set (0.06 sec)

压根没有这条记录------又是一个好大的”坑“啊。

原因:对于在mysql中执行上面的允许冲突的语句,首先在主键上进行insert,如果发现冲突,则执行语句中update后面的更新操作。 然后就变更就完成。 在此列中,没有再去管b是什么值。

作者还想进行一个测试,因为a列是自增字段,我们把a列拿掉在做insert,看结果为如何?(初始数据还是上面的查询结果).

mysql> insert into  reptest (b,c) values(8,'8') on duplicate key update c=values(c);

Query OK, 2 rows affected (0.02 sec)

最后表中的结果会如何呢?是有(17,8,8)的记录,还是(7,8,8)的记录? 让我们拭目以待吧。

mysql> select * from reptest;

+----+------+------+

| a  | b    | c    |

+----+------+------+

|  1 |    1 | NULL |

|  2 |    2 | NULL |

|  3 |    4 | NULL |

|  4 |    5 | NULL |

|  5 |    6 | NULL |

|  7 |    8 | 8    |

|  8 | NULL | 8    |

| 16 |    7 | 8    |

+----+------+------+

8 rows in set (0.02 sec)

您是否猜(答)对?老实说,作者在没有仔细研究之前,也猜不对,不知道replace into与insert into .. on duplicate key update 语句的区别有多少。

结果出炉,原来是(7,8,8),同时,表的自增字段的下一个值也增加了1.

到此,我们是否明白了insert into ... on duplicate key update语句,应对冲突,到底采取何种方式?

在作者看来:1. 对于insert into .. on duplicate key update这样的语句,如果没有冲突,跟replace into ,insert into的执行过程完全一样,结果也一样。(这点是废话,接触过mysql的人都知道)。2.当执行insert的过程中,发现失败且是key冲突,则进入冲突处理,对于insert into .. on duplicate key update这样的语句,则直接在原来的行上进行修改。即冲突发生在哪行,就在哪个行上修改。对于上面的语句insert into  reptest (b,c) values(8,'8') on duplicate key update c=values(c); ,本来是要执行 insert into (a,b,c) values(17,8,'8'),执行过程是:首先在主键上进行插入(17,8,‘8') ,然后再去唯一性索引上插入(8,17),因为唯一性索引上已经有(8,7),(即b=8,a=7的索引行),所以冲突。因此,就在这样行进行更新,之前在主键(表)上插入的行( 17,8,'8')进行回滚。

所以最终结果是(7,8,’8').

关注微信工众号,实时获取pig分享的纯干货原创文章。


    关注 数据库随笔


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册