前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入讲解InnoDB解决幻读的方案:LBCC(解决当前读)+MVCC(解决快照读)

深入讲解InnoDB解决幻读的方案:LBCC(解决当前读)+MVCC(解决快照读)

原创
作者头像
小明爱吃火锅
发布2023-12-28 18:13:33
4980
发布2023-12-28 18:13:33
举报
文章被收录于专栏:小明说Java小明说Java

前言

InnoDB默认的事务隔离级别是repeatable read(后文中用简称RR),它为了解决该隔离级别下的幻读的并发问题,提出了LBCC(锁机制)和多版本并发控制(MVCC)两种方案。其中LBCC解决的是当前读情况下的幻读,MVCC解决的是普通读(快照读)的幻读。

LBCC(解决当前读

LBCC是Lock-Based Concurrent Control的简称,意思是基于锁的并发控制。在InnoDB中按锁的模式来分的话可以分为共享锁(S)、排它锁(X)和意向锁;如果按照锁的算法来分的话又分为记录锁(Record Locks)、间隙锁(Gap Locks)和临键锁(Next-key Locks)。其中临键锁就可以用来解决RR下的幻读问题。接下来按照锁的算法,介绍一下各种锁。

记录锁(Record locks)

记录锁是针对单个数据记录(行)的锁。当一个事务需要对某一记录进行修改或删除操作时,它会申请获取该记录的锁。一旦获得锁,其他事务将无法修改或删除该记录,直到当前事务完成。这种锁机制可以确保数据的完整性,防止在并发操作时对同一记录进行同时修改。

如图案例,针对id = 1 这行数据加锁:

间隙锁(Gap Locks)

间隙锁不是针对具体的记录,而是针对记录之间的空白区域(间隙)进行加锁。当事务需要对某个范围内的数据进行操作(如范围查询或更新)时,它可能会申请间隙锁以防止其他事务在加锁的间隙中插入新的记录,从而导致不可预见的结果。间隙锁有助于防止幻读(Phantom Reads)现象。

那么有种情况,比如,id>15 加锁,那么究竟锁的是哪里呢

代码语言:sql
复制
SELECT * FROM user_key_idx WHERE id>15 FOR UPDATE;

可以模拟手动开启两个事务,

其中A事务加锁查询,一个开区间id>15 加锁,事务A不提交

代码语言:sql
复制
set autocommit=0;
BEGIN;
SELECT * FROM student WHERE id > 15 FOR UPDATE;


COMMIT;

事务A没提交前,事务B 进行插入数据,由于id递增,肯定是大于15

代码语言:sql
复制
set autocommit=0;
BEGIN;
INSERT INTO `student`.`student` (`student_name`, `student_sex`) VALUES ('1234', '222')

COMMIT;

事务B出现的结果是,一直阻塞着,必须等待A事务提交才行

结论:如果间隙锁开区间,也就是最后一个是无穷,上面问题,id>15加锁,其实是从数据15开始就加锁,所以插入16以后都会阻。

临键锁(Next-key Locks) =?记录锁+间隙锁

临键锁是记录锁和间隙锁的组合。这种锁类型不仅锁定记录本身,还锁定记录之间的间隙。临键锁在读取数据时尤为有用,它可以确保在锁定范围内读取到的数据是一致的。当事务需要对一个范围的数据进行读取时,使用临键锁可以防止在操作过程中其他事务插入新的记录,从而导致读取到过时或不一致的数据。

触发条件:where范围包括表数据,比如id>5,id<11,包括了数据9,左开右闭,与间隙锁不同的是,区间不是空白,必须有数据。

可以来验证一下,为什么是左开右闭,同样也是准备两个事务,要验证这个问题,必须保证应用的字段是非唯一索引。比如使用student_num 这个无索引。

事务A

代码语言:sql
复制
set autocommit=0;
BEGIN;
SELECT * FROM student WHERE student_num > 5 and student_num < 11 FOR UPDATE;

COMMIT;

事务B

代码语言:sql
复制
set autocommit=0;
BEGIN;
SELECT * FROM student WHERE student_num = 11 FOR UPDATE;

COMMIT;

结果B事务会阻塞着

这里对 记录锁、间隙锁、临键锁 做一个总结:

InnoDB 中的行锁的实现依赖于索引,一旦某个加锁操作没有使用到索引,那么该锁就会退化为表锁

记录锁存在于包括主键索引在内的唯一索引中,锁定单条索引记录。

间隙锁存在于非唯一索引中,锁定开区间范围内的一段间隔,它是基于临键锁实现的。

临键锁存在于非唯一索引中,该类型的每条记录的索引上都存在这种锁,它是一种特殊的间隙锁,锁定一段左开右闭的索引区间。

MVCC(解决快照读

LBCC是基于锁的并发控制,因为锁的粒度过大,会导致性能的下降,因此提出了比LBCC性能更优越的方法MVCC。MVCC是Multi-Version Concurremt Control的简称,意思是基于多版本的并发控制协议,通过版本号,避免同一数据在不同事务间的竞争,只存在于InnoDB引擎下。它主要是为了提高数据库的并发读写性能,不用加锁就能让多个事务并发读写。MVCC的实现依赖于:三个隐藏字段、Undo log和Read View,其核心思想就是:只能查找事务ID小于等于当前事务ID的行;只能查找删除时间大于等于当前事务ID的行,或未删除的行。接下来让我们从源码级别来分析下MVCC。

MVCC底层原理

MVCC(多版本并发控制,类似CAS):快照读的情况,解决并发访问数据库系统时候,提高读写效率,保证数据一致性(原因加锁导致性能下降,所以使用MVCC),简单介绍一下什么是当前读和快照读

当前读:读取数据的最新版本,总是读取最新的数据??

???? ??? ??? ??? ?select.....lock in share mode 读锁(乐观锁)

??? ???? ??? ??? ?select.....for update???写锁(悲观锁)

?? ??? ??? ??? ??这时另一个是如果insert,update,delete都会被阻塞

快照读:读取的是历史版本记录

?? ??? ??? ???? ??select.....

对于快照读,在不同隔离级别读到数据不一致,原因是,事务版本的可见性,也就是MVCC的可见性算法

MVCC三部分重要组件

重要部分一

三个隐藏字段(用户不可见):每一行记录都会有几个隐藏字段

???DB_TRX_ID:创建或者最后一次修改该记录的事务id

???DB_ROW_ID:隐藏主键 select _row_id 可以查看

???DB_ROLL_PTR:回滚指针,指向undolog

重要部分二

undolog回滚日志,insert、Update,delete操作时,方便进行回滚的日志记录(多个),保存数据的历史版本状态。

重要部分三

Read View读视图:事务在进行快照读的时候产生的读视图。数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复

读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。

MVCC可见性算法

MVCC可见性算法是MySQL中用于处理多版本并发控制(MVCC)的规则。在MVCC机制中,每个事务可以看到一个一致的数据视图,即使其他事务正在修改数据。这是通过维护一个“快照”来实现的,该快照表示事务开始时的数据状态。具体看如图所示:

案例验证

如下场景,四个事务分别操作数据库,其中事务4进行修改数据,事务2再查快照读。

两个事务一起,还没有read view,第一次快照读,所以右边一个事务是修改数据,左边的事务可以看到最新修改数据(测试需要关闭MYSQL自动提交:set autocommit=0)

最终结果,RR隔离级别,在事务4,commit之后,事务2再查快照读,不会生成新的readview,读的还是上一次的,也就是事务0的一开始插入的数据。根据事务并发可见性算法,判断是不能看见另一个事务(事务2)修改的数据。

接下来来验证一直,RR隔离级别下,MVCC快照读会出现幻读的场景:

总结

InnoDB通过使用LBCC(Lock Before Commit Control)和MVCC(Multi-Version Concurrency Control)来解决幻读问题,但是两种方式都有优缺点。

LBCC(Lock Before Commit Control):在当前读(current read)中,InnoDB使用LBCC来解决幻读问题。当一个事务执行当前读时,它会在读取数据之前获取一个行锁,以确保其他事务不能在当前事务完成之前修改或删除这些数据。这样,当前事务可以看到一致的数据,而不会受到其他事务的影响,但是加锁导致性能下降

MVCC(Multi-Version Concurrency Control):在快照读(snapshot read)中,InnoDB使用MVCC来解决幻读问题。快照读是一种轻量级的读操作,它不需要获取行锁。相反,它使用一个快照来确保事务看到的数据是一致的。快照是一个包含事务ID的时间点,它表示事务开始时的数据状态。MVCC可见性算法确保每个事务看到的数据都是一致的,即使其他事务正在修改数据。但是,在RR(可重复隔离级别)幻读还是会出现。

我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • LBCC(解决当前读)
    • 记录锁(Record locks)
      • 间隙锁(Gap Locks)
        • 临键锁(Next-key Locks) =?记录锁+间隙锁
        • MVCC(解决快照读)
          • MVCC底层原理
          • MVCC可见性算法
            • 案例验证
            • 总结
            相关产品与服务
            云数据库 MySQL
            腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
            http://www.vxiaotou.com