MySQL高压缩引擎TokuDB 揭秘( 四 )


随后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记录着下次扫描开始位置 。

MySQL高压缩引擎TokuDB 揭秘

文章插图
 
如上图所示,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从磁盘读数据,读上来的数据也是先解压缩的 。
写优先
这里说的写优先是指并发读写数据块时,写操作优先级高,跟行级锁无关 。
MySQL高压缩引擎TokuDB 揭秘

文章插图
 
假设用户要读区间[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
其中,db,key和val不是必须的,比如checkpoint就没有这些信息 。
长度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的活跃事务列表里面 。


推荐阅读