15.2.7.2 插入...关于重复键更新语句
如果您指定了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
部分的列值。在其他字面上,VALUES(
在col_name
)ON DUPLICATE KEY UPDATE
子句中引用的是将被插入的col_name
值的值,如果没有重复键冲突发生。这一函数在多行插入中特别有用。VALUES()
函数仅在ON DUPLICATE KEY UPDATE
子句或INSERT
语句中有效,否则返回<code class="literal">NULL</code>。示例:
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
子句中插入一行或多行数据。使用行别名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
用于前面显示的两条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
语句,这些规则适用于在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
语句也被标记为不安全。 (Bug #11765650, Bug #58637)
请参见第19.2.1.1节,“Statement-Based and Row-Based Replication 的优缺”。