事务隔离是数据库处理的基础之一。隔离是首字母缩略词 ACID 中的 I;隔离级别是在多个事务同时进行更改和执行查询时,微调性能与可靠性、一致性和结果可重复性之间平衡的设置。
InnoDB
提供 SQL:1992 标准描述的所有四种事务隔离级别:READ UNCOMMITTED
、READ COMMITTED
、REPEATABLE READ
和 SERIALIZABLE
。InnoDB
的默认隔离级别为 REPEATABLE READ
。
用户可以使用 SET TRANSACTION
语句更改单个会话或所有后续连接的隔离级别。要为所有连接设置服务器的默认隔离级别,请在命令行或选项文件中使用 --transaction-isolation
选项。有关隔离级别和级别设置语法的详细信息,请参见 第 15.3.7 节“SET TRANSACTION 语句”。
InnoDB
使用不同的 锁定 策略支持此处描述的每个事务隔离级别。您可以使用默认的 REPEATABLE READ
级别对关键数据的操作强制执行高度的一致性,其中 ACID 合规性非常重要。或者,您可以在诸如批量报告之类的场景中,使用 READ COMMITTED
甚至 READ UNCOMMITTED
放宽一致性规则,在这些场景中,精确一致性和可重复结果不如最小化锁定开销重要。SERIALIZABLE
强制执行比 REPEATABLE READ
更严格的规则,并且主要用于特殊情况,例如使用 XA 事务以及解决并发性和 死锁 问题。
以下列表描述了 MySQL 如何支持不同的交易级别。该列表从最常用的级别到最少使用的级别排列。
这是
InnoDB
的默认隔离级别。同一事务中的 一致性读取 读取由第一次读取建立的 快照。这意味着,如果您在同一事务中发出多个普通(非锁定)SELECT
语句,则这些SELECT
语句在彼此之间也是一致的。请参见 第 17.7.2.3 节“一致性非锁定读取”。对于 锁定读取(带有
FOR UPDATE
或FOR SHARE
的SELECT
)、UPDATE
和DELETE
语句,锁定取决于该语句是使用具有唯一搜索条件的唯一索引还是使用范围类型搜索条件。对于具有唯一搜索条件的唯一索引,
InnoDB
仅锁定找到的索引记录,而不锁定它之前的 间隙。对于其他搜索条件,
InnoDB
会锁定扫描的索引范围,使用 间隙锁 或 临键锁 来阻止其他会话将记录插入到该范围内的间隙中。有关间隙锁和临键锁的信息,请参阅 “17.7.1 节 “InnoDB 锁定””。
每个一致性读取,即使在同一个事务中,也会设置和读取其自身的最新快照。有关一致性读取的信息,请参阅 “17.7.2.3 节 “一致性非锁定读取””。
对于锁定读取(带有
FOR UPDATE
或FOR SHARE
的SELECT
语句)、UPDATE
语句和DELETE
语句,InnoDB
仅锁定索引记录,而不锁定它们之前的间隙,因此允许在锁定记录旁边自由插入新记录。间隙锁仅用于外键约束检查和重复键检查。由于间隙锁被禁用,因此可能会出现幻读问题,因为其他会话可以在间隙中插入新行。有关幻读的信息,请参阅 “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
会执行““半一致性””读取,将其读取的每一行的最新提交版本返回给 MySQL,以便 MySQL 可以确定该行是否与UPDATE
的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
语句在尝试获取相同记录的 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;
可以在启动时设置
READ COMMITTED
隔离级别,也可以在运行时更改它。在运行时,可以为所有会话全局设置它,也可以为每个会话单独设置。SELECT
语句以非锁定方式执行,但可能会使用行的可能较早版本。因此,使用此隔离级别,此类读取不一致。这也被称为 脏读。否则,此隔离级别的作用类似于READ COMMITTED
。此级别类似于
REPEATABLE READ
,但如果autocommit
被禁用,则InnoDB
会将所有普通的SELECT
语句隐式转换为SELECT ... FOR SHARE
。如果autocommit
已启用,则SELECT
是其自身的事务。因此,已知它是只读的,如果作为一致性(非锁定)读取执行,则可以序列化,并且不需要阻塞其他事务。(要强制普通的SELECT
在其他事务修改了所选行时阻塞,请禁用autocommit
。)从 MySQL 授权表读取数据(通过连接列表或子查询)但不修改它们的 DML 操作不会在 MySQL 授权表上获取读锁,无论隔离级别如何。有关更多信息,请参阅 授权表并发。