Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


17.7.2.1 事务隔离级别

事务隔离是数据库处理的基础之一。隔离是ACID中的I;隔离级别是调整性能和可靠性、一致性和结果可重复性的平衡设置,当多个事务同时进行更改和查询时。

InnoDB 提供了SQL:1992标准中描述的四种事务隔离级别:READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLEInnoDB 的默认隔离级别是REPEATABLE READ

用户可以使用 SET TRANSACTION 语句更改单个会话或所有后续连接的隔离级别。要将服务器的默认隔离级别设置为所有连接,请在命令行或选项文件中使用 --transaction-isolation 选项。关于隔离级别和级别设置语法的详细信息,请见第15.3.7节,“SET TRANSACTION Statement”

InnoDB 支持本文中描述的每种事务隔离级别,使用不同的锁定策略。您可以使用默认的REPEATABLE READ级别,强制高程度的一致性,对于对关键数据进行操作时,ACID一致性非常重要。或者,您可以 relaxation 一致性规则使用READ COMMITTEDREAD UNCOMMITTED,在情况下,如批量报告,精确的一致性和可重复结果不太重要,而锁定开销最小化时。

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

  • REPEATABLE READ

    这是 InnoDB 的默认隔离级别。 一致读取 在同一个事务中读取由第一个读取建立的 快照。这意味着,如果您在同一个事务中-issue多个简单(非锁定)的SELECT 语句,这些SELECT 语句也是一致的,相对于彼此。请参阅第17.7.2.3节,“一致非锁定读取”

    对于锁定读取SELECT with FOR UPDATEFOR SHARE),UPDATEDELETE 语句,锁定取决于语句是否使用唯一索引和唯一搜索条件,或者范围类型的搜索条件。

    • 对于唯一索引和唯一搜索条件,InnoDB 只锁定找到记录的索引记录,而不是空隙

    • 对于其他搜索条件,InnoDB 将锁定扫描的索引范围,使用gap locksnext-key locks来阻止其他会话在范围内插入记录。关于gap锁和next-key锁的信息,请见第17.7.1节,“InnoDB Locking”

  • READ COMMITTED

    每个一致读取,包括同一个事务中的读取,都是设置和读取自己的最新快照。关于一致读取的信息,请见第17.7.2.3节,“一致非锁定读取”

    对于锁定读取(SELECT with FOR UPDATEFOR SHARE),UPDATE 语句和DELETE 语句,InnoDB 只锁定索引记录,而不是它们之前的空隙,因此允许在锁定的记录旁边插入新的记录。gap锁仅用于外键约束检查和重复键检查。

    由于 gap锁被禁用,可能会出现幻行问题,因为其他会话可以将新行插入到空隙中。关于幻行的信息,请见第17.7.4节,“幻行”

    只支持基于行的二进制日志记录与READ COMMITTED隔离级别。使用READ COMMITTEDbinlog_ format=MIXED时,服务器自动使用基于行的记录。

    使用READ COMMITTED还会产生以下影响:

    • 对于UPDATEDELETE语句,InnoDB只持有更新或删除的行锁。对于不匹配的行,记录锁将在MySQL评估WHERE条件后释放。这大大减少了死锁的可能性,但仍然可能发生。

    • 对于UPDATE语句,如果一行已经被锁定,InnoDB将执行“semi-consistent”读取,返回最新提交的版本到MySQL,以便MySQL确定该行是否匹配UPDATEWHERE条件。如果该行匹配(必须更新),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;

    在这种情况下,表没有索引,所以搜索和索引扫描使用隐藏的聚簇索引来实现记录锁定(见第17.6.2.1节,“聚簇索引和次要索引”),而不是使用索引列。

    假设有一会话执行以下语句的UPDATE:

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

    假设第二会话也执行以下语句的UPDATE,紧接着第一会话的语句:

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

    InnoDB执行每个UPDATE时,它首先获取每行的排他锁,然后确定是否需要修改该行。如果InnoDB不修改该行,它就释放锁定。否则,InnoDB保留锁定直到事务结束。这对事务处理产生以下影响。

    在使用默认的REPEATABLE READ隔离级别时,第一个UPDATE获取每行的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块马上就阻塞了,因为第一个更新操作已经保留了所有行的锁定,并且直到第一个UPDATE提交或回滚:

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

    如果使用READ COMMITTED,第一个UPDATE将在读取每行时获取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)

    对于第二个UPDATEInnoDB执行一个“semi-consistent”读取,返回每行最新提交版本给MySQL,以便MySQL可以确定该行是否满足WHERE条件的更新操作:

    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

    然而,如果WHERE条件包括索引列,并且InnoDB使用了索引,那么在获取和保留记录锁定时,只考虑索引列。在以下示例中,第一个UPDATE获取并保留每行b = 2的x锁。第二个UPDATE阻塞,因为它也使用了定义在列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;

    可以在启动时或运行时设置READ COMMITTED隔离级别。在运行时,可以将其设置为所有会话的全局值,也可以单独设置每个会话。

  • READ UNCOMMITTED

    SELECT 语句在非锁定方式下执行,但可能使用该行的早期版本。因此,在使用这个隔离级别时,这些读取操作不一致。这也被称为脏读。否则,这个隔离级别与READ COMMITTED相同。

  • SERIALIZABLE

    这个级别类似于REPEATABLE READ,但是InnoDBautocommit被禁用时,将所有普通SELECT语句隐式转换为SELECT ... FOR SHARE。如果autocommit被启用,那么SELECT语句将是其自己的事务。因此,它被认为只读,可以被序列化,如果作为一致的(非锁定)读取执行,不需要阻塞其他事务。(要强制普通SELECT语句阻塞其他事务已经修改的行,禁用autocommit。)

    读取 MySQL 授權表(通过连接列表或子查询)的 DML 操作,但不修改它们,不会在任何隔离级别下获取授權表的读锁定。更多信息,请见Grant Table Concurrency