事务

事务(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)协议来实现并行调度的可串行性, 即

  1. 扩展阶段: 事务可以申请任何数据项上的任何类型的锁, 但是不能释放任何锁
  2. 收缩阶段: 事务可以释放任何数据项上的任何类型锁, 但是不能再申请任何锁

通过这两个阶段, 才能保证加锁的有效性并且避免发生死锁.

封锁协议

协议等级 解释
一级封锁协议 事务T在修改数据R之前必须对其加X锁, 并直到事务结束才释放
二级封锁协议 在一级封锁协议的基础上, 事务T在读取数据R之前必须对其加S锁, 读取完毕即可释放
三级封锁协议 在一级封锁协议的基础上, 事务T在读取数据R之前必须对其加S锁, 直到事务结束才释放
  • 对数据加入X锁后, 其他事务只能等待当前事务完成修改后才能读写, 从而保证了当前事务的更新不会丢失
  • 对数据加入S锁后, 当前事务只能等待其他事务完成更新后才能读取, 并且当前事务在读取过程中, 其他事务不能对数据进行修改, 从而保证了不会读取到脏数据
  • 只到事务结束再释放, 可以保证数据在整个事务阶段不被修改, 从而保证可重复读
采用封锁协议 丢失更新 脏读 不可重复读 幻影
不使用封锁 可能 可能 可能 可能
一级封锁协议 不可能 可能 可能 可能
二级封锁协议 不可能 不可能 可能 可能
三级封锁协议 不可能 不可能 不可能 可能
  • 如果在三级封锁协议的基础上, 还对范围进行锁定, 则阻止了新数据的插入, 从而保证了不发生幻影.

解除死锁

根据操作系统中提到的死锁的四个必要条件, 即互斥条件、部分分配、不可剥夺和环路等待出发, 抛弃其中的任意一个即可解除死锁, 即

抛弃条件 对应方案
抛弃部分分配 事务一次性对所有需要的数据加锁, 否则不能执行
抛弃不可剥夺 死锁时选择一个处理代价最小的事务, 将其撤销并释放资源
抛弃环路等待 所有资源进行排序, 按照一定的顺序申请资源

注意: 互斥条件是任务的固有属性, 不可抛弃.

选择封锁粒度原则

  • 封锁粒度越大, 数据库可以封锁的单元越少, 并发度就越小, 系统开销也小
  • 封锁粒度越小, 并发度较高, 但系统开销也高

系统通常可以提供多种封锁粒度, 从而供不同的事务选择, 这种方法也称为多粒度封锁(multiple granularity locking)

参考资料与扩展阅读

最后更新: 2024年04月23日 00:19

版权声明:本文为原创文章,转载请注明出处

原始链接: https://lizec.top/2019/08/06/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8E%9F%E7%90%86%E4%B9%8B%E4%BA%8B%E5%8A%A1%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/