多线程下索引的并发控制
强制所有访问数据结构的线程都使用某种协议或者某种方式。并发控制协议的概念:并发控制协议是一种当并发操作作用在一个共享对象上时DBMS用来确保correct的method。关心的正确性:逻辑正确性和物理正确性。
逻辑正确性:如果我正在访问该数据结构我希望看到的数据是我想看到的。
物理正确性:如何保护数据结构的内部表示。
segmentation falut 存储器区块错误,内存越界一般是。
| — | — | — |
Blocking OS Mutex
std::mutex m;
m.lock();
// do something
m.unlock();
Test-and-Set Spin Latch(TAS)
即使用一个spin latch 或者 TAS Spin Latch,非常高效,super fast,因为可以通过modern CPUS的单条指令来完成TAS动作,
api: std::atomic 简化:atomic_flag = atomic
std::atomic_flag latch;
// ...
while(latch.test_and_set(...)){
// come in when you don't get latch
// retry? yiled? abort?
}
Reader-Writer Latch
latch 粒度:
可能会遇到的问题:
解决方案:latch crabbing/ coupling
允许多线程访问修改B+ Tree。
基本思想:
Find:
Delete:
再到D的话,删除38,则D是安全状态,然后可以释放AB的latch
线程向下进行遍历的时候,会用stack来保存一路上所持有的latch。
Insert:
对于插入来说 B是安全的,可以释放A的latch
在D的时候不可以释放B,到最后插入的时候可以释放B and D。先B再D,提高效率
what’s the first step that all the update examples did on the B+ Tree?在Root上使用独占模式的latch或者写模式的latch进行加锁。 独占性(exclusive)
大多数的线程不需要对叶子节点进行拆分或者合并操作,所以拿read latch 而不是 write latch,在叶子节点使用write latch,判断需不需要拆分,不需要则拿read latch。如果在拆分或者merge的时候出错,abort在根节点重启,向下遍历这次获取write latch。
到达节点过程只拿read latch,执行删除的时候才去获取当前节点的write latch。
read latch会阻止任何人对这些节点进行写入和拆分操作
write latch则是阻止除了我之外的人对这些节点进行修改
通常情况下 在Page上使用悲观锁 效果比较好
这些线程都是从上到下(Top-down)遍历,所以不会产生死锁。
如果是一条线程从上到下扫描,另外一条做叶子结点扫描。
和之前的crabbing一样,获得了下一个latch才释放之前拥有的。
find 这种 read latch 不会deadlock
再来看T1 : delete 4 T2: find keys > 1
使用的是乐观锁机制latch coupling/crabbing 所以T1 T2都能拿到A的read latch。
当子节点读锁和写锁冲突的时候,T2最好不要等待T1结束,因为不知道T1要多久也不知道会不会增加形成死锁的可能,所以最好的解决方案是abort and restart. /! 可能会遇到starvation的问题。
处理overflow情况下可以使用的一种额外优化手段(BlinkTree 才加了兄弟指针,现在B+ tree基本都加兄弟指针了)
当任意时刻 某个叶子节点出现overflow情况,延迟对父节点的更新操作。只需要更新树中全局信息表中的一点内容,当有人遍历这棵树,再去更新父节点。
添加一个该树的全局表,有人用的时候再去执行这个ADD 31调整结构。拆分玩并且添加完新节点之后,紧接着要进行下次修改的线程会接受这个改变并且对内容进行更新。
notoriously 出名的