文档首页
MySQL 8.4 参考手册
相关文档 下载本手册
PDF (US Ltr) - 39.9Mb
PDF (A4) - 40.0Mb
手册页 (TGZ) - 258.5Kb
手册页 (Zip) - 365.5Kb
信息 (Gzip) - 4.0Mb
信息 (Zip) - 4.0Mb


MySQL 8.4 参考手册  /  ...  /  UPDATE 语句

15.2.17 UPDATE 语句

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 BYLIMIT

对于分区表,此语句的单表和多表形式都支持使用 PARTITION 子句作为表引用的一部分。此选项接受一个或多个分区或子分区(或两者)的列表。只会检查列出的分区(或子分区)是否匹配,不在这些分区或子分区中的行不会更新,无论是否满足 where_condition

注意

与将 PARTITIONINSERTREPLACE 语句一起使用的情况不同,即使列出的分区(或子分区)中没有行匹配 where_condition,否则有效的 UPDATE ... PARTITION 语句也被视为成功。

有关更多信息和示例,请参阅 第 26.5 节,“分区选择”

where_condition 是一个表达式,对于要更新的每一行都为真。有关表达式语法,请参阅 第 11.5 节,“表达式”

table_referenceswhere_condition 的指定方式如 第 15.2.13 节,“SELECT 语句” 中所述。

您只需要对 UPDATE 中实际更新的列具有 UPDATE 权限。对于任何读取但未修改的列,您只需要 SELECT 权限。

UPDATE 语句支持以下修饰符

  • 使用 LOW_PRIORITY 修饰符,UPDATE 的执行将延迟到没有其他客户端从表中读取数据为止。这仅影响使用表级锁定的存储引擎(例如 MyISAMMEMORYMERGE)。

  • 使用 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 值。结果是 col1col2 具有相同的值。此行为与标准 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 值的行在具有较小值的行的前面更新

UPDATE t SET id = id + 1 ORDER BY id DESC;

您还可以执行涵盖多个表的 UPDATE 操作。但是,不能将 ORDER BYLIMIT 与多表 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% 或更高且库存少于 100 件的任何商品的零售价,您可能会尝试使用 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 的值。

另一种可能性是重写子查询,使其不使用 INEXISTS,如下所示

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;

在这种情况下,子查询默认是物化的而不是合并的,因此不需要禁用派生表的合并。