文档主页
MySQL 9.0 参考手册
相关文档 下载本手册
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
手册页 (TGZ) - 258.2Kb
手册页 (Zip) - 365.3Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 9.0 参考手册  /  ...  /  INSERT ... ON DUPLICATE KEY UPDATE 语句

15.2.7.2 INSERT ... ON DUPLICATE KEY UPDATE 语句

如果指定了 ON DUPLICATE KEY UPDATE 子句,并且要插入的行会导致在 UNIQUE 索引或 PRIMARY KEY 中出现重复值,则会对旧行进行 UPDATE 操作。例如,如果将列 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 标志指定给 mysql_real_connect() C API 函数,则如果将现有行设置为其当前值,则受影响行值为 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 ... ON DUPLICATE KEY UPDATE 语句。换句话说,VALUES(col_name)ON DUPLICATE KEY UPDATE 子句中引用的是 col_name 的值,如果未发生重复键冲突,则会插入该值。此函数在多行插入中特别有用。 VALUES() 函数仅在 ON DUPLICATE KEY UPDATE 子句或 INSERT 语句中有意义,否则返回 NULL。示例

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;
注意

使用 VALUES() 来引用新行和列已过时,并且将在 MySQL 的未来版本中删除。相反,请使用行和列别名,如本节接下来的几段中所述。

可以在 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 子句中使用行和列别名。在上面显示的两个 INSERT ... ON DUPLICATE KEY UPDATE 语句中,使用 SET 代替 VALUES 可以像这里显示的那样完成

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 语句,这些规则适用于您可以在 ON DUPLICATE KEY UPDATE 子句中引用的可接受的 SELECT 查询表达式的形式

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

  • 对来自跨多个表联接的查询的列的引用。

  • 对来自 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 语句的结果取决于来自 SELECT 的行的排序,并且此排序无法始终保证,因此当为源和副本记录 INSERT ... SELECT ON DUPLICATE KEY UPDATE 语句时,它们可能会发生分歧。因此,INSERT ... SELECT ON DUPLICATE KEY UPDATE 语句被标记为不适合基于语句的复制。此类语句在使用基于语句的模式时会在错误日志中生成警告,并且在使用 MIXED 模式时会使用基于行的格式写入二进制日志。对具有多个唯一键或主键的表的 INSERT ... ON DUPLICATE KEY UPDATE 语句也被标记为不安全。(错误 #11765650,错误 #58637)

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