UPDATE
是一种 DML 语句,用于修改表中的行。
UPDATE
语句可以以 WITH
子句开头,以定义在 UPDATE
中可访问的公用表表达式。请参阅 第 15.2.20 节,“WITH (公用表表达式)”。
单表语法
UPDATE [LOW_PRIORITY] [IGNORE] table_reference
SET assignment_list
[WHERE where_condition]
[ORDER BY ...]
[LIMIT row_count]
value:
{expr | DEFAULT}
assignment:
col_name = value
assignment_list:
assignment [, assignment] ...
多表语法
UPDATE [LOW_PRIORITY] [IGNORE] table_references
SET assignment_list
[WHERE where_condition]
对于单表语法,UPDATE
语句使用新值更新命名表中现有行的列。SET
子句指示要修改哪些列以及应赋予它们的值。每个值都可以作为表达式给出,也可以使用关键字 DEFAULT
将列显式设置为其默认值。WHERE
子句(如果给出)指定标识要更新哪些行的条件。如果没有 WHERE
子句,则更新所有行。如果指定了 ORDER BY
子句,则按指定的顺序更新行。LIMIT
子句限制了可以更新的行数。
对于多表语法,UPDATE
会更新 table_references
中命名的每个表中满足条件的行。 即使每行匹配多次条件,也只会更新一次。 对于多表语法,不能使用 ORDER BY
和 LIMIT
。
对于分区表,此语句的单表和多表形式都支持使用 PARTITION
子句作为表引用的一部分。 此选项接受一个或多个分区或子分区(或两者)的列表。 仅检查列出的分区(或子分区)是否匹配,并且不在任何这些分区或子分区中的行不会被更新,无论它是否满足 where_condition
。
与将 PARTITION
与 INSERT
或 REPLACE
语句一起使用的情况不同,即使列出的分区(或子分区)中没有行匹配 where_condition
,否则有效的 UPDATE ... PARTITION
语句也被视为成功。
有关更多信息和示例,请参见 第 26.5 节“分区选择”。
where_condition
是一个表达式,对于要更新的每一行都为真。 有关表达式语法,请参见 第 11.5 节“表达式”。
table_references
和 where_condition
的指定方式如 第 15.2.13 节“SELECT 语句” 中所述。
您只需要对在 UPDATE
中实际更新的列引用的列具有 UPDATE
权限。 对于任何读取但未修改的列,您只需要 SELECT
权限。
UPDATE
语句支持以下修饰符
使用
LOW_PRIORITY
修饰符,UPDATE
的执行将延迟,直到没有其他客户端从表中读取数据。 这仅影响使用表级锁定的存储引擎(例如MyISAM
、MEMORY
和MERGE
)。使用
IGNORE
修饰符,即使在更新期间发生错误,更新语句也不会中止。 唯一键值上发生重复键冲突的行不会更新。 更新为会导致数据转换错误的值的行将更新为最接近的有效值。 有关更多信息,请参见 IGNORE 对语句执行的影响。
UPDATE IGNORE
语句(包括具有 ORDER BY
子句的语句)被标记为对基于语句的复制不安全。 (这是因为行更新的顺序决定了哪些行被忽略。)此类语句在使用基于语句的模式时会在错误日志中生成警告,并在使用 MIXED
模式时使用基于行的格式写入二进制日志。 (错误 #11758262,错误 #50439)有关更多信息,请参见 第 19.2.1.3 节“确定二进制日志记录中的安全和不安全语句”。
如果在表达式中访问要更新的表中的列,则 UPDATE
将使用该列的当前值。 例如,以下语句将 col1
设置为比其当前值大 1
UPDATE t1 SET col1 = col1 + 1;
以下语句中的第二个赋值将 col2
设置为当前(已更新)的 col1
值,而不是原始的 col1
值。 结果是 col1
和 col2
具有相同的值。 此行为不同于标准 SQL。
UPDATE t1 SET col1 = col1 + 1, col2 = col1;
单表 UPDATE
赋值通常从左到右求值。 对于多表更新,不能保证赋值按任何特定顺序执行。
如果将列设置为其当前具有的值,MySQL 会注意到这一点并且不会更新它。
如果通过设置为 NULL
来更新已声明为 NOT NULL
的列,则在启用严格 SQL 模式时会发生错误; 否则,该列将设置为列数据类型的隐式默认值,并且警告计数会递增。 隐式默认值对于数字类型为 0
,对于字符串类型为空字符串 (''
),对于日期和时间类型为 “零” 值。 请参见 第 13.6 节“数据类型默认值”。
如果显式更新了生成的列,则唯一允许的值为 DEFAULT
。 有关生成的列的信息,请参见 第 15.1.20.8 节“CREATE TABLE 和生成的列”。
UPDATE
返回实际更改的行数。 mysql_info()
C API 函数返回匹配和更新的行数以及 UPDATE
期间发生的警告数。
您可以使用 LIMIT
限制 row_count
UPDATE
的范围。 LIMIT
子句是对匹配行的限制。 语句一旦找到满足 WHERE
子句的 row_count
行,无论它们是否实际更改,都会停止。
如果 UPDATE
语句包含 ORDER BY
子句,则会按照子句指定的顺序更新行。 这在某些情况下可能很有用,否则可能会导致错误。 假设表 t
包含一列具有唯一索引的 id
。 以下语句可能会因重复键错误而失败,具体取决于行更新的顺序
UPDATE t SET id = id + 1;
例如,如果表在 id
列中包含 1 和 2,并且在 2 更新为 3 之前将 1 更新为 2,则会发生错误。 为避免此问题,请添加 ORDER BY
子句以使 id
值较大的行在 id
值较小的行之前更新
UPDATE t SET id = id + 1 ORDER BY id DESC;
您还可以执行涵盖多个表的 UPDATE
操作。 但是,您不能将 ORDER BY
或 LIMIT
与多表 UPDATE
一起使用。 table_references
子句列出了连接中涉及的表。 它的语法在 第 15.2.13.2 节“JOIN 子句” 中描述。 下面是一个例子
UPDATE items,month SET items.price=month.price
WHERE items.id=month.id;
前面的示例显示了一个使用逗号运算符的内连接,但多表 UPDATE
语句可以使用 SELECT
语句中允许的任何类型的连接,例如 LEFT JOIN
。
如果对具有外键约束的 InnoDB
表使用涉及多表 UPDATE
语句,则 MySQL 优化器可能会按照与其父/子关系不同的顺序处理表。 在这种情况下,语句将失败并回滚。 相反,请更新单个表并依赖 InnoDB
提供的 ON UPDATE
功能来相应地修改其他表。 请参见 第 15.1.20.5 节“FOREIGN KEY 约束”。
您不能更新表并在子查询中直接从同一个表中进行选择。 您可以通过使用多表更新来解决此问题,其中一个表派生自您实际要更新的表,并使用别名引用派生表。 假设您要更新一个名为 items
的表,该表是使用此处显示的语句定义的
CREATE TABLE items (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
wholesale DECIMAL(6,2) NOT NULL DEFAULT 0.00,
retail DECIMAL(6,2) NOT NULL DEFAULT 0.00,
quantity BIGINT NOT NULL DEFAULT 0
);
要降低加价幅度为 30% 或以上且库存不足一百件的任何商品的零售价,您可以尝试使用 UPDATE
语句,例如以下语句,该语句在 WHERE
子句中使用子查询。 如下所示,此语句不起作用
mysql> UPDATE items
> SET retail = retail * 0.9
> WHERE id IN
> (SELECT id FROM items
> WHERE retail / wholesale >= 1.3 AND quantity > 100);
ERROR 1093 (HY000): You can't specify target table 'items' for update in FROM clause
相反,您可以采用多表更新,其中子查询被移动到要更新的表列表中,并使用别名在最外层的 WHERE
子句中引用它,如下所示
UPDATE items,
(SELECT id FROM items
WHERE id IN
(SELECT id FROM items
WHERE retail / wholesale >= 1.3 AND quantity < 100))
AS discounted
SET items.retail = items.retail * 0.9
WHERE items.id = discounted.id;
因为优化器默认尝试将派生表 discounted
合并到最外层的查询块中,所以这仅在您强制实例化派生表时才有效。 您可以通过在运行更新之前将 optimizer_switch
系统变量的 derived_merge
标志设置为 off
来实现,或者使用 NO_MERGE
优化器提示,如下所示
UPDATE /*+ NO_MERGE(discounted) */ items,
(SELECT id FROM items
WHERE retail / wholesale >= 1.3 AND quantity < 100)
AS discounted
SET items.retail = items.retail * 0.9
WHERE items.id = discounted.id;
在这种情况下,使用优化器提示的优点是它仅适用于使用它的查询块,因此在执行 UPDATE
后不需要再次更改 optimizer_switch
的值。
另一种可能性是重写子查询,使其不使用 IN
或 EXISTS
,如下所示
UPDATE items,
(SELECT id, retail / wholesale AS markup, quantity FROM items)
AS discounted
SET items.retail = items.retail * 0.9
WHERE discounted.markup >= 1.3
AND discounted.quantity < 100
AND items.id = discounted.id;
在这种情况下,子查询默认情况下是实例化的而不是合并的,因此不需要禁用派生表的合并。