我们开发自己的存储引擎页面缓存替换策略的过程中主要是参考了InnoDB与LRU-K算法。InnoDB缓存替换策略使用分代与LRU相结合的方式。分为old和young两个分代,系统维护old分代占总buffer大小的3/8左右。当一个页面第一次被访问时,是加入到old分代的lru头。并不是每次访问一个页面时就将这个页提到lru的头部,而是在这个页面在LRU中的位置调整后,是否有很多页面已经被替换出去,若有,则将这个页面移到LRU头。系统在每个页记录调整该页在lru中位置当前系统总共替换了多少个数据页,比较页中记录的这个计数与系统全局页替换计数,就可以发现上次调整这个页在lru链表中位置后,系统中又有多少个页被替换,若比较多,则再次调整该页在lru中的位置。由于第一次访问的页被加入到old分代中,因此一次表扫描不会导致young分代中的页被替换出去。
总的来说InnoDB的缓存替换策略非常简单,性能开销很低,但不知道其效果如何,因此今天进行了测试。测试时的表每条记录包含一个INT和两个CHAR(200)类型的字段,共20万条记录,其访问概率使用screw为1.2的Zip-f分布生成,这样访问概率最高的10%的记录的总访问概率为95%。表大小约占7000个页面,测试时设置InnoDB可用于存储数据的缓存页为730(InnoDB缓存大小必须设置为64个页的整数倍,无法精确控制到700个),约为数据量的10%。这时,理论上最优的缓存失配率应为5%。
使用10个线程进行测试,每个进行100000次随机操作,每个操作根据概率随机访问一条记录。测试之前先进行了一次全表扫描来预热。
测试之前各类读操作统计如下
| Innodb_buffer_pool_reads | 77 | | Innodb_data_reads | 7004 | | Innodb_pages_read | 6994 |
测试之后各类读操作统计如下
| Innodb_buffer_pool_reads | 72055 | | Innodb_data_reads | 95197 | | Innodb_pages_read | 95187 |
如果按Innodb_buffer_pool_reads计算,即不考虑预读时,测试期间读取的页面数为72000左右,失配率为7.2%。如果按Innodb_pages_read计算,即考虑预读时,测试期间读取的页面数为88200左右,失配率为8.8%。如理想的5%相比两者都还是有一定距离的。若能优化到理想状态,大致可以减少40%的IO。
同样的条件对比测试了一下,我们的存储引擎测试期间产生的读操作为62100次,稳定时的失配率为6.1%左右。目前还不清楚为什么我们的存储引擎为什么表现会比InnoDB要好,因为两者的策略是类似的。可能是因为InnoDB中的预读机制在这类纯随机的负载下反而会影响到缓存的命中率。由于Innodb_pages_read明显的超过Innodb_buffer_pool_reads的增长,可以看出测试过程中InnoDB也在不定的进行预读,有可能预读进来无用的页面,导致有用的页面被替换出去。我们的存储引擎的预读检测机制有所不用,在测试期间没有发生预读。