核心面试题:MVCC、间隙锁、Undo Log链、表级锁、行级锁、页级锁、共享锁、排它锁、记录锁等等

前言:

在尼恩的读者社群(50+个)中,经常遇到:MVCC、间隙锁、Undo Log链、表级锁、行级锁、页级锁、共享锁、排它锁、记录锁等等相关的面试题。

这里尼恩给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”。

也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典 PDF》,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请从下面的链接获取:码云

聊聊:MVCC 机制如何解决幻读?

在RR的隔离级别下,Innodb使用MVCC和 next-key locks(行锁和间隙锁的组合)解决幻读,

  • MVCC解决的是普通读(快照读)的幻读,
  • next-key locks解决的是当前读情况下的幻读。

所以,来看看 MVCC 机制如何普通读(快照读)的幻读?

了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:

  1. 首先获取事务自己的版本号,也就是事务 ID;
  2. 获取 ReadView 读试图;
  3. 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;
  4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;
  5. 最后返回符合规则的数据。

不同的隔离级别,或者的Read View 数量不同:

  • 在隔离级别为RC 读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次Read View。如表所示:

注意,此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生 不可重复读或者幻读的情况。

  • 当隔离级别为 RR 可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会 获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View,如下表所示:

举例说明 :READ COMMITTED 隔离级别下

READ COMMITTED :每次读取数据前都生成一个ReadView。

现在有两个 事务id 分别为 10 、 20 的事务在执行:

# Transaction 10 BEGIN;
UPDATE student SET name="李四" WHERE id=1; 
UPDATE student SET name="王五" WHERE id=1; 

# Transaction 20 BEGIN; 
# 更新了一些别的表的记录 ...

此刻,表student 中 id 为 1 的记录得到的版本链表如下所示:

说明:

事务执行过程中,只有在第一次真正修改记录时(比如使用INSERT、DELETE、UPDATE语句),才会被分配一个单独的事务id,这个事务id是递增的。所

以我们才在事务2中更新一些别的表的记录,目的是让它分配事务id。

假设现在有一个使用 READ COMMITTED 隔离级别的事务开始执行:

# 使用READ COMMITTED隔离级别的事务 
BEGIN;
# SELECT1:Transaction 10、20未提交

SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'

之后,我们把 事务id 为 10 的事务提交一下:

# Transaction 10 
BEGIN; UPDATE student SET name="李四" WHERE id=1; 
UPDATE student SET name="王五" WHERE id=1;
COMMIT;

然后再到 事务id 为 20 的事务中更新一下表 student 中 id 为 1 的记录:

# Transaction 20
BEGIN; # 更新了一些别的表的记录 ... 
UPDATE student SET name="钱七" WHERE id=1; 
UPDATE student SET name="宋八" WHERE id=1;

此刻,表student中 id 为 1 的记录的版本链就长这样:

然后再到刚才使用 READ COMMITTED 隔离级别的事务中,继续查找这个 id 为 1 的记录,如下:

# 使用READ COMMITTED隔离级别的事务 
BEGIN;

# SELECT1:Transaction 10、20均未提交
SELECT * FROM student WHERE id = 1;

# 得到的列name的值为'张三'
# SELECT2:Transaction 10提交,Transaction 20未提交 

SELECT * FROM student WHERE id = 1; 
# 得到的列name的值为'王五'

这个SELECT2的执行过程如下:

步骤1∶在执行SELECT语句时会又会单独生成一个ReadView,该ReadView的trx_ids列表的内容就是[20],up_limit_id为20,low_limit_id为21, creator_trx_id为0。

步骤2∶从版本链中挑选可见的记录,从图中看出,最新版本的列name的内容是‘宋八’,该版本的tr×_id值为20,在trx_ids列表内,所以不符合可见性要求,根据roll.pointer跳到下一个版本。

步骤3∶下一个版本的列name的内容是’钱七’,该版本的trx_id值为20,也在trx_ids列表内,所以也不符合要求,继续跳到下一个版本。

步骤4∶下一个版本的列name的内容是’王五’,该版本的trx_id值为10,小于ReadView中的up_limit.id值20,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’王五’的记录。

以此类推,如果之后事务id为20的记录也提交了,再次在使用READ CONMMITTED隔离级别的事务中查询表student中id值为1的记录时,得到的结果就是‘宋八’了,具体流程我们就不分析了。

REPEATABLE READ隔离级别下

使用 REPEATABLE READ 隔离级别的事务来说,只会在第一次执行查询语句时生成一个 ReadView ,之 后的查询就不会重复生成了。

比如,系统里有两个 事务id 分别为 10 、 20 的事务在执行:

# Transaction 10 
BEGIN;
UPDATE student SET name="李四" WHERE id=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值