如果指定了 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 的未来版本中删除。相反,请使用行和列别名,如本节接下来的几段中所述。
可以在 VALUES
或 SET
子句后使用行的别名,可选地使用一个或多个要插入的列,并在 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;
此外,如果您使用列别名 m
、n
和 p
,则可以在赋值子句中省略行别名,并将相同的语句写成这样
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)