MYSQL存储引擎InnoDB(四十六):事务隔离级别

事务隔离是数据库处理的基础之一。Isolation 是首字母缩写词 ACID中的 I ;隔离级别是在多个事务同时进行更改和执行查询时微调性能与结果的可靠性、一致性和可再现性之间的平衡的设置。

InnoDB提供 SQL:1992 标准描述的所有四个事务隔离级别 : READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ和SERIALIZABLE。InnoDB的默认隔离级别是REPEATABLE READ。

用户可以使用SET TRANSACTION语句更改单个会话或所有后续连接的隔离级别。要为所有连接设置服务器的默认隔离级别,请在命令行或选项文件中使用--transaction-isolation选项。

InnoDB使用不同的锁定策略支持此处描述的每个事务隔离级别 。对于ACID合规性很重要的关键数据操作, 您可以强制执行与默认REPEATABLE READ级别的高度一致性 。或者,您可以使用READ COMMITTED甚至READ UNCOMMITTED放宽一致性规则, 例如在批量报告等情况下,最小化锁定开销比精确的一致性和可重复的结果更重要。 SERIALIZABLE执行比REPEATABLE READ更严格的规则,主要用于特殊情况,例如XA事务以及解决并发和死锁问题。

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

1、REPEATABLE READ

这是 InnoDB的默认隔离级别。同一事务中的 一致性读取规则将会读取第一次读取建立的快照。这意味着,如果您在同一个事务中发出多个普通(非锁定)SELECT 语句,这些 SELECT语句彼此之间也是一致的。

对于锁定读取 (SELECT 带 FOR UPDATE或FOR SHARE)、 UPDATE和 DELETE语句,锁定取决于语句是使用具有唯一搜索条件的唯一索引还是范围类型的搜索条件。

对于具有唯一搜索条件的唯一索引, InnoDB只锁定找到的索引记录,而不锁定它之前的间隙。

对于其他搜索条件,InnoDB 锁定扫描的索引范围,使用间隙锁或下一键锁来阻止其他会话插入该范围所覆盖的间隙。


2、READ COMMITTED

对于每次一致读取,即使在同一个事务中,也会设置并读取自己的新快照。

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

由于间隙锁定已禁用,因此可能会出现幻读问题,因为其他会话可以将新行插入间隙中。

READ COMMITTED隔离级别仅支持基于行的二进制日志记录 。如果使用READ COMMITTED同时 binlog_format=MIXED,服务器会自动使用基于行的日志记录。

使用READ COMMITTED有以下额外的效果:

(1)对于UPDATE或 DELETE语句, InnoDB仅对它更新或删除的行持有锁。WHERE在 MySQL 评估条件后释放不匹配行的记录锁 。这大大降低了死锁的可能性,但它们仍然可能发生。

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


在这种情况下,表没有索引,因此搜索和索引扫描使用隐藏的聚集索引来锁定记录而不是索引列。

假设一个会话 UPDATE使用这些语句执行:

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


还假设第二个会话 UPDATE通过在第一个会话之后执行这些语句来执行:

# Session BUPDATE t SET b = 4 WHERE b = 2;


在InnoDB执行每个UPDATE时,它首先为每一行获取一个排他锁,然后决定是否修改它。如果InnoDB不修改行,则释放锁。否则, InnoDB保留锁直到事务结束。这会影响事务处理,如下所示。

当使用默认REPEATABLE READ 隔离级别时,第一个UPDATE在它读取的每一行上获取一个 x 锁并且不释放它们中的任何一个:

x-lock(1,2); retain x-lockx-lock(2,3); update(2,3) to (2,5); retain x-lockx-lock(3,2); retain x-lockx-lock(4,3); update(4,3) to (4,5); retain x-lockx-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-lockx-lock(3,2); unlock(3,2)x-lock(4,3); update(4,3) to (4,5); retain x-lockx-lock(5,2); unlock(5,2)


对于第二个UPDATE, InnoDB进行 “半一致”读取,将读取到的每一行的最新提交版本返回给 MySQL,以便 MySQL 可以确定该行是否符合 WHERE条件 UPDATE:

x-lock(1,2); update(1,2) to (1,4); retain x-lockx-lock(2,3); unlock(2,3)x-lock(3,2); update(3,2) to (3,4); retain x-lockx-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 ASTART TRANSACTION;UPDATE t SET b = 3 WHERE b = 2 AND c = 3;# Session BUPDATE t SET b = 4 WHERE b = 2 AND c = 4;


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


3、READ UNCOMMITTED

SELECT语句以非锁定方式执行,但可能会使用行的早期版本。因此,使用这个隔离级别时的读取是不一致的。这也称为 脏读。否则,此隔离级别的工作方式类似于 READ COMMITTED。


4、SERIALIZABLE

此级别类似于REPEATABLE READ,但InnoDB在autocommit已禁用的情况下将所有普通SELECT语句隐式转换为SELECT ... FOR SHARE。如果autocommit启用, 则SELECT是它自己的事务。因此,如果作为一致读取执行并且不需要阻塞其他事务,它可以被视为只读且是序列化的。(如果其他事务修改了选定的行,为了强制普通SELECT阻塞,需要禁用autocommit。)

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章