CMU 15-445 16-Two Phase Locking

Background

上一章的分析都是基于事务已经发生了,然后再去判断分析是否可行

但实际上是不可能这么顺利的,因为我们不知道整个事务是怎么运行的

所以很自然的想到用锁实现多个事务的并发,从而实现对数据的保护




Lock types



Latches

是微观的概念

使用在底层的数据结构(比如说B树、红黑树、B+树、hash表等)



Lock

是宏观的概念

对数据库的抽象的内容的锁

对于用户来说,是获取一条数据的lock,而真正去操作这条数据的时候,比如说操作B+树,才会去操作latches

所以说lock锁的是具体的数据表或者数据行


lock的类型:

  • S-LOCK:shared lock,共享锁,读锁
  • X-LOCK:exclusive lock,排他锁,写锁

DBMS使用lock的过程:

  • 事务需要锁
  • 锁管理器要么给锁(锁是空闲的),要么阻塞(锁给其他事务了)
  • 事务释放锁
  • 锁管理器更新锁的使用情况



Two-phase locking

2PL

把锁用在并发控制中,决定了一个事务在运行过程中如何和其他的事务进行协调

不需要知道提前知道事务做了什么

PS:2PL是针对单个事务的整体拿锁释放锁来说的

用二阶段锁的事务执行顺序,用依赖图表示是没有环的


阶段一:growing

  • 事务只能不断的加锁,不能解锁
  • lock manager在这个阶段要么给锁,要么阻塞(此时lock被其他事务获取了)

阶段二:shrinking

  • 事务在这个阶段只能解锁,不能加锁

2PL会出现级联终止的问题(即脏读

  • T1先做一部分,T2基于T1的数据再做一部分,结果T1要回滚了,导致T2读取了未提交事务的数据
  • 问题就在于T2是基于T1还未提交的一个版本进行的


Strong Strict 2PL

针对级联终止的问题,使用严格二阶段锁(SS2PL)可以解决

原来的二阶段锁,在shrinking的时候,是可以边操作边解锁的

而严格二阶段锁,是指一定程度后锁的数量会保持不变,到了最后事务commit的时候才会释放所有的锁

即事务修改的数据,一直到事务提交之前,别人都不能修改


优点:不会出现级联回滚;事务中间可能对数据又多次操作,但是不用管,直接回滚到事务之前的版本即可;被abort的txn可以通过恢复到原始数据来消除影响



2PL基本上都会遇到死锁的情况

解决办法:deadlock detection and prevention




Deadlocking detection + prevention

无论是2PL,还是SS2PL,都可能会产生死锁饥饿的情况


解决办法:

  • deadlock detection(死锁检测)
  • deadlock prevention(死锁预防)


Deadlock detection

死锁检测:DBMS在内部会维护一个锁依赖图,记录了当前并发的事务谁在等待谁的锁,图的每个结点都是一个事务

比如说,事务A指向事务B,表示事务A在等待事务B的锁

DBMS会周期性的检测锁依赖图,检查是否出现成环的情况

如果发现了环,就会选择其中某个事务进行回滚,以此来解开环,使得事务继续进行下去


这里会有一个权衡:DBMS检查锁依赖图的频率

一个考虑因素就是:应该选择哪一个事务进行回滚

  • 如果是执行了时间特别的长或者快要执行完了的事务,就尽量不要回滚它
  • 看事务执行了多少条sql语句,即查看要回滚的代价,尽量回滚sql语句做得少的事务
  • 查看事务拿了多少锁,尽量回滚加锁比较多的事务
  • 考虑多少其他的事务都因为它而回滚过(即检测该事务被回滚了多少次)

当然还有另一个考虑因素:事务的回滚程度

  • 完全回滚 completely rollback
  • 部分回滚 minimallly rollback


Deadlock prevention

死锁预防:当T1想要的lock被T2拿到的时候,那么DBMS就会挑选其中一个kill

根据时间戳给事务优先级,越早开始的事务有高优先级(older time stamp = higher priority)


Old waits young

  • 如果是老的事务(高优先级)想要加锁,发现锁被一个年轻的事务(低优先级)拿到了,那么老的事务就要等年轻的事务释放了才能拿

  • 反之,年轻的事务想要加的锁在老的事务持有,那么年轻的事务就要abort


Young waits old

  • 如果一个老的事务想要拿的锁被一个年轻的事务持有,那么老的事务就把年轻的事务的锁抢过来,并把年轻的事务abort

  • 反之,年轻的事务想要加的锁在老的事务持有,那么年轻的事务要等待老的事务


简而言之就是给事务优先级,然后针对优先级指定不同的策略

(这也是为什么可以解决死锁的原因,有点像swap时,要先拿低地址数据的lock,再拿高地址数据的lock一样,就要保持所有的拿锁都有一个规律的顺序)


被abort的事务的时间戳是多少:应该还是原来的时间戳

  • 为了防止饥饿,因为此时年轻的时间戳,早晚会变成老的时间戳

MySQL的死锁检测是第一种(Old waits young)




Lock granularitiles

锁的粒度

如果两条需要更新的事务都需要把整张表给锁住,那么并发度就会因此下降

因此DBMS需要尽可能的控制好锁的粒度和数量

根据业务需求,看看是需要更少的锁的数量,还是要需要更大粒度的锁


大粒度的锁:给整个表加锁,要先检查每个行是否要加锁

  • solve:给一个标记,加行锁的时候标记一下,表示当前加不了表锁了


Intention locks

意向锁,可以认为是一个意向标记

当有事务给表的数据行加了共享锁或排他锁,同时会给表设置一个标识,代表已经有行锁了

其他事务要想对表加表锁时,就不必逐行判断有没有行锁可能跟表锁冲突了

直接读这个标识就可以确定自己该不该加表锁


意向锁允许更高级别的节点锁定在共享(S锁)或独占(X锁)模式,无需检查所有的节点

如果一个表被加了意向锁,就代表表中的部分数据被加上了S锁或X锁


意向锁的类型

  • intention shared(IS)意向共享锁
    • 下面的行有被加S锁
    • 意味着对整个表加S锁前,需要先获取到IS锁
  • intention exclusive(IX)意向排他锁
    • 下面的行有被加X锁
    • 意味着对整个表加X锁前,需要先获取到IX锁
  • shared + intention - exclusive (SIX
    • 部分行被加了排他锁(X lock),整个表又被加了共享锁(IS)


Locking protocol

To get S or IS lock on a node, the txn must hold at least IS on parent node

  • 想要对数据加S锁或IS锁,就必须要持有数据的IS锁

To get X, IX, or SIX on a node, must hold at least IX on parent node

  • 想要对数据加X锁,IX,SIX,就必须要持有数据的IX锁


Lock escalation

当低级别的锁过多的时候,锁会自动升级为表锁,从而能够减少锁的请求和持有数量



Lock table

绝大部分锁,都是DBMS自己加的,当然有时候可以显式的给node加锁

而DBMS也提供了接口,让用户可以在语法上加锁

  • LOCK TABLE <table> IN <mode> MODE;
    select * from <table> where <> for update;    //    数据库备份的时候用到的
  • 显式告诉数据读的数据要加的不是共享锁,而是排他锁



Select…for update

用sql语句显式的给数据加上X lock

select * from <table> where <qualification> for update;



Conclusion

几乎所有的关系数据库都用了2PL,例如MySQL,Postgresql(Postgresql有些还有个ssi)

通过2PL可以放心的让事务进行并发


我对2PL的一些思考:

无论是2PL还是SS2PL,其实都存在不符合可串行化的情况

比如说T1,T2两个事务T1先执行,T2后执行,那么准确的语义应该是T2读到数据,都应该是T1执行后的数据

而如果T2的操作全部是读取数据,而T1的操作是一开始读取数据,最后的时候才修改数据

那么T1和T2并发的过程中,T2能够读取的数据有可能就是T1一开始读取的数据,即T1还没有修改过的数据

这是不符合我们事务上的逻辑顺序的(T2晚于T1),我们的要求是T2读到的数据必须是T1执行完后的

因此商用数据库并不只使用2PL这一种并发控制协议,而是多种(MVCC,TOO)并发协议一起搭配着使用


 上一篇
CMU 15-445 17-Timestamp Ordering Concurrency Control CMU 15-445 17-Timestamp Ordering Concurrency Control
Background并发控制的两个流派:悲观的2PL,乐观的OCC 上一章讲到的2PL其实是一种悲观的并发控制协议 它假设未来所有的事务都会发生竞争,所以在操作每一条SQL的时候都会提前加上锁 即在问题发生之前解决问题,阻止问题的发生
2022-11-28
下一篇 
CMU 15-445 15-Concurrency Control Theory CMU 15-445 15-Concurrency Control Theory
Motivation多个事务对于同一条数据进行修改 可能会出现竞争、更新丢失的问题 执行多条语句(事务)的时候机房发生断电,该如何处理 持久化(durability)的问题,需要恢复(recovery)解决 并发控制
2022-11-26
  目录