B+树数据结构这里不做赘述。
1. B+树的悲观并发控制可以采用crabbing latch。具体方法如下:
读操作:安全获取子节点页后加锁,释放父节点锁。
写操作:(在查找路径上,对覆盖了不安全节点的范围加锁;完成平衡操作后,自底向上解锁)
- 在修改路径上任一个被遍历到的内部节点,都保存到一个栈中,这是因为split之后的平衡操作是自底向上的。同时,对于访问到的内部节点如果是unsafe的,在push到栈中前要加写锁(unsafe说明有split或merge的风险,但由于我们是自上而下检测,而split是自底向上的,所以有可能出现假阳性)。
- 处理完父节点的调整后,释放子节点锁
螃蟹锁理论上不会发生死锁,且设计简单,一定程度减小了锁的粒度,但并不是真正意义上的“节点锁”,原因是在进行平衡操作时要将路径上所有unsafe节点都上锁,最差情况下(分裂到根)锁住整个路径。但由于其是自底向上解锁,自上而下解锁的顺序,不会发生死锁。
2. B-link tree
进一步减小并发力度,针对读写并发操作时无锁带来的问题(子节点split前后因父节点不一致导致reader读到旧视图而出现幻读),B-link tree微调了B+tree的结构,主要为两点
- 内部节点若存在右兄弟,增加一个指向右兄弟的side pointer
- 每个内部节点增加一个high key字段,表示其子树中最大的值。(等于最右侧子节点的high key,层层向上传递)
可以看到,在B-link的读写流程中,保存读写路径时无需对unsafe的节点提前加锁,而是能够真正意义上实现“节点”粒度的锁,同一时刻一个reader/writer只对一个(最多三个)节点上锁。再者,当出现上述读写冲突时,为了预防幻读,若发生上述情况,reader判断target 若大于旧节点high key,说明出现split,顺着side pointer遍历右兄弟节点查找即可。
另一个特点是,b-link tree的读操作是无锁的,写操作不会阻塞读,。
可见,引入high key和link指针,在并发查找时可以作为节点是否发生变更的根据。解决了树在修改过程中发生平衡操作时的查找定位问题
缺点:由于每个内部节点增加了两个字段,因此引入了额外的维护代价,尤其是每次split后,需要更新其high key,同时该high key需要更新到父节点的key。
3. B-w tree
(WIP)