随后checkpoint线程会对每个标记checkpoint_pending的脏页进行回写 。为了减少I/O期间数据块读写锁冲突,先把数据clone一份,然后对cloned数据进行回写;clone过程是持有读写锁的write锁,clone结束后释放读写锁,数据块可以继续提供读写服务 。Cloned数据块写回时,持有读写I/O的mutex锁,保证on-going的I/O至多只有一个 。
更新数据块发现是checkpoint_pending并且dirty,那么需要先把老数据写盘 。由于checkpoint是单线程,可能来不及处理这个数据块 。为此,TokuDB提供一个专门的线程池,server上下文只要把数据clone一份,然后把回写cloned数据的任务扔给线程池处理 。
Cachetable
所有缓存在内存的数据块按照首次访问(cachemiss)时间顺序组织成clocklist 。TokuDB没有维护LRU list,而是使用clocklist和count(可理解成age)来模拟数据块使用频率 。
Evictor,checkpoint和cleaner线程(参见异步合并小结)都是扫描clock_list,每个线程维护自己的head记录着下次扫描开始位置 。

文章插图
如上图所示,hash中黑色连线表示bucket链表,蓝色连线表示clocklist 。Evictor,checkpoint和cleaner的header分别是mclockhead,mcheckpointhead和mcleaner_head 。
数据块被访问,count递增(最大值15);每次evictor线程扫到数据块count递减,减到0整个数据块会被evict出去 。
TokuDB块size比较大,缺省是4M;所以按照块这个维度去做evict不是特别合理,有些partition数据比较热需要在内存多呆一会,冷的partition可以尽早释放 。
为此,TokuDB还提供partial evict功能,数据块被扫描时,如果count>0并且是clean的,就把冷partition释放掉 。Partial evict对中间数据块(包含key分布信息)做了特殊处理,把partition转成压缩格式减少内存使用,后续访问需要先解压缩再使用 。Partial evict对leaf数据块的处理是:把partition释放,后续访问需要调用pf_callback从磁盘读数据,读上来的数据也是先解压缩的 。
写优先
这里说的写优先是指并发读写数据块时,写操作优先级高,跟行级锁无关 。

文章插图
假设用户要读区间[210, 256],需要从root->leaf每层做binary search,在search之前要把数据块读到内存并且加readlock 。
如上图所示,root(height 3)和root子数据块(height 2)尝试读锁(try_readlock)成功,但是在root的第二级子数据块(height 1)尝试读锁失败,这个query会把root和root子数据块(height 2)读锁释放掉,退回到root重新尝试读锁 。
日志
TokuDB采用WAL(Write Ahead Log),每个INSERT/DELETE/CREATE INDEX/DROP INDEX操作之前会记redo log和undo log,用于崩溃恢复和事务回滚 。
TokuDB的redo log是逻辑log,每个log entry记录一个更新事件,主要包含:
- 长度1
- log command(标识操作类型)
- lsn
- timestamp
- 事务id
- crc
- db
- key
- val
- 长度2
长度1和长度2一定是相等的,记两个长度是为了方便前向(backward)和后向(forward)扫描 。
Recory过程首先前向扫描,寻找最后一个有效的checkpoint;从那个checkpoint开始后向扫描回放redo log,直至最后一个commit事务 。然后把所有活跃事务abort掉,最后做一个checkpoint把数据修改同步到磁盘上 。
TokuDB的undo日志是记录在一个单独的文件上,undo日志也是逻辑的,记录的是更新的逆操作 。独立的undo日志,避免老数据造成数据空间膨胀问题 。
事务和MVCC
相对RocksDB,TokuDB最显著的优势就是支持完整事务,支持MVCC 。
TokuDB还支持事务嵌套,可以用来实现savepoint功能,把一个大事务分割成一组小事务,小事务失败只要重试它自己就好了,不用回滚整个事务 。
ISOLATION LEVEL
TokuDB支持隔离级别:READ UNCOMMITTED, READ COMMITTED (default), REPEATABLE READ, SERIALIZABLE 。SERIALIZABLE是通过行级锁实现的;READ COMMITTED (default),和REPEATABLE READ是通过snapshot实现 。
TokuDB支持多版本,多版本数据是记录在页数据块上的 。每个leaf数据块上的 二元组,key是索引的key值(其实是拼了pk的),value是MVCC数据 。这与oracle和InnoDB不同,oracle的多版本是通过undo segment计算构造出来的 。InnoDB MVCC实现原理与oracle近似 。
事务的可见性
每个写事务开始时都会获得一个事务id(TokuDB记做txnid,InnoDB记做trxid) 。其实,事务id是一个全局递增的整数 。所有的写事务都会被加入到事务mgr的活跃事务列表里面 。
推荐阅读
- 梦见几棵大树又高又粗 梦见一颗很高的大树倒了
- 如何挑选娃娃菜
- 清明上坟时间有什么讲究 清明上坟从高祖开始拜还是辈份低开始
- 全球公认最好用的10大眼霜 十大高端眼霜
- 深圳的平安大厦高多少米 深圳最高楼是平安大厦吗
- 高考将近家长要稳字当头 高三家长会发言稿
- 营造拥有你独特气息的高雅茶席
- 汽车发动机水温高报警怎么办?值得收藏备用!
- 事业单位改革后,高校教师、医生、参公人员哪个更有发展?
- 发动机水温高的原因是?