数据库笔记6-事务处理
事务
事务是数据库数据恢复和并发控制的基本单位。
事务的ACID属性
原子性(Atomicity)
保证事务是一个独立的逻辑单位,事务中的诸多操作要么全做,要么全不做。事务对于数据库来说,要么彻底完成数据的增删改,要么对数据库未做任何操作。
一致性(Consistency)
事务的执行结果必须是使数据库从一个一致性状态到另一个一致性状态。
隔离性(Isolation)
一个事务的执行不会被另一个事务干扰。DBMS可以通过加锁在并发执行的多事务间提供不同级别的分离。
持续性(Durability)
一个事务一旦提交,它对数据库中的数据改变是永久性的。
事务被破坏的两种情况:
- 多个事务并行运行,并且不同事务的操作交叉执行
- 事务运行过程被强制停止
故障类型
- 事务内部的错误,如数据库数据部不满足业务逻辑导致的错误(只剩100块,扣200块),这类错误无法执行
commit
操作,因此数据库操作只执行了一部分,需要通过UNDO
操作恢复。 - 系统故障,如CPU、操作系统、DBMS故障、断电等故障导致系统停止运转。此时系统正在执行的事务被影响,对于已经
commit
的事务,但仍在数据库缓冲区中的内容也会丢失。系统需要重新启动,对故障发生前commit
过的事务执行REDO
操作。 - 介质故障,系统故障为软故障,而介质故障为硬故障,即磁盘、磁头、瞬时强磁场干扰等,几率小破坏性更大。
- 计算机病毒,通过非法程序破坏数据库,或恶意篡改数据库数据。
事务在程序中的使用方法
登记日志文件
日志文件是用来记录事务对数据库的更新操作的文件(增删改)
日志内容:
- 事务开始标记
- 事务所有更新操作(事务标识、操作类型、操作对象、更新前值,更新后值)
- 事务结束标记
日志要求:
- 登记时间严格按并发事务执行时间的顺序
- 必须先写日志文件后写数据库
如果日志文写入了,数据未写入,在恢复时只会多做一个无效的UNDO操作。
如果先写数据,若数据写入,日志未写入,数据库则无法根据日志恢复。
恢复
数据转储
数据库管理员可定期将整个数据库复制到磁盘或其他存储介质中。
- 当数据库需要恢复时,可拿
后备副本
恢复到数据库备份时的状态。 - 数据转储费时费力,可以考虑海量动态转储、海量静态转储、增量动态转储、增量静态转储等多种方式。
- 动态转储是数据库转储前的内容+转储期间的日志
事务故障的恢复
- 反向扫描日志文件,查找该事务的更新操作
- 对日志记录进行逆操作,插入变删除、删除变插入,修改为前值
- 直到事务的开始标记
系统故障的恢复
- 正向从头扫描日志文件到故障发生处,所有有
commit
记录的事务添加到REDO-LIST
,没有commit
记录的添加到UNDO-LIST
- 对撤销队列中的事务执行
UNDO
操作 - 对重做队列中的事务执行
REDO
操作
介质故障的恢复
- 装入最新一次的数据库后备副本。(如果是动态转储的副本需要附加转储过程的日志文件结合系统故障恢复步骤恢复)
- 装入相应日志文件副本,从备份点到故障发生点所有已经提交的事务执行
REDO
操作(因为是新的磁盘,无需UNDO)
检查点
- 从系统故障恢复步骤来看,从头扫描日志文件,是费时费力的,而且若数据库在故障发生前,事务commit后结果已经写入数据库了,则该事务无需执行REDO操作。
- 因此系统可以准备一个
重新开始文件
,里面可以专门定时记录检查点
并利用检查点,在数据库恢复时快速找到真正需要重做的事务。
检查点内容
- 某一刻正在运行的所有事务清单
- 这些事务在日志文件中,最近一个日志记录地址
带检查点的恢复
- 根据重新开始文件,找到最近一个检查点,并索引到相应日志位置
- 将检查点这一刻正在运行的所有事务
ACTIVE-LIST
暂时加入到UNDO-LIST
- 从检查点开始到故障点,扫描日志文件,若遇到事务开始标记,将其加入到
UNDO-LIST
,若遇到事务提交标记(此时该事务一定在UNDO-LIST中),则从UNDO-LIST
中移除,转移到REDO-LIST
中。 - 对
UNDO-LIST
中的事务做UNDO
操作,对REDO-LIST
中的事务做REDO
操作。
并发控制
并发控制可以更好的利用CPU、I/O、通信资源。数据库系统通过事务的并发操作,实现数据库共享资源的特点。
- 数据库系统可以通过不同的并发控制策略以解决这些问题(封锁、时间戳、乐观控制法、多版本并发控制(MVCC))
- 并发操作,很容易破坏事务的隔离性和一致性,并产生以下经典问题。
丢失修改
T1 T2读入同一个数据并修改,T1先保存修改,T2后保存修改,并将T1的修改结果覆盖掉了,导致T1丢失修改。
不可重复读
T1 T2读取同一个数据,T2进行修改并保存修改,T1再次读取数据,结果与第一次不一致。
读”脏”数据
T1修改一个数据并写入磁盘,T2在T1写入后读取同一数据,T2读取后T1事务发生异常导致T1回滚,使得T2读到的数据为脏数据。
封锁
排它锁(写锁、X锁)
事务T1给对象O加上排他锁后,其他事务不能对O加任何的锁,直到T1释放锁,以实现保护数据不被其他事务影响。(但若T2事务的策略不打算给O加锁的话,尽管T1加了X锁,T2仍可以读O)
共享锁(读锁、S锁)
事务T1给对象O加了共享锁后,其他事务不能给该事务加排它锁,只能继续加共享锁。
数据库通过使用封锁功能中的X锁和S锁可以实现并发控制。而不同的加锁策略可以实现不同级别的封锁协议,以满足不同的功能。
一级封锁协议
事务如果要执行修改操作,则需要在修改前,给修改对象加上X锁,直到事务结束。
解决了丢失修改的问题
未解决不可重复和读”脏”数据的问题
二级封锁协议
在一级封锁协议的基础上,如果事务要读取数据则需先给目标加S锁,读取结束后释放锁。
解决了丢失修改和读”脏”数据的问题
未解决不可重复读的问题
三级封锁协议
在一级封锁协议的基础上,如果事务要读取数据则需先给目标加S锁,事务结束后释放锁。
解决了丢失修改、不可重复读和读”脏”数据的问题
死锁活锁
活锁:是某一个事务在申请锁资源时,由于其他事务一直插队,导致这个事务一直处于等待状态。
死锁:是两个事务执行时各自锁上一个对象,后又申请封锁对方的资源,导致两个事务互相等待。
活锁解决:先来后到
死锁解决:
- 一次性将所需资源全部上锁
- 事务超时判定死锁
- 等待图法,找到死锁环,选一个代价最小的事务终止,解除死锁,后重新执行这个事务。
串行化调度
若多个事务严格按前后循序执行,T1执行完,T2开始,则这些事务属于
串行化调度
。
- 虽然T1和T2的执行顺序可能导致结果不同,但都认为串行化调度的结果是正确的。
- 如果存在一个并发的调度,其运行结果属于某个串行化调度,则称这个并发的调度是
可串行化
的,并且定义只有一个调度是可串行化
调度,才认为这个调度是正确的。
冲突可串行化
冲突
不同事务对同一个数据的
读写
操作和写写
操作认为是两对冲突操作。
对同一数据的读读
操作不为冲突操作,同时对不同数据的操作也不为冲突操作。
读写
冲突例子:Ri(x) 和 Wj(x) :事务i读取x和事务j写入x写写
冲突例子:Wi(x) 和 Wj(x) :事务i写入x和事务j写入x
交换条件
- 冲突操作不能交换
- 同一事务内的操作不能交换
一个调度在满足交换条件的情况下,若能转变成串行化调度,则认为这个调度是冲突可串行化的
- 若调度是冲突可串行化的,那一定是可串行化的调度
- 若调度是可串行化的,则不一定是冲突可串行化的
例1:r1(A)w1(A)r2(A)w2(A)r1(B)w1(B)
r2(B)w2(B)
高亮处第一次交换:r1(A)w1(A)r2(A)r1(B)w1(B)
w2(A)r2(B)w2(B)
高亮处第二次交换:r1(A)w1(A)r1(B)w1(B)r2(A)w2(A)r2(B)w2(B)
交换完毕:事务1的操作全在事务2的前面,因此是可串行化的
两段锁(TwoPhase Locking)
两阶段锁,简称2PL。两阶段分别为
扩展阶段
和收缩阶段
,扩展阶段只能给数据上锁,收缩阶段只能释放锁,进入收缩阶段后不能回到扩展阶段。
- 若并发的所有事务满足两段锁协议,则这些事务的任何并发调度策略都是可串行化的。
- 可串行化不一定满足两段锁协议
封锁的粒度(granularity)
封锁对象的大小称为封锁粒度,封锁粒度与系统的并发度和并发控制开销密切相关。封锁对象越大,并发度越小,开销小,封锁对象小,并发度高,开销也大。
多粒度封锁
多粒度树
的根节点是整个数据库表示最大的粒度,往下粒度减小,比如数据库、数据分区、数据文件、数据记录。多粒度封锁协议
允许多粒度树中每个结点被独立地加锁。对一个结点加锁意味着这个结点的所有子结点也被加了同类型的锁。显式封锁
指直接加了锁的结点隐式封锁
指由于上级结点加了锁,导致该结点被锁- 加新锁,需要检查上级结点和下级结点是否有冲突的锁,导致效率低下,因此引入
意向锁
(intention lock),这样加锁后就不用逐个检查下一结点的显示封锁。 - 给一个结点加意向锁,表示该结点的下层结点正在被加锁,给任意结点加锁前,先给其上层结点加相应意向锁:
IS(意向共享锁)
、IX(意向排它锁)
、SIX(共享意向排他锁)
- 加锁过程自上而下,释放锁自下而上。
各个锁的相容性:
T1/T2 | S | X | IS | IX | SIX | - |
---|---|---|---|---|---|---|
S | Y | N | Y | N | N | Y |
X | N | N | N | N | N | Y |
IS | Y | N | Y | Y | Y | Y |
IX | N | N | Y | Y | N | Y |
SIX | N | N | Y | N | N | Y |
- | Y | Y | Y | Y | Y | Y |
总结
事务技术 | 效果 |
---|---|
恢复技术 | 保证原子性和持续性 |
并发控制 | 保证一致性和隔离性 |
转储方式 | 动态转储状态 | 静态转储状态 |
---|---|---|
海量转储 | 动态海量转储 | 静态海量转储 |
增量转储 | 动态增量转储 | 静态海量转储 |
协议 | 一级封锁协议 | 二级封锁协议 | 三级封锁协议 |
---|---|---|---|
事务结束释放X锁 | √ | √ | √ |
读取结束释放S锁 | - | √ | × |
事务结束释放S锁 | - | × | √ |
丢失修改 | √ | √ | √ |
不可重复读 | × | × | √ |
读”脏”数据 | × | √ | √ |
版权声明:本博客所有文章除特殊声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明出处 Marig_Weizhi的博客!