如果指定了 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;
但效果并不完全相同:对于 a
是自动递增列的 InnoDB
表,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
时,如果将行作为新行插入,则每行的 affected-rows 值为 1;如果更新了现有行,则为 2;如果将现有行设置为其当前值,则为 0。如果您在连接到 mysqld 时,为 mysql_real_connect()
C API 函数指定了 CLIENT_FOUND_ROWS
标志,则如果将现有行设置为其当前值,则 affected-rows 值为 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
语句)。换句话说,ON DUPLICATE KEY UPDATE
子句中的 VALUES(
指的是如果没有发生重复键冲突,将插入的 col_name
)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
子句之后使用它。
像下面这样在 UPDATE
子句中使用 VALUES()
的 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)