#### 15.7.2.1 事务隔离级别

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

事务隔离是数据库处理的基础之一。隔离是首字母缩略词中的 I[酸](glossary.html#glos_acid);隔离级别是在多个事务同时进行更改和执行查询时微调性能和结果的可靠性、一致性和可再现性之间的平衡的设置。

`InnoDB`提供 SQL:1992 标准描述的所有四个事务隔离级别：[`阅读未提交`](innodb-transaction-isolation-levels.html#isolevel_read-uncommitted),[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed),[`可重复阅读`](innodb-transaction-isolation-levels.html#isolevel_repeatable-read)， 和[`可序列化`](innodb-transaction-isolation-levels.html#isolevel_serializable).的默认隔离级别`InnoDB`是[`可重复阅读`](innodb-transaction-isolation-levels.html#isolevel_repeatable-read).

用户可以更改单个会话或所有后续连接的隔离级别[`设置交易`](set-transaction.html)陈述。要为所有连接设置服务器的默认隔离级别，请使用[`--事务隔离`](server-options.html#option_mysqld_transaction-isolation)命令行或选项文件中的选项。有关隔离级别和级别设置语法的详细信息，请参阅[第 13.3.7 节，“SET TRANSACTION 语句”](set-transaction.html).

`InnoDB`支持使用不同的此处描述的每个事务隔离级别[锁定](glossary.html#glos_locking)策略。您可以强制执行与默认值的高度一致性[`可重复阅读`](innodb-transaction-isolation-levels.html#isolevel_repeatable-read)级别，对于关键数据的操作，其中[酸](glossary.html#glos_acid)合规性很重要。或者您可以放松一致性规则[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed)甚至[`阅读未提交`](innodb-transaction-isolation-levels.html#isolevel_read-uncommitted)，在批量报告等情况下，精确的一致性和可重复的结果不如最大限度地减少锁定开销重要。[`可序列化`](innodb-transaction-isolation-levels.html#isolevel_serializable)执行更严格的规则[`可重复阅读`](innodb-transaction-isolation-levels.html#isolevel_repeatable-read), 主要用于特殊情况，例如与[XA](glossary.html#glos_xa)事务以及解决并发问题和[死锁](glossary.html#glos_deadlock).

下面的列表描述了 MySQL 如何支持不同的事务级别。该列表从最常用的级别到最少使用的级别。

-   [](<>) `可重复阅读`

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

    这是默认的隔离级别`InnoDB`.[一致的读取](glossary.html#glos_consistent_read)在同一事务中读取[快照](glossary.html#glos_snapshot)由第一次阅读建立。这意味着，如果您发出几个普通（非锁定）[`选择`](select.html)同一事务中的语句，这些[`选择`](select.html)陈述彼此之间也是一致的。看[第 15.7.2.3 节，“一致的非锁定读取”](innodb-consistent-read.html).

    为了[锁定读取](glossary.html#glos_locking_read)([`选择`](select.html)和`更新`要么`分享`),[`更新`](update.html)， 和[`删除`](delete.html)语句，锁定取决于语句是使用具有唯一搜索条件的唯一索引，还是范围类型的搜索条件。

    -   对于具有唯一搜索条件的唯一索引，`InnoDB`只锁定找到的索引记录，而不是[差距](glossary.html#glos_gap)在它之前。

    -   对于其他搜索条件，`InnoDB`锁定扫描的索引范围，使用[间隙锁](glossary.html#glos_gap_lock)要么[下一键锁](glossary.html#glos_next_key_lock)阻止其他会话插入范围覆盖的间隙。有关间隙锁和下一键锁的信息，请参阅[第 15.7.1 节，“InnoDB 锁定”](innodb-locking.html).

-   [](<>) `阅读已提交`

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

    每个一致的读取，即使在同一个事务中，也会设置和读取自己的新快照。有关一致性读取的信息，请参阅[第 15.7.2.3 节，“一致的非锁定读取”](innodb-consistent-read.html).

    对于锁定读取 ([`选择`](select.html)和`更新`要么`分享`),[`更新`](update.html)陈述，以及[`删除`](delete.html)陈述，`InnoDB`只锁定索引记录，而不是它们之前的间隙，因此允许在锁定记录旁边自由插入新记录。间隙锁定仅用于外键约束检查和重复键检查。

    由于间隙锁定被禁用，因此可能会出现幻像行问题，因为其他会话可以将新行插入间隙中。有关虚拟行的信息，请参阅[第 15.7.4 节，“幻影行”](innodb-next-key-locking.html).

    仅支持基于行的二进制日志记录`阅读已提交`隔离级别。如果你使用`阅读已提交`和[`binlog_format=混合`](replication-options-binary-log.html#sysvar_binlog_format)，服务器自动使用基于行的日志记录。

    使用`阅读已提交`有额外的效果：

    -   为了[`更新`](update.html)要么[`删除`](delete.html)陈述，`InnoDB`仅对它更新或删除的行持有锁。不匹配行的记录锁在 MySQL 评估`在哪里`健康）状况。这大大降低了死锁的可能性，但它们仍然可能发生。

    -   为了[`更新`](update.html)语句，如果一行已经被锁定，`InnoDB`执行“半一致”读取，将最新提交的版本返回给 MySQL，以便 MySQL 可以确定该行是否匹配`在哪里`的条件[`更新`](update.html).如果行匹配（必须更新），MySQL 再次读取该行，这一次`InnoDB`要么锁定它，要么等待锁定它。

        考虑以下示例，从该表开始：

    ```
    CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
    INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
    COMMIT;
    ```

    在这种情况下，表没有索引，因此搜索和索引扫描使用隐藏的聚集索引来锁定记录（参见[第 15.6.2.1 节，“聚集索引和二级索引”](innodb-index-types.html)) 而不是索引列。

    假设一个会话执行一个[`更新`](update.html)使用这些语句：

    ```
    # Session A
    START TRANSACTION;
    UPDATE t SET b = 5 WHERE b = 3;
    ```

    还假设第二个会话执行[`更新`](update.html)通过在第一个会话之后执行这些语句：

    ```
    # Session B
    UPDATE t SET b = 4 WHERE b = 2;
    ```

    作为[`InnoDB`](innodb-storage-engine.html)执行每个[`更新`](update.html)，它首先为每一行获取一个排他锁，然后决定是否修改它。如果[`InnoDB`](innodb-storage-engine.html)不修改行，它释放锁。否则，[`InnoDB`](innodb-storage-engine.html)保留锁直到事务结束。这会影响事务处理，如下所示。

    使用默认值时`可重复阅读`隔离级别，第一[`更新`](update.html)在它读取的每一行上获取一个 x 锁并且不释放它们中的任何一个：

    ```
    x-lock(1,2); retain x-lock
    x-lock(2,3); update(2,3) to (2,5); retain x-lock
    x-lock(3,2); retain x-lock
    x-lock(4,3); update(4,3) to (4,5); retain x-lock
    x-lock(5,2); retain x-lock
    ```

    第二[`更新`](update.html)尝试获取任何锁时立即阻塞（因为第一次更新在所有行上都保留了锁），直到第一次更新才继续[`更新`](update.html)提交或回滚：

    ```
    x-lock(1,2); block and wait for first UPDATE to commit or roll back
    ```

    如果`阅读已提交`而是使用第一个[`使现代化`](update.html)在读取的每一行上获取x锁，并释放未修改的行的x锁：

    ```
    x-lock(1,2); unlock(1,2)
    x-lock(2,3); update(2,3) to (2,5); retain x-lock
    x-lock(3,2); unlock(3,2)
    x-lock(4,3); update(4,3) to (4,5); retain x-lock
    x-lock(5,2); unlock(5,2)
    ```

    第二次`使现代化`,`InnoDB`执行“半一致”读取，将读取的每一行的最新提交版本返回给MySQL，以便MySQL可以确定该行是否与`哪里`情况[`使现代化`](update.html):

    ```
    x-lock(1,2); update(1,2) to (1,4); retain x-lock
    x-lock(2,3); unlock(2,3)
    x-lock(3,2); update(3,2) to (3,4); retain x-lock
    x-lock(4,3); unlock(4,3)
    x-lock(5,2); update(5,2) to (5,4); retain x-lock
    ```

    然而，如果`哪里`条件包括一个索引列，以及`InnoDB`使用索引时，在获取和保留记录锁时只考虑索引列。在下面的示例中，第一个[`使现代化`](update.html)在b=2的每一行上获取并保留一个x锁。第二个[`使现代化`](update.html)当它试图在相同的记录上获取x锁时会阻塞，因为它还使用在列b上定义的索引。

    ```
    CREATE TABLE t (a INT NOT NULL, b INT, c INT, INDEX (b)) ENGINE = InnoDB;
    INSERT INTO t VALUES (1,2,3),(2,2,4);
    COMMIT;

    # Session A
    START TRANSACTION;
    UPDATE t SET b = 3 WHERE b = 2 AND c = 3;

    # Session B
    UPDATE t SET b = 4 WHERE b = 2 AND c = 4;
    ```

    这个`阅读承诺`隔离级别可以在启动时设置，也可以在运行时更改。在运行时，可以为所有会话全局设置，也可以为每个会话单独设置。

-   [](<>) `阅读未提交`

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

    [`选择`](select.html)语句以非锁定方式执行，但可能会使用行的早期版本。因此，使用这种隔离级别，这样的读取是不一致的。这也被称为[脏书](glossary.html#glos_dirty_read).否则，该隔离级别的工作原理如下[`阅读承诺`](innodb-transaction-isolation-levels.html#isolevel_read-committed).

-   [](<>) `可序列化`

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

    这个级别就像[`可重复读取`](innodb-transaction-isolation-levels.html#isolevel_repeatable-read)但是`InnoDB`隐式地将所有[`选择`](select.html)对……的陈述[`选择分享`](select.html)如果[`自动提交`](server-system-variables.html#sysvar_autocommit)它是残疾的。如果[`自动提交`](server-system-variables.html#sysvar_autocommit)如果已启用，则[`选择`](select.html)这是它自己的交易。因此，已知它是只读的，如果作为一致（非锁定）读取执行，并且不需要为其他事务阻塞，则可以序列化。（迫使一个平原[`选择`](select.html)要阻止其他事务是否修改了所选行，请禁用[`自动提交`](server-system-variables.html#sysvar_autocommit).)

    笔记

    从MySQL 8.0.22开始，从MySQL授权表（通过联接列表或子查询）读取数据但不修改数据的DML操作不会获取MySQL授权表上的读锁，无论隔离级别如何。有关更多信息，请参阅[授权表并发](grant-tables.html#grant-tables-concurrency).
