17.7.2.1 事务隔离级别
事务隔离是数据库处理的基础之一。隔离是ACID中的I;隔离级别是调整性能和可靠性、一致性和结果可重复性的平衡设置,当多个事务同时进行更改和查询时。
InnoDB
提供了SQL:1992标准中描述的四种事务隔离级别:READ UNCOMMITTED
、READ COMMITTED
、REPEATABLE READ
和SERIALIZABLE
。InnoDB
的默认隔离级别是REPEATABLE READ
。
用户可以使用 SET TRANSACTION
语句更改单个会话或所有后续连接的隔离级别。要将服务器的默认隔离级别设置为所有连接,请在命令行或选项文件中使用 --transaction-isolation
选项。关于隔离级别和级别设置语法的详细信息,请见第15.3.7节,“SET TRANSACTION Statement”。
InnoDB
支持本文中描述的每种事务隔离级别,使用不同的锁定策略。您可以使用默认的REPEATABLE READ
级别,强制高程度的一致性,对于对关键数据进行操作时,ACID一致性非常重要。或者,您可以 relaxation 一致性规则使用READ COMMITTED
或READ UNCOMMITTED
,在情况下,如批量报告,精确的一致性和可重复结果不太重要,而锁定开销最小化时。
以下列表描述了 MySQL 支持的不同事务级别。列表从最常用的级别到最少使用的级别。
-
这是
InnoDB
的默认隔离级别。 一致读取 在同一个事务中读取由第一个读取建立的 快照。这意味着,如果您在同一个事务中-issue多个简单(非锁定)的SELECT
语句,这些SELECT
语句也是一致的,相对于彼此。请参阅第17.7.2.3节,“一致非锁定读取”。对于锁定读取(
SELECT
withFOR UPDATE
或FOR SHARE
),UPDATE
和DELETE
语句,锁定取决于语句是否使用唯一索引和唯一搜索条件,或者范围类型的搜索条件。-
对于唯一索引和唯一搜索条件,
InnoDB
只锁定找到记录的索引记录,而不是空隙。 -
对于其他搜索条件,
InnoDB
将锁定扫描的索引范围,使用gap locks或next-key locks来阻止其他会话在范围内插入记录。关于gap锁和next-key锁的信息,请见第17.7.1节,“InnoDB Locking”。
-
-
每个一致读取,包括同一个事务中的读取,都是设置和读取自己的最新快照。关于一致读取的信息,请见第17.7.2.3节,“一致非锁定读取”。
对于锁定读取(
SELECT
withFOR UPDATE
或FOR SHARE
),UPDATE
语句和DELETE
语句,InnoDB
只锁定索引记录,而不是它们之前的空隙,因此允许在锁定的记录旁边插入新的记录。gap锁仅用于外键约束检查和重复键检查。由于 gap锁被禁用,可能会出现幻行问题,因为其他会话可以将新行插入到空隙中。关于幻行的信息,请见第17.7.4节,“幻行”。
只支持基于行的二进制日志记录与
READ COMMITTED
隔离级别。使用READ COMMITTED
和binlog_ format=MIXED
时,服务器自动使用基于行的记录。使用
READ COMMITTED
还会产生以下影响:考虑创建并填充表的方式:
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)
对于第二个
UPDATE
,InnoDB
执行一个“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
隔离级别。在运行时,可以将其设置为所有会话的全局值,也可以单独设置每个会话。 -
SELECT
语句在非锁定方式下执行,但可能使用该行的早期版本。因此,在使用这个隔离级别时,这些读取操作不一致。这也被称为脏读。否则,这个隔离级别与READ COMMITTED
相同。 -
这个级别类似于
REPEATABLE READ
,但是InnoDB
在autocommit
被禁用时,将所有普通SELECT
语句隐式转换为SELECT ... FOR SHARE
。如果autocommit
被启用,那么SELECT
语句将是其自己的事务。因此,它被认为只读,可以被序列化,如果作为一致的(非锁定)读取执行,不需要阻塞其他事务。(要强制普通SELECT
语句阻塞其他事务已经修改的行,禁用autocommit
。)读取 MySQL 授權表(通过连接列表或子查询)的 DML 操作,但不修改它们,不会在任何隔离级别下获取授權表的读锁定。更多信息,请见Grant Table Concurrency。