一致性的非锁定读(consistent nonlocking read)是指 InnoDB存储引擎通过行多版本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行 DELETE或 UPDATE操作,这时读取操作不会因此去等待行上锁的释放。相反地, InnoDB存储引擎会去读取行的一个快照数据。如图所示。
图6-4直观地展现了 InnoDB存储引擎一致性的非锁定读。之所以称其为非锁定读,因为不需要等待访问的行上X锁的释放。
快照数据是指该行的之前版本的数据,该实现是通过undo段来完成。而 undo用来在事务中回滚数据,因此快照数据本身是没有额外的开销。此外,读取快照数据是不需要上锁的,因为没有事务需要对历史的数据进行修改操作。
可以看到,非锁定读机制极大地提高了数据库的并发性。在 InnoDB存储引擎的默认设置下,这是默认的读取方式,即读取不会占用和等待表上的锁。但是在不同事务隔离级别下,读取的方式不同,并不是在每个事务隔离级别下都是采用非锁定的一致性读。此外,即使都是使用非锁定的一致性读,但是对于快照数据的定义也各不相同。
通过图6-4可以知道,快照数据其实就是当前行数据之前的历史版本,每行记录可能有多个版本。就图6-4所显示的,一个行记录可能有不止一个快照数据,一般称这种技术为行多版本技术。由此带来的并发控制,称之为多版本并发控制(Multi version Concurrency Control, MVCC)。
在事务隔离级别 READ COMMITTED和 REPEATABLE READ(InnoDB存储引擎的默认事务隔离级别)下, InnoDB存储引擎使用非锁定的一致性读。然而,对于快照数据的定义却不相同。在 READ COMMITTED事务隔离级别下,对于快照数据,非一致性读总是读取被锁定行的最新一份快照数据。而在REPEATABLE READ事务隔离级别下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。来看下面的一个例子,首先在当前 MySQL数据库的连接会话A中执行如下SQL语句:
#Session A
mysql> BEGIN;
Query OK,0 rows affected (0.00 sec)
mysql> SELECT * FROM parent WHERE id =1;
id
1
会话A中已通过显式地执行命令 BEGIN开启了一个事务,并读取了表 parent中id为1的数据,但是事务并没有结束。与此同时,用户再开启另一个会话B,这样可以模拟并发的情况,然后对会话B做如下的操作
#Session B
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE parent set id=3 WHERE id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
在会话B中将事务表 parent中id为1的记录修改为id=3,但是事务同样没有提交,这样id=1的行其实加了一个X锁。这时如果在会话A中再次读取id为1的记录,根据 InnoDB存储引擎的特性,即在 READ COMMITTED和 REPEATETABLE READ的事务隔离级别下会使用非锁定的一致性读。回到之前的会话A,接着上次未提交的事务,执行SQL语句 SELECT* FROM parent WHERE id=1的操作,这时不管使用READ COMMITTED还是 REPEATABLE READ的事务隔离级别,显示的数据应该都是:
#Session A
mysql> SELECT FROM parent WHERE id = 1;
id
1
1 row in set (0.00 sec)
由于当前id=1的数据被修改了1次,因此只有一个行版本的记录。接着,在会话B中提交上次的事务:
# Session B
mysql> commit
Query OK, 0 rows affected (0.01 sec)
在会话B提交事务后,这时在会话A中再运行 SELECT * FROM parent WHERE id=1的SQL语句,在READ COMMITTED和REPEATABLE事务隔离级别下得到结果就不一样了。对于READ COMMITTED的事务隔离级别,它总是读取行的最新版本,如果行被锁定了,则读取该行版本的最新一个快照(fresh snapshot)。在上述例子中,因为会话B已经提交了事务,所以READ COMMITTED事务隔离级别下会得到如下结果:
mysql> SELECT @@tx_isolation\G;
******************1.row********************
@@tx_isolation: READ-COMMITTED
l row in set (0.00 sec)
mysql> SELECT FROM parent WHERE id =1;
Empty set (0.00 sec)
而对于 REPEATABLE READ的事务隔离级别,总是读取事务开始时的行数据。因此对于REPEATABLE READ事务隔离级别,其得到的结果如下:
mysql> SELECT @@tx_isolation\G;
******************1.row********************
@@tx_isolation:: REPEATABLE-READ
1 row in set (0.00 sec)
mysql> SELECT FROM parent WHERE id = 1;
id
1
1 row in set (0.00 sec)
下面将从时间的角度展现上述演示的示例过程,如下表所示。需要特别注意的是,对于READ COMMITTED的事务隔离级别而言,从数据库理论的角度来看,其违反了事务ACID中的I的特性,即隔离性。
时间 | 会话A | 会话B |
---|---|---|
时间 | 会话A | 会话B |
1 | BEGIN | |
2 | SELECT*FROMparentWHEREid=1 | |
3 | BEGIN | |
4 | UPDATEparentSETid=3WHEREid=1 | |
5 | SELECT*FROMparentWHEREid=1 | |
6 | COMMIT | |
7 | SELECT*FROMparentWHERE id=1; | |
8 | COMMIT |
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。