如果您指定了 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;
使用 VALUES()
引用新行和列是 deprecated 的,并且可能在未来版本的 MySQL 中被删除。相反,使用行和列别名,如下一节所述。
可以使用行别名,例如 new
,并且可以选择性地使用一个或多个要插入的列,紧跟在 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
子句一起使用,如前所述。使用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
语句的结果取决于SELECT
和INSERT ... 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)