MySQL的LBCC&MVCC底层原理

331 阅读5分钟

MySQL的LBCC&MVCC底层原理

1. MySQL的隔离级别

事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持续性( Durability ),这四个特性简称为 ACID 特性。 SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。 image.png

问题:MySQL的可重复读实现的事务隔离原理?

  1. 加锁
  2. mvcc

2.LBCC(Lock-Based Concurrent Control)

该部分讲解MySQL基于锁的并发控制

2.1 MySQL的锁分类

  • 锁的模式

image.png

  • 锁算法

image.png

下面详细讲解每一种锁

2.2 锁的模式

2.2.1 共享锁

有名读锁,对某一资源加共享锁,自身可以读取该资源,其他人也可以读该资源(可继续加共享锁),但是无法修改,想要修改就必须等所有共享锁都释放完后才能进行

  • 加锁 select * from table lock in share mode
  • 释放锁 Commit 、 Rollback

示例:

//navicat开启一个终端,运行第一句和第二句
BEGIN;
SELECT * from user where id=1 LOCK IN SHARE MODE;
ROLLBACK;

//navicat开启另外一个终端,运行第一句和第二句,这个时候会发现卡无法获取锁
BEGIN;
SELECT * from user where id=1 LOCK IN SHARE MODE;
UPDATE user set name="李四" where id=1;
ROLLBACK;

2.2.2 排它锁

对某一资源加排它锁、自身科技进行增删改查、其他人无法进行任何操作

注意:排它锁不能与其他锁并存

  • 加锁

加锁: 自动:DML语句默认都会加排它锁
手动:select * from user where id=1 for update

  • 释放锁

Commit 、 Rollback 示例:

//navicat开启一个终端,运行第一句和第二句
BEGIN;
UPDATE user set name="李四" where id=1;
ROLLBACK;

//navicat开启另外一个终端,运行第一句和第二句,这个时候会发现卡无法获取锁
BEGIN;
SELECT * from user where id=1 LOCK IN SHARE MODE;
ROLLBACK;

2.2.3 意向锁

  • 意向共享锁--Intention Shared Locks 表示事务准备个数据加共享锁之前——前提是获取此表的IS锁
  • 意向排它锁--Intention Exclusive Locks 表示事务准备个数据加排它锁之前——前提是获取此表的IX锁

注意: 均为表锁、无法手动创建,存储引擎实现。

问题:为什么加入意向锁?

举例: 事务A锁住了一行数据,事务B申请整个表的写锁,A持有的行锁是冲突的,InnoDB如何检查这个情况呢?

如果不加意向锁判断的步骤:

  1. 判断表是否已被其他事务用表锁锁表
  2. step2:判断表中的每一行是否已被行锁锁住

如果加意向锁判断的步骤:

  1. 判断表是否已被其他事务用表锁锁表
  2. 发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此事务B申请表的写锁会被阻塞

结论:引入意向锁是为了提高加锁效率,无法手动创建,必须存储引擎实现

2.3 锁的算法

上面讲解了几种类型锁,下面讲解锁的具体算法。

2.3.1 锁的区间

在讲解之前需要了解锁区间的概念。

image.png

2.3.2 记录锁

记录锁大家应该比较清楚了,对某一条记录进行加锁。

锁住id=1的记录。
select * from user where id=1 for update

image.png

2.3.3 间隙锁

听这个名字大家应该就能理解啥意思,对区间间隙进行加锁。

锁住 ( 5 , 9 ) 的记录
select * from user where id>5 and id<9 for update

image.png

示例:

//navicat开启一个终端,运行第一句和第二句,会对(59)的区间进行加锁
BEGIN;
select * from user where id>5 and id<9 for UPDATE;
commit;
ROLLBACK;

//navicat开启另外一个终端,运行第一句和第二句,这个时候无法插入到间隙中,应为(5,9)区间已经锁住。
BEGIN;
INSERT INTO user VALUES  (6,"jj");
commit;
ROLLBACK;

注意:Gap Locks 只存在可重复读级别下

2.3.4 临间锁

即为:记录锁 + 间隙锁

锁住 ( 5 , 9 ] , ( 9 , 11 ]的记录
select * from user where id>5 and id<11 for update

image.png

3.MVCC

在讲解mvcc之前大家先看一个示例:数据库5条记录,高并发的情况下同时10个事务修改记录,20个事务同时读数据,如何保证读读不阻塞、读写不阻塞、写写阻塞?

3.1 MVCC 概念

Multi-Version Concurrency Control,翻译为中文即 多版本并发控制
MVCC只在读取已提交(Read Committed)和可重复读(Repeatable Read)两个事务级别下有效

3.2 MySQL隐藏的三个字段

背景知识:在MySQL中,会默认为我们的表后面添加三个隐藏字段

  • DB_ROW_ID 每个表必须要有一个主键,没有设置的话,这个DB_ROW_ID上自动生成一个唯一值当作主键
  • DB_TRX_ID 事务ID,记录的是当前事务在做INSERT或UPDATE语句操作时的事务ID
  • DB_ROLL_PTR 回滚指针,通过它可以将不同的版本串联起来,形成版本链

3.3 ReadView

所有 未提交事务的ID数组 和 已经创建的最大事务ID 组成。

  1. 读取已提交级别:每执行一次SELECT语句就会重新生成一份ReadView。
  2. 可重复读级别:只会在第一次SELECT语句执行的时候会生成一份,后续的SELECT语句会沿用之前生成的ReadView(即使后面有更新语句的话,也会继续沿用)

3.4 版本链

版本的数据都只会存一份,并存在undo log 中,然后通过 回滚指针连接 起来。

image.png

3.5 版本比对规则

image.png

  1. DB_TRX_ID < min_id , 如果这个版本id比最小的还小,这个数据是可见。

  2. DB_TRX_ID > max_id , 这个版本是由将来启动的事务还未开始,这个数据不可见。

  3. min_id <= DB_TRX_ID <= max_id 。
    a . DB_TRX_ID 在ReadView的未提交事务数组 , 这个版本是由还未提交,这个数据不可见。

    b. DB_TRX_ID 在ReadView的不在未提交事务数组 ,这个版本是已经提交了 ,这个数据可见。

3.6 MVCC示例

在mysql的默认可重复读的隔离级别下:

  • select1 image.png

select1开启事务后,进行三次读,三次读取的readview都是同一个,根据版本比对规则,会发现读取的的是统一数据,也是验证上面的理论。

  • select2

image.png select2开始进行读,他的readview和select1的readview是不同的,然后根据版本比对规则比对,会发现数据是和select1不同的,这个也是验证上面的理论。