我理解的myisam引擎之五 MyISAM key cache实现细节

key cache block的访问

1,可以以共享读的方式访问;

2,一个block同时只能由一个session进行update;

3,如果需要从磁盘读入一个新的block,而导致要替换cache中的一个block,那么如果被替换的block是脏的,需要先flush。

另外,key_buffer_size设置不能太小,至少要能装下8个block。否则不使用buffer,仍然使用文件系统缓存。试试看key_cache浪费的空间有多少?key_buffer_size - key_cache_block_size*( Key_blocks_unused + Key_blocks_used )

key buffer的内存管理

key buffer有两种内存管理方式,LRU和Midpoint Insertion Strategy。具体使用哪种,是根据key_cache_division_limit的设置不同决定的

我们先看较简单的LRU管理方式:当key_cache_division_limit设置为100时,整个buffer就是一个LRU链表。上端是lRU的end,下端为LRU的begining。一个block从end端进入,从begining端被挤出。在进入的时候 block位于end端,这时候是最近访问的块,最终block不再被访问,会慢慢挪到begining端,并从这里被挤出去。不要问具体细节,问就是不知道。官方文档就说了寥寥数句,并没有透露过多细节。如果要知道更详细的话,需要去翻mysql源码。

我理解的myisam引擎之五 MyISAM key cache实现细节

下面再看看Midpoint Insertion Strategy方式:

我理解的myisam引擎之五 MyISAM key cache实现细节

整个buffer被分为hot 和warm两个子列表。key_cache_division_limit设置了warm子列表应该占整个buffer的百分比。上半部分为hot 子列表,下半部分为warm子列表。一个块从磁盘读取出来,首先从warm子列表的end端进入。经过三次命中后,挪到hot子列表的end端。此后,block在hot子列表中循环。随着block在hot中的访问热度降低,慢慢挪到hot子列表的begining端,再经过足够长的时间没有访问命中,会降级到warm子列表中。这个时间由key_cache_age_threshold设置。key_cache_age_threshold的使用为:假设一个buffer中包括N个block,那么在最近的N * key_cache_age_threshold / 100次命中,block都没有被访问过,那么block被降级到warm 子列表的begining端。

原文是这样描述的:

When an index block is read from a table into the key cache, it is placed at the end of the warm sublist. After a certain number of hits (accesses of the block), it is promoted to the hot sublist. At present, the number of hits required to promote a block (3) is the same for all index blocks.

A block promoted into the hot sublist is placed at the end of the list. The block then circulates within this sublist. If the block stays at the beginning of the sublist for a long enough time, it is demoted to the warm sublist. This time is determined by the value of the key_cache_age_threshold component of the key cache.

The threshold value prescribes that, for a key cache containing N blocks, the block at the beginning of the hot sublist not accessed within the last N * key_cache_age_threshold / 100 hits is to be moved to the beginning of the warm sublist. It then becomes the first candidate for eviction, because blocks for replacement always are taken from the beginning of the warm sublist.

这里面有诸多没有解释清楚的地方,我来试着按照我认为合理的方式做下推测:

1,hot 子列表的end端与begining端,哪个是相对热端?个人为end端是相对热端。按照通常的查询逻辑,一个块第一次从磁盘读取到内存中,那么在接下来的数次查询中,这个块极有可能被再次访问(仅针对大部分情况,当然对于select count这种需要索引全扫描的,不包括在内)。因此块会首先读到热端。

2,hot子列表内部是如何循环?个人认为hot和warm两个子列表内部也是使用lru算法进行管理。相当于是一个大的lru列表被key_cache_division_limit分割成了两个独立的lru。end作为相对热端,begining作为相对冷端,最近最多访问的会往end端聚集,不经常访问的会往begining端流动。

3,从hot降级到warm,是直接降级到warm的end还是begining端?个人认为是begining端。在hot中已经极少访问的,在warm中被访问的概率也比较小。

4,Midpoint Insertion Strategy出现的意义何在?个人认为最大的目的应该是避免select count这种突发性的读取大量block,但是后续再次命中的概率极低的block,将lru中访问频次高的高价值block,冲出cache。一旦出现这种大量一次性访问的block,顶多也是把warm中的block冲掉,而高价值的block基本又都处在hot列表中,不会受到影响。oracle针对这种情况的处理是,通过参数控制让这种大量一次性访问的block不被读取到共享池中,而是直接读到进程的pga中。

还有很多值得深挖的地方:

key_cache_division_limit设置为多少合适?怎么观察?

key_buffer_size - key_cache_block_size*( Key_blocks_unused + Key_blocks_used )  在我的实例上,这个值相当大,基本上占到了40%的buffer内存。那么这么多内存浪费到哪里了?我是不相信管理结构可以占到总体空间一小半的。

索引预加载

LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES;

大量的index block通过serial的磁盘访问方式被读取到内存。因为磁盘io是serial的,整体会快。仅此而已。IGNORE LEAVES可以忽略索引页块,只读索引跟块和枝块。

key buffer相关参数

key_buffer_size和key_cache_block_size可动态设置,一旦设置与之前的值不同,当前的buffer会销毁重建。已存在的脏块刷新到磁盘。在此期间的索引块访问通过文件系统缓存的方式进行。

多内存池

可以通过“变量.组件”的结构化变量形式指定多个key cache buffer内存池。

例如:

SET GLOBAL keycache1.key_buffer_size=128*1024;
SET GLOBAL keycache1.key_buffer_size=0;

结构化变量只能通过select @@global.name.component的方式查看变量值。

通过cache index语句,将特定索引load到指定内存池:

CACHE INDEX db1.t1, db1.t2, db2.t3 IN hot_cache;

cache index的效果会在mysql重启后消失,可以通过init_file的配置文件参数指定一个脚本,在脚本中load。脚本会在每次mysql启动的时候执行。

init_file=/path/to/data-directory/mysqld_init.sql