数据库基本概念之事务与并发控制
- 事务ACID
- 锁
参考资料:关系数据库工作原理简述
数据库事务具有ACID特性
「为什么引入原子性」事务执行过程中可能是不一致的状态,如果能确保事务原子性,除了执行过程中,其它时刻不一致性状态都是不可见的。通过记录redo/undo日志-通过
- 原子性(Atomicity):数据库事务包含的所有操作,要么全部执行成功,要么完全不执行。执行成功的操作写入数据库,一旦执行失败对数据库没有任何影响。
- 一致性(Consistency):数据库在事务执行前和执行后都是一致性的状态,即事务的执行是从一个一致性状态转换至另一个一致性状态「完整性约束」
- 隔离性(Isolation):数据库应该保证:事务不应该读取其他事务产生的不一致的中间数据,因为这样会导致对数据库的错误更新。即,每个事务都感觉不到其它事务在并发的执行。
- 持久性(Durability):事务执行成功,它对数据库的改变将是永久的--数据库发生故障不应该影响已经成功之行的事务。
事务调度
调度:多个事务的中操作的一个CPU执行顺序。
可串行化调度:如果存在调度S1,使得无论什么数据库初态,调度S1、S效果相同,那么S是可串行化的。
两个事务操作Ii,Ij分别属于事务Ti,Tj冲突的条件
- 操作Ii,Ij位于不同事务中,即:i!=j
- 操作面向同一个数据对象
- 两个操作中至少有一个为写
若调度S经过若干非冲突操作的交换,得到调度S1,那么S和S1是冲突等价的,若S为串行调度则称S1为冲突可串行化。
调度S与S1称为视图可串行化,当满足如下条件:
- 任意数据项Q,调度S中是Ti读取了初始值,那么调度S1中也是Ti读取了初始值
- 任意数据项Q,调度S中Ti读取了Tj写入的数据,那么S1中也必须是Ti读取了Tj写入的数据
- 任意数据项Q,调度S中Ti完成最后写,那么S1中也必须是Ti完成最后写
视图可串行化比冲突可串行化更宽松一点,任意一个视图可串行化非冲突可串行化的调度中,肯定存在盲写(即不读直接写)。级联回滚是由于脏读引起的,避免级联回滚读已提交。如果仍然是读未提交级别的话,需要满足当前事务读的数据的创造者早于。
锁
为什么要引入锁?防止多个事务并发执行时:同时对数据库元素读/写导致非可串行化发生。临界区
如果在访问数据完成立即释放锁,将会导致不一致的情况发生。例如:
T1: X(A)R(A)(A+=50)W(A)U(A)X(B)R(B)(B-=50)W(B)U(B)
T2: S(A)R(A)U(A)S(B)R(B)U(B)display(A+B)
事务执行过程中,SET A、B 100,执行结果可能是250,也可能是150,造成了数据库的不一致性。
若假定事务约定提交时候才释放锁,那么会导致死锁的产生。例如:
T1: W(A)W(B)
T2: W(B)W(A)
在事务T1执行完W(A),需要在B上加X锁才能释放在A上的锁,T2需要在A上加X锁才能释放B上的排他锁,这就导致一个死锁的发生。Ti->Tj表示Tj等待Ti持有的锁,假如等待图中出现环,即表示发生死锁,需回滚其中一个事务才能解决。
饿死: T1持有A上的S锁,T2要在A上加X锁,如果再有事务在A上加S锁,都会排在T2之前,就会导致T2总是不能加排他锁。避免的条件(T要在Q上加M锁):
- 不存在其它事务持有Q上与M冲突的锁
- 等待队列中不存在先于T的事务(先后顺序)
避免死锁的措施:T2需要在Q上加锁,如果Q的等待队列中存在T1,T1需要持有T2已持有的锁,那么把T2放在T1前边。
- 两阶段封锁(two phase lock,2PL):即在释放锁之前必须把其他的锁都加上。是实现可串行化的充分条件。
- 严格两阶段封锁协议:事务持有的排他锁在事务执行结束才释放
- 强两阶段封锁协议:事务提交之前不得释放任何锁
更新锁:两个事务先加共享锁,然后两个事务都想升级锁,导致的经典死锁问题,更新锁加锁时自己和S一样,已持有后,更排他锁一样
警示锁:用在数据库元素多层次上,IS IX S X对子元素上锁是,必须先对父元素上锁
幻读
:将insert、delete设计成对数据库表粒度的写操作
T1
BEGIN
select count(*) from table1 where id = 1;
***** <--
end;
begin;
insert into table(id) values(1);
end;
以上例子T1首先对表中所有元组加S锁,正在计算还未提交,事务T2插入了一条符号条件的,但是被漏掉了~
树形结构封锁:例子,索引文件的索引块
- 事务第一个锁可以在树的任意节点上
- 只有事务当前节点的父节点持有锁时才能获得后续的锁
- 节点可以任意时候释放锁
- 事务不能对一个解锁的事务重新上锁,即使改节点在其父节点上仍然持有锁(不能回头)
有效性确认:
- 对于所有经过有效性验证且在T开始前没有完成的U,即满足FIN(U)>START(T),检测WS(T) WS(U)是否有交叉。
- 对于已经经过有效性验证且在T有效性验证前没有完成的U,