封锁协议 和 数据库的隔离级别
本文回答下面的问题:
- 数据库的隔离级别有哪些?
- 数据库的隔离级别有什么效果?或者说每个隔离级别解决的问题?
- 封锁协议有哪些?
- 数据库的隔离级别和封锁协议的关系?
数据隔离级别概念缘起
数据库的基本目标为无论什么情况下,都要保持数据的一致性,而为了达成这一目标,事务处理必须确保ACID性质,即:
- 原子性(Atomic) => 数据库恢复机制
- 一致性(Consistency) => 事务处理的目标
- 隔离性(Isolation) => 并发控制机制
- 持久性(Durability) => 数据库恢复机制
隔离级别对应的概念正是隔离性,所以这是一个与并发控制相关的概念。一个良好的隔离级别应该是每个事务在执行时感觉不到任何其他事务的影响。
数据隔离性应对的问题
显然当事务并发时会产生很多数据不一致的问题,归结起来有三类不一致性:
- 丢失修改 Lost Update
- 读脏数据 Dirty Read
- 不可重复读 Non-repeatable Read
但实际操作起来,由于隔离的粒度不同,还有一类不一致性,即:
- 幻读 Phantoms
值得提一下的是,幻读和不可重复读的概念很接近,都是两次查询(读取)操作结果不同,而不一样的是:幻读的条件更特殊,幻读指的是记录的条数变了,当记录条数不变,仅仅内容改变时不是幻读。幻读属于不可重复读的一种情况
例子:
- 某事务第一次查询某人的存款余额是100,之后其他事务修改了余额,再次读取时余额不再是100,这就是不可重复读,但不是幻读。
- 某事务查询所有存款余额大于100的人,第一次查询只有一个人,之后其他事务插入了另外一个余额大于100的记录,再次查询,变成了两个人,这就是幻读,也是不可重复读。
数据库的隔离级别定义
数据库的隔离级别分为四层,即:
- 串行化 Serializable
- 可重复读 Repeatable Read
- 读已提交 Read Committed
- 读未提交 Read Uncommitted
而这四个概念的定义是通过何时释放锁以及何时释放锁来定义的(也有用时间戳机制来描述的): 注意下列加锁操作都是要做什么操作,就加对应操作的锁,比方说读操作就加读锁,而不需要考虑接下来要进行写操作而在读的时候加写锁的情况。
1. 串行化
最高的隔离级别。对于基于锁的并发控制数据库实现:串行化要求读锁和写锁(根据所选数据获取)在事务结束的时候释放。同时当使用带有范围条件where
的SELECT
语句查询时,要求增加相应的范围锁(例如使用select * from t where val>10 and val < 15
查询后,会增加相应的范围锁,其他事务插入val
在这个范围的数据是不被允许的)。
2.可重复读 对于基于锁的并发控制数据库实现:要求读锁和写锁(根据所选数据获取)在事务结束的时候释放。但是不管理范围锁。
3.读已提交
对于基于锁的并发控制数据库实现:要写锁(根据所选数据获取)在事务结束的时候释放,读锁在select
执行结束后立即释放。这一级别同样不管理范围锁。
4.读未提交 对于这一层级,完全不加锁。
到这里我们就可以得出如下结论的表格:
其实这个表格中一些问题的避免是和封锁方法是对应的:
- 范围锁可以避免幻读,
- 读锁和写锁在事务结束时释放可以避免丢失修改,不可重复度
- 写锁在事务结束时释放,申请读锁并在之后释放可以避免读脏数据
数据库封锁协议
为了定义并发控制的解决策略,引入了串行化和可串行化的概念。为了达到可串行化调度,就有了一系列的控制方法。其中比较好的控制协议就是通过锁的控制来实现的,这就引入了一系列的封锁协议:
- 一级封锁协议
- 二级封锁协议
- 三级封锁协议
- 两阶段封锁协议
- 一次封锁协议
封锁协议是通用的协议,是编程人员编码或者设计的规范。
它们的定义如下:
一级封锁协议: 事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。
二级封锁协议:一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后方可释放S锁。
三级封锁协议:一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。
两阶段封锁协议:① 在对任何数据进行读、写操作之前,首先要申请并获得对该数据的封锁。② 在释放一个封锁之后,事务不再申请和获得其它任何封锁。
一次封锁协议: 一次将所有要使用的数据全部加锁
其中事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)
通过定义我们貌似可以发现,封锁协议和数据库隔离级别看似有如下对应关系:
- 二级封锁协议 => 读已提交隔离级别
- 三级封锁协议 => 可重复读隔离级别
但实际上并不存在对应关系
根本原因出现在封锁协议中的在数据修改前加锁,指的是Read(A)
和Write(A)
绑定在一起考虑,虽然具体的修改操作发生在Write(A)
时,但是在Read(A)
的时候就要加上写锁。而数据库的隔离级别不会提前加锁。
另外两者的主体也是不一样的,在隔离层级策略下会出现锁升级的情况(这其实也是因为数据库无法知道操作者接下来的操作是什么导致的,如果知道可能在一开始就会加上写锁保护)。
一些额外的问题
为什么封锁协议和隔离级别看着定义一样,实际效果却不同?
在封锁协议中Read(A)和Write(A)要绑定认为是一个写操作,开始就要加写锁, 对于之前提到的四种数据一致性问题,我们可以发现:
- 一级封锁协议
什么都不能保证保证不丢失修改 - 二级封锁协议可以保证不读脏数据 也保证不丢失修改
- 三级封锁协议则可以保证不读脏数据,可重复读,不丢失修改,
但是不能解决幻读
对于一二级封锁协议的反例如下:
而这个反例也是不存在的,因为封锁协议要求T1在Read(A)
的时候就要加写锁。
Part 2
此外由于存在幻读现象,那么三级封锁协议和两阶段封锁协议是否还是可串行化的?
仍然是可串行化的,幻读本质是封锁粒度的问题,不过当我们理解封锁协议加锁时,应该更好的理解封锁协议中所说的封锁操作对象的含义,随着访问对象的粒度而调整封锁对象的粒度,比如
Select
范围选择时,应该对表加锁。
Part 3
封锁协议之间的包含关系?