Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

MySQL 8.3 Reference Manual  /  ...  /  INSERT ... ON DUPLICATE KEY UPDATE Statement

15.2.7.2 INSERT ... ON DUPLICATE KEY UPDATE 语句

如果您指定了 ON DUPLICATE KEY UPDATE 子句,并且要插入的行将导致唯一索引或 PRIMARY KEY 中的重复值,则将更新旧行。例如,如果列 a 声明为 UNIQUE 并包含值 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;

效果并不完全相同:对于 InnoDB 表,其中 a 是自动递增列,INSERT 语句增加自动递增值,但 UPDATE 不会。

如果列 b 也是唯一的,那么 INSERT 等同于以下 UPDATE 语句:

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

如果 a=1 OR b=2 匹配多行,只更新 一行。一般来说,您应该避免在具有多个唯一索引的表上使用 ON DUPLICATE KEY UPDATE 子句。

使用 ON DUPLICATE KEY UPDATE 时,每行的受影响行数为 1,如果行被插入为新行;为 2,如果现有行被更新;为 0,如果现有行被设置为其当前值。如果您在连接到 mysqld 时指定了 CLIENT_FOUND_ROWS 标志给 C API 函数 mysql_real_connect(),那么受影响行数为 1(不是 0),如果现有行被设置为其当前值。

如果表包含 AUTO_INCREMENT 列,并且 INSERT ... ON DUPLICATE KEY UPDATE 插入或更新了一行,LAST_INSERT_ID() 函数将返回 AUTO_INCREMENT 值。

ON DUPLICATE KEY UPDATE 子句可以包含多个列赋值,逗号分隔。

ON DUPLICATE KEY UPDATE 子句中的赋值表达式中,您可以使用 VALUES(col_name) 函数来引用 INSERT 部分的列值。

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;
Note

使用 VALUES() 引用新行和列是 deprecated 的,并且可能在未来版本的 MySQL 中被删除。相反,使用行和列别名,如下一节所述。

可以使用行别名,例如 new,并且可以选择性地使用一个或多个要插入的列,紧跟在 VALUESSET 子句之后,并在 AS 关键字之前。使用行别名 new,可以将之前使用 VALUES() 访问新列值的语句写成以下形式:

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

如果您还使用列别名 mnp,那么可以在赋值子句中省略行别名,并将语句写成以下形式:

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

使用这种方式的列别名时,必须在VALUES子句后面使用行别名,即使您不直接在赋值子句中使用它。

使用VALUES()UPDATE子句中的INSERT ... SELECT ... ON DUPLICATE KEY UPDATE语句将抛出警告:

INSERT INTO t1
  SELECT c, c+d FROM t2
  ON DUPLICATE KEY UPDATE b = VALUES(b);

您可以使用子查询来消除这些警告,如下所示:

INSERT INTO t1
  SELECT * FROM (SELECT c, c+d AS e FROM t2) AS dt
  ON DUPLICATE KEY UPDATE b = e;

您还可以使用行别名和列别名与SET子句一起使用,如前所述。使用SET代替VALUES在两个INSERT ... ON DUPLICATE KEY UPDATE语句中可以如下所示:

INSERT INTO t1 SET a=1,b=2,c=3 AS new
  ON DUPLICATE KEY UPDATE c = new.a+new.b;

INSERT INTO t1 SET a=1,b=2,c=3 AS new(m,n,p)
  ON DUPLICATE KEY UPDATE c = m+n;

行别名不能与表名相同。如果不使用列别名,或者列别名与列名相同,则必须使用行别名来区分它们在ON DUPLICATE KEY UPDATE子句中。列别名必须是唯一的,相对于应用于的行别名(即,不同行的列别名不能相同)。

对于INSERT ... SELECT语句,这些规则适用于可接受的SELECT查询表达式形式,可以在ON DUPLICATE KEY UPDATE子句中引用:

  • 来自单个表的查询的列引用,可能是派生表。

  • 来自多个表的连接查询的列引用。

  • 来自DISTINCT查询的列引用。

  • 来自其他表的列引用,只要SELECT不使用GROUP BY。一个副作用是您必须限定非唯一列名的引用。

不支持来自UNION的列引用。要解决这个限制,可以将UNION重写为派生表,以便将其行视为单个表结果集。例如,这个语句将产生错误:

INSERT INTO t1 (a, b)
  SELECT c, d FROM t2
  UNION
  SELECT e, f FROM t3
ON DUPLICATE KEY UPDATE b = b + c;

相反,使用等效语句将UNION重写为派生表:

INSERT INTO t1 (a, b)
SELECT * FROM
  (SELECT c, d FROM t2
   UNION
   SELECT e, f FROM t3) AS dt
ON DUPLICATE KEY UPDATE b = b + c;

将查询重写为派生表的技术也使得来自GROUP BY查询的列引用成为可能。

因为INSERT ... SELECT语句的结果取决于SELECTINSERT ... SELECT ON DUPLICATE KEY UPDATE语句的行顺序,而这个顺序不能总是保证,因此在记录INSERT ... SELECT ON DUPLICATE KEY UPDATE语句时,源和副本可能会分歧。因此,INSERT ... SELECT ON DUPLICATE KEY UPDATE语句被标记为基于语句的复制中的不安全语句。当使用基于语句的模式时,这些语句将在错误日志中产生警告,并在使用MIXED模式时以基于行的格式写入二进制日志。对具有多个唯一或主键的表的INSERT ... ON DUPLICATE KEY UPDATE语句也被标记为不安全的。(Bug #11765650, Bug #58637)

另请参阅第 19.2.1.1 节,“基于语句和基于行的复制的优缺点”