### 13.2.13 UPDATE 语句

[](<>)[](<>)

[`更新`](update.html)是修改表中行的 DML 语句。

一个[`更新`](update.html)语句可以以 a 开头[`和`](with.html)子句定义可在[`更新`](update.html).看[第 13.2.15 节，“WITH（公用表表达式）”](with.html).

单表语法：

```
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.html)语句用新值更新命名表中现有行的列。这`放`子句指示要修改的列以及应给定的值。每个值都可以作为表达式或关键字给出`默认`将列显式设置为其默认值。这`在哪里`如果给定子句，则指定标识要更新哪些行的条件。没有`在哪里`子句，所有行都被更新。如果`订购方式`子句时，行将按指定的顺序更新。这`限制`子句限制可以更新的行数。

对于多表语法，[`更新`](update.html)更新每个名为 in 的表中的行*`表参考`*满足条件的。每个匹配的行都会更新一次，即使它多次匹配条件。对于多表语法，`订购方式`和`限制`不能使用。

对于分区表，该语句的单表和多表形式都支持使用`划分`子句作为表引用的一部分。此选项采用一个或多个分区或子分区（或两者）的列表。仅检查列出的分区（或子分区）是否匹配，并且不更新不在这些分区或子分区中的行，无论它是否满足*`where_condition`*或不。

笔记

与使用时的情况不同`划分`与[`插入`](insert.html)要么[`代替`](replace.html)声明，否则有效`更新...分区`即使列出的分区（或子分区）中没有行匹配*`where_condition`*.

有关更多信息和示例，请参阅[第 24.5 节，“分区选择”](partitioning-selection.html).

*`where_condition`*是一个表达式，对于要更新的​​每一行，其计算结果为真。有关表达式语法，请参阅[第 9.5 节，“表达式”](expressions.html).

*`表参考`*和*`where_condition`*指定如中所述[第 13.2.10 节，“SELECT 语句”](select.html).

你需要[`更新`](privileges-provided.html#priv_update)特权仅适用于在[`更新`](update.html)实际上已更新。你只需要[`选择`](privileges-provided.html#priv_select)读取但未修改的任何列的特权。

这[`更新`](update.html)语句支持以下修饰符：

-   随着`低优先级`修改器，执行[`更新`](update.html)被延迟，直到没有其他客户端从表中读取。这仅影响仅使用表级锁定的存储引擎（例如`MyISAM`,`记忆`， 和`合并`）。

    [](<>)

-   随着`忽视`修饰符，即使更新期间发生错误，更新语句也不会中止。不会更新在唯一键值上发生重复键冲突的行。更新为会导致数据转换错误的值的行将更新为最接近的有效值。有关详细信息，请参阅[IGNORE 对语句执行的影响](sql-mode.html#ignore-effect-on-execution).

    [](<>)

[`更新忽略`](update.html)陈述，包括那些有`订购方式`子句，对于基于语句的复制被标记为不安全。（这是因为行更新的顺序决定了哪些行被忽略。）此类语句在使用基于语句的模式时会在错误日志中产生警告，并在使用时使用基于行的格式写入二进制日志`混合`模式。（错误 #11758262、错误 #50439）请参阅[第 17.2.1.3 节，“确定二进制日志中的安全和不安全语句”](replication-rbr-safe-unsafe.html)， 了解更多信息。

如果您访问要在表达式中更新的表中的列，[`更新`](update.html)使用列的当前值。例如，以下语句集`col1`比其当前值大一：

```
UPDATE t1 SET col1 = col1 + 1;
```

以下语句中的第二个赋值集`col2`到当前（更新）`col1`价值，而不是原始`col1`价值。结果是`col1`和`col2`具有相同的价值。此行为不同于标准 SQL。

```
UPDATE t1 SET col1 = col1 + 1, col2 = col1;
```

单桌[`更新`](update.html)作业通常从左到右进行评估。对于多表更新，不能保证以任何特定顺序执行分配。

如果您将列设置为它当前具有的值，MySQL 会注意到这一点并且不会更新它。

如果更新已声明的列`非空`通过设置为`空值`, 如果启用严格的 SQL 模式会出错；否则，该列将设置为该列数据类型的隐式默认值，并且警告计数会增加。隐含的默认值为`0`对于数字类型，空字符串 (`''`) 用于字符串类型，“零”值用于日期和时间类型。看[第 11.6 节，“数据类型默认值”](data-type-defaults.html).

[](<>)

如果显式更新生成的列，则唯一允许的值是`默认`.有关生成的列的信息，请参阅[第 13.1.20.8 节，“创建表和生成的列”](create-table-generated-columns.html).

[](<>)

[`更新`](update.html)返回实际更改的行数。这[`mysql_info()`](https://dev.mysql.com/doc/c-api/8.0/en/mysql-info.html)C API 函数返回匹配和更新的行数以及在执行过程中发生的警告数[`更新`](update.html).

您可以使用`限制 *`行数`*`来限制范围[`更新`](update.html).一种`限制`子句是行匹配的限制。语句一找到就停止*`行数`*满足的行`在哪里`子句，不管它们是否真的被改变了。

如果[`更新`](update.html)声明包括一个`订购方式`子句，行将按照子句指定的顺序更新。这在某些可能会导致错误的情况下很有用。假设一个表`吨`包含一列`id`具有唯一索引。以下语句可能会因重复键错误而失败，具体取决于更新行的顺序：

```
UPDATE t SET id = id + 1;
```

例如，如果表中包含 1 和 2`id`列和 1 在 2 更新为 3 之前更新为 2，出现错误。为避免此问题，请添加`订购方式`子句导致更大的行`id`在具有较小值的值之前更新值：

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

你也可以执行[`更新`](update.html)覆盖多个表的操作。但是，您不能使用`订购方式`要么`限制`多表[`更新`](update.html).这*`表参考`*子句列出了连接中涉及的表。它的语法在[第 13.2.10.2 节，“JOIN 子句”](join.html).这是一个例子：

```
UPDATE items,month SET items.price=month.price
WHERE items.id=month.id;
```

前面的示例显示了一个使用逗号运算符的内连接，但它是多表[`更新`](update.html)语句可以使用允许的任何类型的连接[`选择`](select.html)陈述，例如`左连接`.

如果您使用多表[`更新`](update.html)涉及的声明`InnoDB`对于存在外键约束的表，MySQL 优化器可能会以不同于其父/子关系的顺序处理表。在这种情况下，语句失败并回滚。相反，更新单个表并依赖`在更新`能力`InnoDB`规定导致其他表被相应地修改。看[第 13.1.20.5 节，“外键约束”](create-table-foreign-keys.html).

您不能更新表并直接从子查询中的同一个表中进行选择。您可以通过使用多表更新来解决此问题，其中一个表派生自您实际希望更新的表，并使用别名引用派生表。假设您希望更新一个名为`项目`这是使用此处显示的语句定义的：

```
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 件的商品的零售价，您可以尝试使用`更新`如下语句，它在`在哪里`条款。如此处所示，此语句不起作用：

```
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
```

相反，您可以使用多表更新，其中子查询被移动到要更新的表列表中，使用别名在最外层引用它`在哪里`子句，像这样：

```
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;
```

因为优化器默认尝试合并派生表`打折`进入最外层的查询块，这仅在您强制实现派生表时才有效。您可以通过设置[`派生合并`](switchable-optimizations.html#optflag_derived-merge)国旗[`优化器开关`](server-system-variables.html#sysvar_optimizer_switch)系统变量为`离开`在运行更新之前，或使用[`NO_MERGE`](optimizer-hints.html#optimizer-hints-table-level)优化器提示，如下所示：

```
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 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;
```

在这种情况下，子查询默认实现而不是合并，因此不必禁用派生表的合并。
