MySQL事务隔离级别的实现

1 多版本并发控制

多版本并发控制(Multi-Version Concurrency Control, MVCC)以乐观锁为理论基础,和基于锁的并发控制最大的区别和优点是:读不加锁,读写不冲突

1.1 Undo 日志

MVCC 将每一个更新的数据标记一个版本号,在更新时进行版本号的递增,插入时新建一个版本号,同时旧版本数据存储在 Undo 日志中,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来。

在这里插入图片描述

1.2 快照读与当前读

1 快照读

快照读只是针对于目标数据的版本号小于等于当前事务的版本号,也就是说读数据的时候可能读到旧数据,但是这种快照读不需要加锁。也就是说,使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销。

select * from table ...;

2 当前读

当前读是读取当前数据的最新版本,但是更新等操作会对数据加锁,所以当前读需要获取记录的行锁,存在锁争的问题。以下第一个语句需要加 S 锁,其它都需要加 X 锁。

select * from table where ? lock in share mode; # 加 S 锁
select * from table where ? for update;
insert;
update;
delete;

2 MySQL 中事务隔离级别的实现

2.1 可串行化(SERIALIZABLE)

  • 读加共享锁(S),写加排他锁(X),读写互斥;
  • 使用的是悲观锁的理论,实现简单,数据更加安全;

2.2 提交读(READ COMMITTED) 和可重复读(REPEATABLE READ)

对于读操作,由于 MVCC 的引入,分为快照读和当前读:

  • 快照读:读取的是快照中的数据,不需加锁;
  • 当前读:读取的是最新的数据,需要加锁;

RC 和 RR 都是基于 MVCC 实现的,但是读取的快照数据是不相同的:

  • RC 级别下。读取的总是最新的数据,有可能会出现一个事务中两次读到了不同的结果;
  • RR 级别下。总是读到小于等于此事务的数据,也就实现了可重复读;

2.3 未提交读(READ UNCOMMITTED)

总是读取最新的数据行,无需使用 MVCC。

3 Next-Key Locks

  1. Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现;
  2. MVCC 不能解决幻影读问题,Next-Key Locks 就是为了解决这个问题而存在的;
  3. 在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题;

3.1 Record Locks

  1. 锁定一个记录上的索引,而不是记录本身;
  2. 如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚簇索引,因此 Record Locks 依然可以使用;

3.2 Gap Locks

锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 c 中插入 15。

SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;

3.3 Next-Key Locks

它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙,是一个前开后闭区间。例如一个索引包含以下值:10, 11, 13, and 20,那么就需要锁定以下区间:

(-, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +supremum)

3.4 关于 Next-Key Locks 的几个问题

问题一:对主键索引或唯一索引会使用间隙锁吗?

不一定。视情况而定:

  • 如果 where 条件全部命中(不会出现幻读),则不会加间隙锁,只会加记录锁;
  • 如果 where 条件部分命中 / 全都不命中,则会加间隙锁;
delete from tb where id = 9
-- Table: tb(name primary key,id unique key) 
-- key 是唯一索引

根据 id=9 条件定位,此时给 id = 9 的索引加上记录锁,根据 name 值到主索引中检索获得记录,再给该记录加上记录锁。

在这里插入图片描述
问题二:间隙锁是否用在非唯一索引的当前读中?

是的。

delete from tb1 where id = 9
-- Table: tb1(name primary key,id key)
-- key 是非唯一索引

在这里插入图片描述
可以看出,在 (6,9] 、(9,11] 加了间隙锁。

问题三:间隙锁是否用在不走索引的当前读中?

是的。

delete from tb2 where id = 9
-- Table: tb2(name primary key,id)
-- 没有为 id 建立索引

在这里插入图片描述
此时对所有的间隙都上锁(功能上相当于锁表)。

总结

主键索引 / 唯一索引:

  • 如果 where 条件全部命中(不会出现幻读),则不会加间隙锁,只会加记录锁
  • 如果 where 条件部分命中 / 全都不命中,则会加间隙锁

非唯一索引:

  • 会加间隙锁

不走索引:

  • 对所有间隙都加间隙锁,相当于锁表
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hellosc01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值