Mysql中INSERT ... ON DUPLICATE KEY UPDATE语句用法

系统环境:

Mysql:5.7.32

基本功能

如果在INSERT语句末尾指定了ON DUPLICATE KEY UPDATE语句,并且新插入的行会导致UNIQUE KEY(唯一索引)或PRIMARY KEY(主键值)重复,那么会对原有记录进行UPDATE操作;如果不会导致唯一索引或主键值重复,则执行INSERT操作;

解决了什么问题

我们在实际的项目中,经常有以下需求:

向表中插入一条记录时,如果UNIQUE KEY(唯一索引)或PRIMARY KEY已存在,则更新记录,否则插入一条记录

逻辑上我们会这么写(伪代码):

result = mysql_query("select...")
row = mysql_fetch(result)
if row:
    mysql_query("update...")
else:
    mysql_query("insert...")

这么做有两个问题:

1、效率低下,每次需要执行两条语句
2、高并发时会出问题,不能保证两条语句的原子性

Mysql通过ON DUPLICATE KEY UPDATE语句为我们解决了以上问题,既保证了原子性也保证了效率。

PS:在使用INSET...ON DUPLICATE KEY UPDATE这样的语句来插入记录时,如果遇到主键或者唯一二级索引列的值重复,会对B+树中已存在的相同键值的记录加X锁。

UPDATE语句的异同

1、假设列aUNIQUE索引且a=1的记录已经存在,下面两条语句效果相同:

INSERT INTO t1 (a, b, c) VALUES (1, 2, 3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE t1 SET c=c+1 WHERE a=1;

区别:

如果aInnodb表中的自增键,则INSERT语句会导致auto-increment的值增加,UPDATE语句则不会

2、如果b也是UNIQUE索引,下面两条语句效果相同:

INSERT INTO t1 (a, b, c) VALUES (1, 2, 3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE t1 SET c=c+1 WHERE a=1 OR b=2 LIMIT 1;

区别:

如果一条记录中包含多个唯一索引(含主键),那么即使匹配上多条记录也只会更新一条记录。所以,一般情况在,不要在有多个唯一索引的表上使用ON DUPLICATE KEY UPDATE语句;

存在什么问题

1、对于Innodb引擎的表,使用INSET...ON DUPLICATE KEY UPDATE语句会导致auto-increment的值增加;
2、如果一条记录中包含多个唯一索引(含主键),那么即使匹配上多条记录也只会更新一条记录。所以,一般情况在,不要在有多个唯一索引的表上使用ON DUPLICATE KEY UPDATE语句;
3、如果是插入操作,影响的行数为1;如果是更新操作,影响的行数为2;如果更新的数据和已有的数据一样(就相当于没变,所有值保持不变),影响的行数为0;
4、这是Mysql独有的语法,其它数据库可能不支持。

VALUES函数

ON DUPLICATE KEY UPDATE语句的赋值表达式中,可以使用VALUES(列名)来引用INSERT语句中该列的值,也就是将要插入的列的值。在多行插入时VALUES函数特别有用。
示例:

INSERT INTO t1 (a, b, c) VALUES (1,2,3),(4,5,6)
  ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);

等同于

INSERT INTO t1 (a, b, c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=3;
INSERT INTO t1 (a, b, c) VALUES (4,5,6)
  ON DUPLICATE KEY UPDATE c=9;

Tips:VALUES()函数只在ON DUPLICATE KEY UPDATEINSERT语句中有意义,其它时候会返回NULL。

实验

1、实验前准备,创建表,并插入一条记录

CREATE TABLE t1 (
      a int(11) NOT NULL AUTO_INCREMENT,
      b int(11),
      c int(11),
      PRIMARY KEY(a)
    )ENGINE=Innodb default charset=utf8;

INSERT INTO t1(a, b, c) VALUES(1, 1, 1);

查看表中记录

mysql> SELECT * FROM t1;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    1 |    1 |
+---+------+------+
1 row in set (0.01 sec)

查看当前Auto_increment值:

mysql> show table status like 't1'\G;
*************************** 1. row ***************************
           Name: t1
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 1
 Avg_row_length: 16384
    Data_length: 16384
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: 2
    Create_time: 2021-03-09 03:32:51
    Update_time: 2021-03-09 03:33:22
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

2、单UNIQUE键测试
插入ON DUPLICATE KEY UPDATE语句并查看:

mysql> INSERT INTO t1 (a, b, c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;
Query OK, 2 rows affected (0.01 sec)

从插入结果看,影响了2行记录。

再次查看表中记录及Auto_increment值:

mysql> SELECT * FROM t1;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    1 |    2 |
+---+------+------+
1 row in set (0.00 sec)
mysql> show table status like 't1'\G;
*************************** 1. row ***************************
           Name: t1
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 1
 Avg_row_length: 16384
    Data_length: 16384
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: 2  // 与官网文档不符,按照官网文档,这里应该增加,实际却并没有增加,不知道是哪里不对
    Create_time: 2021-03-09 03:32:51
    Update_time: 2021-03-09 03:34:40
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

3、多UNIQUE键测试
修改t1表结构,将b列改为UNIQUE索引

mysql> ALTER TABLE t1 ADD UNIQUE KEY uk_b(b);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

增加一列

mysql> INSERT INTO t1(a, b, c) VALUES(2,  2,  3);
Query OK, 1 row affected (0.01 sec)

查看

mysql> desc t1;
+-------+---------+------+-----+---------+----------------+
| Field | Type    | Null | Key | Default | Extra          |
+-------+---------+------+-----+---------+----------------+
| a     | int(11) | NO   | PRI | NULL    | auto_increment |
| b     | int(11) | YES  | UNI | NULL    |                |
| c     | int(11) | YES  |     | NULL    |                |
+-------+---------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM t1;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    1 |    2 |
| 2 |    2 |    3 |
+---+------+------+
2 rows in set (0.00 sec)

mysql> show table status like 't1'\G;
*************************** 1. row ***************************
           Name: t1
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 2
 Avg_row_length: 8192
    Data_length: 16384
Max_data_length: 0
   Index_length: 16384
      Data_free: 0
 Auto_increment: 3
    Create_time: 2021-03-09 03:40:17
    Update_time: 2021-03-09 03:41:41
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

插入ON DUPLICATE KEY UPDATE语句,使ab列同时冲突:

mysql> INSERT INTO t1(a, b, c) VALUES(1, 2, 4) ON DUPLICATE KEY UPDATE c=c+1;
Query OK, 2 rows affected (0.00 sec)

查看

mysql> SELECT * FROM t1;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    1 |    3 |
| 2 |    2 |    3 |
+---+------+------+
2 rows in set (0.00 sec)

从结果看,只有第一行执行更新,验证了多行记录满足条件只会更新一行的说法。

4、VALUSE()函数实验
先把b列索引删除,改为普通列

mysql> ALTER TABLE t1 DROP KEY uk_b;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

插入记录

mysql> INSERT INTO t1 (a, b, c) VALUES (1,2,3), (2,5,6), (3, 4, 5) ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 1  Warnings: 0

查看结果

mysql> SELECT * FROM t1;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    1 |    3 |
| 2 |    2 |    7 |
| 3 |    4 |    5 |
+---+------+------+
3 rows in set (0.00 sec)

可见,所有重复主键的行已经更新,没有重复主键的按原数据执行插入操作。

还可以将原数据与新插入的数据一起操作,示例如下:

mysql> INSERT INTO t1 (a, b, c) VALUES (1,2,5) ON DUPLICATE KEY UPDATE c=c+VALUES(c);
Query OK, 2 rows affected (0.01 sec)

查看结果

mysql> SELECT * FROM t1;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    1 |    8 |
| 2 |    2 |    7 |
| 3 |    4 |    5 |
+---+------+------+
3 rows in set (0.00 sec)

总结

  • INSERT...ON DUPLICATE KEY UPDATE语句,会在UNIQUE KEY(唯一索引)或PRIMARY KEY(主键值)重复时,对原有记录进行UPDATE操作;不重复,则执行INSERT操作;
  • VALUSE()函数在INSERT...ON DUPLICATE KEY UPDATE语句中非常有用,它可以获取到将要插入的列的值,对多行操作非常有用
  • INSERT...ON DUPLICATE KEY UPDATE有一些副作用,使用时要注意
    • AUTO_INCREMENT自增值问题(这个在实验中未验证)
    • 多行匹配时,只能更新一行(当表中存在多个唯一索引时慎用)

参考

Mysql官方文档
https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容