事务管理
# 事务管理
# 事务概述
# 概念
事务是 DBMS 提供的一种特殊手段 通过这一手段 应用程序将一系列的数据库操作组合在一起 作为一个整体执行 以保证数据库处于一致的状态
# 事务的特性
# 原子性 Atomicity
事务在逻辑上是数据库的基本工作单位 要么全部执行 要么全部不执行
如果一个事务内部部分未能执行成功 那么恢复时必须取消整个事务对数据库的任何影响
# 一致性 Consistency
事务执行的结果必须是使数据库从一个一致性状态转变到另一个一致性状态
事务的原子性是一致性的保证
# 隔离性 Isolation
数据库系统中多个事务可以被同时执行 但是必须保证一个事务的执行不能被其他事务干扰 即一个事务内部操作及其使用的数据对于其他事务来说是隔离的
# 持久性 Durability
一个事务一旦提交 正常完成 它对数据库的数据的改变就是永久的
# 并发控制技术
并发操作有两个明显的好处
- 提高系统的资源利用率
- 改善短事务的响应时间
# 并发可能出现的问题
# 丢失更新 Lost update
当访问数据库的两个不同事务以某种方式交替进行 就可能发生丢失更新的问题
# 读“脏”数据 Dirty Read
事务更新了某个数据项 接着由于某种原因事务又被取消了 然而所更新的项在恢复到原值之前 另一个事务读取了该项
这是由一个没有完成或没有提交的事务产生的
# 不可重复读 Non-Repeatable Read
某一个事务需要连续读取同一个数据项 但是在两次读取操作的间隔中 另外一个事务改变了该数据项的值 因此第一个事务 2 次读取到了不同的值
# 封锁 Locking
封锁是普遍采用的一种并发控制手段 封锁可避免并发操作出现的问题
# 排它锁
可读 可写
一旦对数据对象加了排它锁 Exclusive Lock
简称 X 锁 写锁
其他事务就不能对此数据对象执行任何操作 只有该事务可以执行读和修改的操作
# 共享锁
可读 不可写
一旦对数据对象加了共享锁Share Lock
简称 S 锁 读锁
其他事务不能对该对象加上 X 锁 但是可以加上自己的 S 锁
加了 S 锁的数据对象无法被修改 但是可以被读取
# 一级封锁协议
任何事务在修改数据前 必须加上 X 锁 直到事务结束
事务结束包括
commit
以及rollback
解决了 丢失更新 但是不能保证可重复读和不读脏数据
# 二级封锁协议
- 一级封锁协议
- 任何事务在读取数据前 必须加上 S 锁 读完后即可释放 S 锁
解决了 丢失更新 读脏数据 但是不保证可重复读
# 三级封锁协议
- 一级封锁协议
- 任何事务在读取数据前 必须加上 S 锁 事务结束后即可释放 S 锁
解决了 丢失更新 读脏数据 不可重复读
# 加锁请求的选择策略和活锁
当一个数据对象 被一个事务封锁的时候 其他任何事务对该数据进行封锁的请求只能进行等待
但如果有多个事务在进行等待 那么选择哪一个比较好呢?
# 活锁
事务申请对数据 R 进行封锁 但由于加锁请求选择策略的问题而导致事务长时间甚至永远处于等待的状态 这就是活锁
# 选择策略
最有效的方法就是 先来先服务的策略 按照先后次序对事务进行排序
这种方法避开了活锁的问题
也可在排队的时候加上优先级 适当的插队
# 死锁
虽然选择策略是合理的 但仍然不可能选中的封锁申请称为死锁
# 预防死锁
即破坏死锁产生的条件
一次封锁法
任何事务必须一次同时申请所有的加锁请求 若不能全部加锁 则不进行处理 处于等待状态 在执行过程中不可 再对数据申请加锁
这降低了系统的并发性
顺序封锁法
预先对所有数据对象进行一个排序 任何一个事务在执行时 必须严格按照此顺序进行加锁 若有一个未能加锁成功 则处于等待状态
能够有效的解决死锁的产生 但是很难实现
# 解决死锁
超时法
预先设定一个最大的等待时间 如果一个事务的等待时间超过了此规定时间 则认为产生了死锁
是最简单的方法 但是可能会产生误判 如果规定的时间太长 则不能有效的检测到死锁
等待图法
是一个有向图
- T
- 所有在运行中的事务
若 Ti 申请的加锁对象被 Tj 封锁 则在两者之间产生一个有向边 即 Ti 等待 Tj 产生有向边 ij
此等待解除 则删除有向边
显然的 死锁的产生和等待图中产生回路是等价的
DBMS 会周期性的检查等待图 及时发现死锁即回路
- T
一般 DBMS 发现死锁之后 会立即着手解除
一般选择一个发生死锁的事务 将其卷回 (释放其获得的锁和其他资源)
被卷回的事务必须等待一段时间后才能重新启动 以避免再次产生死锁
一般有一下几种方法来选择要卷回的事务
- 选择最迟交付的事务
- 选择已获锁最少的事务
- 选择卷回代价最小的事务
# 并发调度的可串行性
在并发执行若干事务时 这些事务交叉执行的顺序不同 最后各事务所得结果也不会相同
因此 并发执行的事务具有不可再现性 这是就需要一个判断的准则
可串行性准则 Serializability
多个事务并发执行的结果是正确的 当且仅当其结果与按某个次序串行地执行各事务所得结果相同
有多重可串行化的调度方法 其中最广泛的应用是两段封锁协议
# 两端封锁协议
指一个事务在读写任何数据执行必须先申请并获得对该数据的封锁
一旦一个事务释放了一个封锁 就不能再申请任何封锁
- 扩展阶段
- 获得锁阶段
- 逐步申请并获得各种加锁 且不释放锁
- 收缩阶段
- 释放锁阶段
- 逐步释放各种加锁 但是无加锁申请
若所有事务都遵循两端封锁协议 则所有这些事务的任何并发调度 都是可串行化的
但是可串行化调度的事务 不一定满足两断封锁协议
这与前面防止死锁的一次封锁法是不一样的
一次封锁法满足两段封锁协议
但两段封锁协议并不要求一次封锁法
因此遵守两段封锁法的事务也是可能发生死锁的
# 多粒度封锁
# 多粒度封锁的概念
封锁对象的规模称为封锁粒度 Granularity
封锁的粒度越大 则风的代价越小 但并发度也越小
比较适合的方法是提供多重封锁粒度供选择 称为 多粒度封锁 Multiple Granularity Locking
# 显示封锁和隐式封锁
显示封锁
应事务的要求直接加到某一数据对象上的封锁为显示封锁
隐式封锁
数据对象并未被加锁 但是包含它的一个大粒度数据对象称为上级节点被封锁了 它也就被隐含地封锁的了
这两种封锁的效果是相同的 系统在检查封锁的过程中 必须同时检查这两种封锁
然而回溯检查上游节点的效率比较低 因为有了新的一种方法
# 意向锁
若对某一数据对象加锁 那么必须先对包含该数据对象的所有大粒度数据对象加上意向锁
- IS 意向共享锁
Intent Share Lock
- 对一个数据对象加 S 锁 则先对所有它的祖先都必须加上 IS 锁
- IX 意向排它锁
Intent Exclusive Lock
- 对一个数据对象加 X 锁 则必须先对它的所有祖先都加上 IX 锁
- SIX 共享意向排它锁
Share Intent Exclusive Lock
- 表示 S 锁加上 IX 锁