北川广海の梦

北川广海の梦

snapshot isolation隔离级别

2023-11-28

Snapshot Isolation

一般来说,我们能从许多教科书与网络上的资料学习到,数据库的隔离级别分为四种:

  • RU 读未提交,可以读取到其他事务未提交的更改,会导致脏读

  • RC 读已提交,只能读取其他事务已提交的更改,但是在两次读取间隔中,其他事务由提交,会导致两次读取不一致

  • RR 可重复读,在一个事务中,多次读取的结果始终是一致的,但是对于新插入的数据是无法感知的

  • Serializable 串行化执行,完全排除并发导致的问题

但是在许多其他数据库种,还支持一种 snapshot isolation 语义的隔离级别

这个隔离顾名思义,是基于快照的方式,每一个事务,都有其对应的快照,快照是不会被修改的,从而使得事务之间的修改相互隔离。但是在事务提交的时候,如果某个事务的更改,与其他已提交的事务冲突了,那么这个事务会被检测到冲突,从而不会被提交。

实现 snapshot isolation 的常见做法,是基于 MVCC,这样可以降低产生一个快照的开销。

事务的读取都是能保证成功的,并且由于都是读的快照,所以自然不存在 脏读、不可重复读、幻读等问题。

但是 snapshot 在读后写时,可能产生问题:

假如系统中 存在 A 、B 两个 数值,我们的系统要求 满足 A+B 始终大于 20

某一时刻,两者的初始值,分别为 12 和 10

事务1:读取 A 的值 12,然后将 A -1,使得 A+B =21,满足条件,提交事务

事务2:读取 B 的值 10,然后将 B -1, 使得 A+B = 21,满足条件,提交事务

1和2两个事务都能成功提交,但是最终状态,A 变成了 11,B 变成了 9,不再满足 A+B 大于20的条件了。

MySQL 事务的特点

MySQL 支持上述的四种常见的隔离级别,并且在 RR 下,MySQL 通过 Gap Lock(间隙锁),加锁后无法插入数据,一定程度解决了幻读的问题。

并且值得强调的是 MySQL 在 RR下,基于 MVCC,所有的 select 语句,都是基于快照的,这种读取,我们称之为 ”快照读“,即使多次快照读,结果也都是一样的。快照读是为了避免加锁,提高性能

而对于需要写入时的”读“,例如 UPDATE SET xx=xx WHERE xx = xx 中的 where,则是”当前读“,读取和修改的都是当前的数据,是需要加锁的。

总体来说,在 RR下,mysql 基于 MVCC ,提高了 select 等场景的性能,避免了加锁,在需要写入的时候,需要设置排他锁,避免脏读,不可重复读。并且增加了 gap lock,一定程度上解决了幻读的问题。

BadgerDB 事务

badgerDB 提供的事务语义,是 snapshot isolation,并且对其做了一定程度优化。

badgerDB 会为每一个事务分配一个 start ts,事务只能读取到比这个 start ts 小的 key 版本,这相当于提供了一个快照。

一个事务修改的所有数据的 ts 都是一样的,在 commit 的时候由 oracle (内存中的一个数据结构)分配。

同时 oracle 会记录最近提交的事务修改过的 key,当一个事务要 commit 的时候,会检查在这个事务的执行过程中读过的 key 是否有被其他并发事务修改过,如果有则属于读写冲突,需要把当前事务 abort 掉,并返回一个 冲突错误,需要重试。