前言:
并发控制是衡量一个数据库管理系统性能的重要指标之一。今天将从并发控制出现的问题,并发控制的主要技术来介绍相关技术。
1.并发控制出现的问题
并发操作会为数据库带来不一致性。主要分为三方面:丢失修改,不可重复读,脏读问题。
1.1丢失修改:
定义:两个事务T1和T2读入同一数据并修改,T2的提交破坏了T1提交的结果,导致T1的修改被丢失。
在本例中,事务T1和事务T2读到的数据都是16,各自对于读取到的数据减一,结果仍然是15。
1.2不可重复读
定义:事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次读取的结果。
在本例中,事务T1读取A和B的值求和得到150。事务T2对于B进行更改为200,然后事务T1再次对求和进行运算,所得结果与第一次求和结果不一样。这时事务T1无法再现前一次读取的结果。
不可重复读又分为三种:事务T2删除了部分数据,事务T2插入了部分数据,事务T2更该了部分数据。其中:插入与删除的不可重复读也成为幻影。
1.3.脏读
定义:事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被撤销,这时T1已经修改过的数据恢复原值,T2读到的数据就与数据库中的不一致。T2读到的数据就为"脏"数据,即不正确的数据。
举例:
并发控制破坏了事务的隔离性。
2.并发控制的主要技术---封锁
并发控制主要用到的技术有:封锁(Locking),时间戳(Timestamp),乐观控制法,多版本并发控制法(MVCC),我主要写一下封锁技术。
2.1封锁
定义:(其实就是加锁)事务T在对某个数据对象操作之前,先向系统发出请求,对其加锁,加锁后事务T就对该数据对象具有了控制,其他的事务不能更新此数据对象。
基本类型:排他锁(Exclusive Locks,简记X锁),共享锁(Share Locks,简记为S锁)
排他锁:又称写锁。若事务T对数据对象A(如表,记录等)加上X锁,则只允许T读取和修改A,其他事务都不能再对A加任何类型的锁,直到T释放A上的锁。
共享锁:若事务T对数据对象A加上S锁,则事务T可以读取A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。
可以这样理解一下:
当一个线程对内存进行写的时候,其他线程即不能读,也不能写。否则会出现数据不一致问题。但是当一个线程读的时候,其他线程也可以读。互不影响。
相容矩阵:
也就是说,只有读锁和读锁可以同时存在。
意向锁:
引进意向锁的原因:
意向锁的两种类型
意向共享锁(IS Lock),事物想要获得一张表中某几行的共享锁
意向排他锁(IX Lock),事物想要获得一张表中某几行的排它锁
InnoDB存储引擎中锁的兼容性
| IS | IX | S | X |
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
这里的S,X指的是对整个表进行S,X上锁,我个人理解为意向锁只对某一行或几行进行加锁,意向锁和意向锁冲突的可能性不大,所以兼容
提高某个数据对象加锁时系统的检查效率
共享意向排他锁(SIX)先对这个对象加对锁,再加意向排他锁,对于某个表加SIX锁,则表示该事务要读整个表,同时会更新个别元组
2.2封锁协议:
在运用X锁和S锁对数据对象加锁时,需要约定一些规则,这些规则称为封锁协议。
它们定义了何时申请X锁和S锁,持锁时间,何时释放锁等问题。
这就是传说中的三级封锁协议:一级封锁协议,二级封锁协议,三级封锁协议
下面我们逐一说明:
一级封锁协议:
事务T在修改数据R之前先对其加X锁,直到事务结束才释放。
解决丢失修改问题
一级封锁协议对于读锁的问题没有说明,所以无法解决不可重复读,脏读问题(用前面那几个例子仔细比划一下,就想清楚啦)
二级封锁协议:
一级封锁协议加上事务T读数据R之前必须先对其加S锁,读完之后即可释放S锁。
解决丢失修改和脏读的问题
三级封锁协议:
一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。
三级封锁协议可以解决丢失修改,脏数据和不可重复读。
综上,封锁协议级别越高,一致性程度越高。
2.3活锁与死锁问题:
活锁原因:
由于分配算法的不公平性,导致某一个事务一直处于等待的状态
活锁预防可以用较为公平的算法例如先来先服务等。
死锁原因:
T1在等待T2,T2在等待T1,两个事务永远不能结束,形成死锁。
2.4死锁的预防
一次封锁法
每个事务必须一次将所有要使用的数据加锁
降低了并发性
顺序封锁法
预先对于数据对象规定一个封锁顺序。
成本高,难以实现
2.5死锁的诊断
超时法,等待图法,银行家算法
等待图法:
事务的等待,有向图,如果出现了贿赂,则是死锁(操作系统中的资源的请求与分配问题)
操作系统资源少,用这个办法检测,但是数据库不现实
超时:<数据库管理系当前使用的办法>
系统强行用一个时间段去判断,如果在一个时间段内,事务没有处理,就认为这个事务处于死锁阶段
缺点:
误判死锁(有可能只是IO操作太多)
时间间隔不好设置
但是我们在数据库中用的最多的还是超时法。
2.6并发调度的可串行性
可串行化调度:
多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行执行这些事务的结果相同。
举个例子:
[例11.2]现在有两个事务,分别包含下列操作:
事务T1:读B;A=B+1;写回A
事务T2:读A;B=A+1;写回B
现给出对这两个事务不同的调度策略
2.7冲突可串行化调度:
冲突操作:不同的事务对同一数据的读写操作和写写操作
v一个调度Sc在保证冲突操作的次序不变的情况下,通过交换两个事务不冲突操作的次序得到另一个调度Sc’,如果Sc’是串行的,称调度Sc是冲突可串行化的调度
2.8两段锁协议
两段锁协议:
所有事务必须分为两个阶段对数据项加锁和解锁。
在对任何数据进行读,写操作之前,事务首先要获得对该数据的封锁、
在释放一个锁之后,事务不再申请和获得其他的封锁
两段锁协议与一级封锁法:
一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能执行,因此一次封锁法遵守两段锁协议。
但是两段锁协议并不是必须一次将所有使用的数据全部加锁,因此遵循两段锁协议的事务可能发生死锁
封锁粒度:封锁对象的大小称为封锁粒度(例如数据库,表,行,页,)等。
多粒度树:以树形结构来表示多粒度封锁度。
以上是我的总结,夹杂着自己的理解,希望对你有用!