在关系型数据库里面,每一项操作,都是要放在一个事务中完成。自顶向下看来,事务处在顶层的位置,事务统领了查询,统领了日志,统领了存储。
按照类似于软件工程中 喷泉模型 的思路,用层层迭代的方式,来看待关系型数据库管理系统从小到大各个组件的扩张,最初解决了无语义的字节存储,然后附加上日志的底层实现,再加上事务(包含了日志的语义信息处理),就得到了一个最小的系统。之后的查询,优化等都可以在这个最小系统的基础上,不断丰富。
图1 最小系统
在simpledb这个小型的教学数据库管理系统中,主要包含了两个组件:并发管理,恢复管理。实际上,还应该有一个锁管理。只是在这里只是用一个LockTable来实现。
首先看Transaction的类图
图2 Transaction 类图
1、并发管理
每个事务都有自己的一个并发管理器,并发管理器跟踪当前事务持有的锁,在必要的时候会跟全局的锁表交换信息。可见,在simpledb的并发管理中,核心的是锁表,通过对锁表数据的操作,实现了并发管理。
> LockTable
锁表提供接口,实现的是对block的加锁、解锁,粒度是底层的数据块Block。
锁表维护的是一个<Block,int>的字典。在处理访问数据块的冲突的时候,实现的是一个忙等待机制:在每个Block上维护了一个等待队列,当事务请求一个数据块并加锁的时候,与一个已存在的锁有冲突,则将该事务加入等待队列。只是,所有的block共享一个等待队列,同时,字段中block对应的int值记录了该block上等待的事务的个数。
当blockc上最后一个锁释放后,等待队列中所有相关的事务同时释放,重新调度。之后,没有请求成功的事务,重新入队。
图3 锁表示意图
Tx1,Tx2,Tx3,Tx4请求的是xLock,等待之前的sLock释放;Tx5请求的是xLock,等待之前的xLock释放
Block上的信息通过字典来保存,事务的等待队列,通过操作系统的线程等待机制来实现,没有显示的数据结构。
simpledb支持加锁有两种:共享锁 sLock,互斥锁xLock。sLock在字典中int值不断增加来表示,xLock通过在字典中int值置-1来表示。
系统实现的是“读优先的”加锁机制,如下代码所示:
图4 加锁代码
由代码可见,在加sLock的时候,只要目前持有锁的不是xLock,便可以继续往上加锁,直接访问Block;而在加xLock的时候,只要有锁,就必须等待。如果一个xLock与sLock同时竞争,显然sLock总会获得锁,而sLock总是要等待。
为了避免“饥饿”现象的出现,在加锁的时候,增加了等待时常的控制:如果等待时间超过了阈值,事务便抛出异常,然后退出。
解锁的时候,如果是sLock,直接将字典中block对应的标记值减1,;如果是xLock,将block从字典中删除,然后唤醒所有的等待事务。
> ConcurrencyMgr
用static标记了一个全局得到LockTable。所有事务共享这一个LockTable。
额外维护了一个字典locks,<Block, string>,string值可以是S或X,标记该Block上加的是sLock还是xLock。
加锁的时候,除了调用LockTable的接口外,还将<Block, string>对存入locks中,标记S或X。注意的是,在加xLock的时候,使用的是锁升级的策略:先价sLock,然后将sLock升级为xLock。
另外,这里的加锁,没有考虑加锁的等待,细节放到了LockTable中。
图5 ConcurrencyMgr中的加锁
假设下:初始,locks为空。首先T1对block1申请了sLock,<block1, S>加入locks中;之后,T2对block1申请sLock,显然locks中已经存在了blk为key的内容,判断条件失败,不做任何的事情,但是,显然,T2加锁成功了。
如果T3要在block2上加xLock,先判断是否有xLock:“locks.ContainsKey(blk) ? locks[blk].Equals("X") : false;” ;实际上既考虑了block2上有xLock的情况,也考虑block2上有sLock的情况。
然后先加sLock,再加xLock。
1)加sLock的阶段locks中添加了字段,同时,在locktbl.sLock执行过程中,由于事先block2上没有任何的锁,他会直接获得sLock,在locktbl.sLock中唯一做的事情,就是将locktbl中的字典locks'里block2对应的value加1,0-->1。
2)锁升级:对block2加sLock后,实际上block2上没有任何的等待队列。locktbl.xLock可以直接执行。注意:判断hasOtherSLocks的条件是vlaue>1,。而之前的sLock是value为1。故hasOtherSLock条件不成立,直接执行“locks[blk] = -1;”。这样,锁升级成功。
图6 ConcurrencyMgr加锁示意图
如上所述,simpledb中的并发管理的实现原理通过加锁来实现,维护的锁表有效地管理了多个事务的并发执行。
恢复管理后文待续。