介绍数据库的并发控制原理。

隔离是事务ACID四大特性之一。在并发环境下,不同事务操纵相同的数据时,每个事务都有各自完整的数据空间。如果不对数据库进行并发控制,或产生异常行为。一些异常行为可以概括为四类。不同的数据库引擎实现不同的事务隔离级别来控制锁的粒度。为了均衡并发效率和异常控制,表中SQL规范中,定义了四个事务隔离级别。

主要有:

  • 封锁(locking)
  • 时间戳(timestamp)
  • 乐观控制法(optimistic scheduler)
  • 多版本并发控制(multi-version concurrency control,MVCC)

加下来谈谈它们的实现和原理。注意不同数据库在并发控制采用的原理不同。先谈谈没有并发控制下的异常情况。

没有并发控制下的异常情况

  • 脏读 dirty read
    当一个事务允许另外一个事务修改但没有COMMIT的数据时,就可能发生脏读。具体过程:事务T1修改数据并写回磁盘,事务T2读取同一数据后,由于T1因某些原因撤销,T1修改的数据恢复到原来的数据,这时T2读到的数据和数据库中的数据不一致。
  • 不可重复读 nonrepeatable read
    事务T1读取数据后,事务T2执行更新操作(但没有COMMIT)使得T1无法再现前一次读取结果。具体包括三种情况。
    • (1)事务T2修改数据:数据不一致
    • (2)事务T2删除部分数据:却小数据
    • (3)事务T2插入新数据:多了数据
  • 丢失修改 lost update
    两个事务T1、T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的修改被丢失。
  • 幻读 phantom read
    幻读就是不可重复读的后两种情况。

上述四种产生数据不一致的情况中,幻读是不可重复读的特例。造成数据不一致的主要原因是并发操作破坏了事务的隔离性。为了更好理解这一点,可以类比到操作系统进程、线程的并发上。

并发控制机制就是要用正确的方式调度并发操作,是一个用户事务执行的操作不受其他事务的干扰,从而避免数据的不一致。实现并发控制主要有封锁(locking)时间戳(timestamp)乐观控制法(optimistic scheduler)多版本并发控制(multi-version concurrency control,MVCC)

封锁

封锁是并发控制中的一个非常重要的技术。所谓封锁就是事务T在对某个数据对象如表、记录进行操作前,先向操作系统发起请求,对其加锁。加锁后,事务T对该数据有了一定的控制,在事务T释放它的锁前,其他事务不能更新此数据。基本的封锁类型用两种:排他锁(exclusive locks,间称X锁)和共享锁(share locks,简称S锁)。它们的对比如下。

  • 排他锁
    若事务T对数据对象A加上X锁,则允许T读取和修改A,其他事务不能对A加任何类型的锁,知道T释放A上的锁为止。这样保证其他事务不能在T释放A的锁之前读取和修改数据对象A。

  • 共享锁
    若事务T对数据对象A加上S锁,则事务T可以读取A但不能修改A,其他事务只能再对A加上S锁,而不能加上X锁,知道T释放A上的S锁为止。这样就能保证其他事务可以读取A,但不能在T释放A的锁前对A做任何修改。

名称 锁的影响范围 别名
共享锁 其他连接可以读取数据但不能修改数据 读锁
排他锁 其他连接无法读取数据,也不能修改数据 写锁

封锁协议

封锁协议约定了在运用X所和S锁着两种基本锁时的规则:如何申请X锁和S锁、持锁时间、何时释放。这里总结三级封锁的基本原理。对并发操作的不正确调度可能会带来丢失修改、不可重复读和读“脏”数据等不一致问题。三级封锁协议分别在不同程度上解决这些问题。

  • 一级封锁协议
    事务T在修改数据R之前先对其加X锁,直到事务结束才释放。

  • 二级封锁协议
    在一级封锁协议基础上增加事务T在读取数据R之前必须先对其加S锁,读取完后即可释放S锁。

  • 三级封锁协议
    在一级封锁协议基础上增加事务T在读取数据R之前必须先对其加S锁,知道事务结束才释放。

死锁和活锁

  • 活锁
    和操作系统一样封锁也会引起活锁和死锁问题。事务T请求封锁R,但由于其他事务一直占用封锁资源,导致事务T一直在等待,这就是活锁。解决活锁的方法是先来先服务。当多个事务请求封锁同一资源是,封锁子系统会根据请求封锁的先后顺序进行排队,根据队列批准请求封锁。

  • 死锁
    这里的死锁和操作系统的死锁是一致的。解决死锁也可以采取操作系统中处理死锁的方案。死锁的最简单情况,事务T1等待事务T2,而事务T2也在等待事务T1,T1和T2两个事务永远不能结束。

  • 乐观锁
    参考乐观控制法

并发调度的可串行性

  • 定义可串行化调度
    多个事务的并发执行时正确的,当且仅当其结果与按某一次序串行地执行这些事务时的结果相同,这种调度策略称为可串行化(serializable)调度。可串行性是并发事务正确调度的准则。

两段锁协议

两段锁协议是指所有事务必须分两个阶段对数据项加锁和解锁。

  • 在对任何数据进行读、写操作之前,首先要申请并获取对该数据的封锁

  • 在释放一个封锁之后,事务不再申请和获取任何其他封锁。

封锁的粒度

封锁对象的大小称为封锁粒度(granularity)。封锁的对象可以是了逻辑单元和物理单元。封锁的粒度与系统的并发读和并发控制的开销密切相关。

多版本并发控制 MVCC

其他并发控制机制

  • 乐观控制法

  • 时间戳

转载请包括本文地址:https://allenwind.github.io/blog/4292
更多文章请参考:https://allenwind.github.io/blog/archives/