事务
事务(Transction)是用户定义的一个数据库操作序列, 这些操作要么全做, 要么全不做. 一个事务是一个不可分割的工作单位.
事务结束方式
名称 | 状态 | 具体操作 |
---|---|---|
COMMIT | 事务正常结束 | 提交事务所有的操作, 事务中所有对数据库的更新写回磁盘 |
ROLLBACK | 事务异常终止 | 撤销全部的操作, 事务回滚到开始状态 |
ACID特性
特性 | 解释 |
---|---|
原子性(Atomicity) | 要么做, 要么不做, 不可拆分 |
一致性(Consistency) | 数据库中只包含事务成功提交的结果 |
隔离性(Isolation) | 一个事务的执行不受其他事务的干扰 |
持续性(Durability) | 一个事务一旦提交, 永久的修改数据库, 后续的操作对事务执行结果没有影响 |
并发
事务是并发控制的基本单位. 并发控制机制的任务是对并发操作进行正确的调度, 保证事务的ACID特性一级数据库的一致性
并发类型 | 执行状态 |
---|---|
交叉并发(Interleaved Concurrency) | 单处理机, 事务轮流执行 |
同时并发(Simultaneous Concurrency) | 多处理机, 每个处理机运行一个事务 |
交叉并发虽然没有真是的并行, 但是能减少处理机空闲时间, 提高系统效率. 同时并发是最理想的并发, 但受硬件环境限制.
不一致性
并发操作会带来不一致性, 包括如下的几种类型:
类型 | 解释 |
---|---|
丢失修改(Lost Update) | T1修改数据后, T2修改数据, 导致T1的修改覆盖 |
不可重复读(Non-repeatable Read) | T1读取数据后, T2更新数据, T1无法再现之前的读取结果 |
幻影(phantom row) | T1读取数据后, T2加入新的数据, T1再次读取发现记录数量变化 |
读”脏”数据(Dirty Read) | T1修改数据后写回, T2读取数据, T1撤回修改, 此时T2读到了错误的数据 |
幻影是不可重复读的一个子集. 读”脏”数据和不可重复读的区别在于脏数据是没有提交的数据, 而不可重复读,读取的是提交的数据.
针对上述的不一致问题, 数据库产生了相应的隔离级别, 不同的隔离级别要求如下所示:
隔离级别 | 丢失更新 | 脏读 | 不可重复读 | 幻影 |
---|---|---|---|---|
读未提交(Read Uncommitted) | 可能 | 可能 | 可能 | 可能 |
读已提交(Read Committed) | 可能 | 不可能 | 可能 | 可能 |
可重复读(Repeatable Read) | 可能 | 不可能 | 不可能 | 可能 |
可串行化(Serializable) | 不可能 | 不可能 | 不可能 | 不可能 |
读未提交显然会导致脏读, 而读已提交解决了脏读问题却不能保证可重读读. 可重复读保证了数据可重复读取, 但不能解决幻影问题. 但通过多版本并发控制(MVCC)可以在这一级别解决幻影问题. 最终, 可串行化放弃了所有并发性, 解决了所有的问题.
上述隔离级别仅仅是定义, 并没有规定实现方法, 常见的实现方法有封锁(locking), 时间戳(timestamp), 多版本并发控制(MVCC)等.
封锁
封锁技术
封锁类型 | 加锁策略 | 锁互斥关系 |
---|---|---|
排它锁(eXclusive Locks, X锁, 写锁) | 写入数据时, 加X锁 | T对A加X锁后, 只有T可以读取和修改A |
共享锁(Share Locks, S锁, 读锁) | 读取数据时, 加S锁 | T对A加S锁后, 其他事务可以对A加S锁, 但不能加X锁 |
为了保证调度是可串行化的, 目前的数据库通常采用两段锁(TwoPhase Locking, 2PL)协议来实现并行调度的可串行性, 即
- 扩展阶段: 事务可以申请任何数据项上的任何类型的锁, 但是不能释放任何锁
- 收缩阶段: 事务可以释放任何数据项上的任何类型锁, 但是不能再申请任何锁
通过这两个阶段, 才能保证加锁的有效性并且避免发生死锁.
封锁协议
协议等级 | 解释 |
---|---|
一级封锁协议 | 事务T在修改数据R之前必须对其加X锁, 并直到事务结束才释放 |
二级封锁协议 | 在一级封锁协议的基础上, 事务T在读取数据R之前必须对其加S锁, 读取完毕即可释放 |
三级封锁协议 | 在一级封锁协议的基础上, 事务T在读取数据R之前必须对其加S锁, 直到事务结束才释放 |
- 对数据加入X锁后, 其他事务只能等待当前事务完成修改后才能读写, 从而保证了当前事务的更新不会丢失
- 对数据加入S锁后, 当前事务只能等待其他事务完成更新后才能读取, 并且当前事务在读取过程中, 其他事务不能对数据进行修改, 从而保证了不会读取到脏数据
- 只到事务结束再释放, 可以保证数据在整个事务阶段不被修改, 从而保证可重复读
采用封锁协议 | 丢失更新 | 脏读 | 不可重复读 | 幻影 |
---|---|---|---|---|
不使用封锁 | 可能 | 可能 | 可能 | 可能 |
一级封锁协议 | 不可能 | 可能 | 可能 | 可能 |
二级封锁协议 | 不可能 | 不可能 | 可能 | 可能 |
三级封锁协议 | 不可能 | 不可能 | 不可能 | 可能 |
- 如果在三级封锁协议的基础上, 还对范围进行锁定, 则阻止了新数据的插入, 从而保证了不发生幻影.
解除死锁
根据操作系统中提到的死锁的四个必要条件, 即互斥条件、部分分配、不可剥夺和环路等待出发, 抛弃其中的任意一个即可解除死锁, 即
抛弃条件 | 对应方案 |
---|---|
抛弃部分分配 | 事务一次性对所有需要的数据加锁, 否则不能执行 |
抛弃不可剥夺 | 死锁时选择一个处理代价最小的事务, 将其撤销并释放资源 |
抛弃环路等待 | 所有资源进行排序, 按照一定的顺序申请资源 |
注意: 互斥条件是任务的固有属性, 不可抛弃.
选择封锁粒度原则
- 封锁粒度越大, 数据库可以封锁的单元越少, 并发度就越小, 系统开销也小
- 封锁粒度越小, 并发度较高, 但系统开销也高
系统通常可以提供多种封锁粒度, 从而供不同的事务选择, 这种方法也称为多粒度封锁(multiple granularity locking)
参考资料与扩展阅读
最后更新: 2024年04月23日 00:19
版权声明:本文为原创文章,转载请注明出处