MySQL的LBCC&MVCC底层原理
1. MySQL的隔离级别
事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持续性( Durability ),这四个特性简称为 ACID 特性。
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
问题:MySQL的可重复读实现的事务隔离原理?
- 加锁
- mvcc
2.LBCC(Lock-Based Concurrent Control)
该部分讲解MySQL基于锁的并发控制
2.1 MySQL的锁分类
- 锁的模式
- 锁算法
下面详细讲解每一种锁
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如何检查这个情况呢?
如果不加意向锁判断的步骤:
- 判断表是否已被其他事务用表锁锁表
- step2:判断表中的每一行是否已被行锁锁住
如果加意向锁判断的步骤:
- 判断表是否已被其他事务用表锁锁表
- 发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此事务B申请表的写锁会被阻塞
结论:引入意向锁是为了提高加锁效率,无法手动创建,必须存储引擎实现
2.3 锁的算法
上面讲解了几种类型锁,下面讲解锁的具体算法。
2.3.1 锁的区间
在讲解之前需要了解锁区间的概念。
2.3.2 记录锁
记录锁大家应该比较清楚了,对某一条记录进行加锁。
锁住id=1的记录。
select * from user where id=1 for update
2.3.3 间隙锁
听这个名字大家应该就能理解啥意思,对区间间隙进行加锁。
锁住 ( 5 , 9 ) 的记录
select * from user where id>5 and id<9 for update
示例:
//navicat开启一个终端,运行第一句和第二句,会对(5,9)的区间进行加锁
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
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 组成。
- 读取已提交级别:每执行一次SELECT语句就会重新生成一份ReadView。
- 可重复读级别:只会在第一次SELECT语句执行的时候会生成一份,后续的SELECT语句会沿用之前生成的ReadView(即使后面有更新语句的话,也会继续沿用)
3.4 版本链
版本的数据都只会存一份,并存在undo log 中,然后通过 回滚指针连接 起来。
3.5 版本比对规则
DB_TRX_ID < min_id , 如果这个版本id比最小的还小,这个数据是可见。
DB_TRX_ID > max_id , 这个版本是由将来启动的事务还未开始,这个数据不可见。
min_id <= DB_TRX_ID <= max_id 。
a . DB_TRX_ID 在ReadView的未提交事务数组 , 这个版本是由还未提交,这个数据不可见。b. DB_TRX_ID 在ReadView的不在未提交事务数组 ,这个版本是已经提交了 ,这个数据可见。
3.6 MVCC示例
在mysql的默认可重复读的隔离级别下:
- select1
select1开启事务后,进行三次读,三次读取的readview都是同一个,根据版本比对规则,会发现读取的的是统一数据,也是验证上面的理论。
- select2
select2开始进行读,他的readview和select1的readview是不同的,然后根据版本比对规则比对,会发现数据是和select1不同的,这个也是验证上面的理论。