第7章:优化 / 7.4. 优化数据库结构 / 7.4.6. MyISAM键高速缓冲
7.4.6.1. 共享键高速缓冲访问
7.4.6.2. 多键高速缓冲
7.4.6.3. 中点插入策略
7.4.6.4. 索引预加载
7.4.6.5. 键高速缓冲块大小
7.4.6.6. 重构键高速缓冲

为了使硬盘I/O最小化,MyISAM存储引擎使用一个被许多数据库管理系统使用的策略。它使用一个缓存机制将经常访问的表锁在内存中:

·         对于索引块,维护一个称之为键高速缓冲(键高速缓冲区)的特殊结构。该结构包含大量块缓存区,其中放置了最常用的索引块。

·         对于数据块,MySQL不使用特殊缓存。而使用原生的操作系统文件系统的缓存。

本节首先描述了MyISAM键高速缓冲的基本操作。然后讨论了提高 键高速缓冲性能并使你更好地控制缓存操作的最新的更改:

·         多个线程可以并行访问缓存。

·         可以设置多个键高速缓冲,并将表索引指定给具体缓存。

可以使用key_buffer_size系统变量控制 键高速缓冲的大小。如果该变量设置为零,不使用键高速缓冲。如果key_buffer_size值太小不能分配最小数量的块缓存区(8),也不使用 键高速缓冲。

如果键高速缓冲不工作,只使用操作系统提供的原生文件系统缓存区访问索引文件。(换句话说,使用与表数据块相同的策略表来访问索引块)

索引块是一个连续的访问MyISAM索引文件的单位。通常一个索引块的大小等于索引B-树节点的大小。(在硬盘上使用B-树数据结构表示索引。树底部的节点为叶子节点。叶子节点上面的节点为非叶子节点)

键高速缓冲结构中的所有块缓存区大小相同。该大小可以等于、大于或小于表索引块的大小。通常这两个值中的一个是另一个的几倍。

当必须访问表索引块中的数据时,服务器首先检查是否它可以用于键高速缓冲中的某些块缓存区。如果适用,服务器访问键高速缓冲中的数据而不是硬盘上的数据。也就是说,从缓存读取或写入缓存,而不是从硬盘读写。否则,服务器选择一个包含一个不同的表索引块的缓存块缓存区,并用需要的表索引块的拷贝替换那里的数据。一旦新的索引块位于缓存中,可以访问索引数据。

如果用于替换的块已经被修改了,块被视为“脏了”。在这种情况下,在替换前,其内容被刷新到它来自的表索引。

通常服务器遵从LRU(最近最少使用)策略:当选择一个块用于替换时,它选择最近最少使用的索引块。为了使该选择更容易, 键高速缓冲模块维护所有使用的块的专门队列(LRU)。当访问块时,它被放到队列最后。当块需要替换时,队列开头的块是最近最少使用的块,并成为第1个候选者。

7.4.6.1. 共享键高速缓冲访问

在以下条件下,线程可以同时访问键高速缓冲缓存区:

·         没有被更新的缓存区可以被多个线程访问。

·         正被更新的缓存区让需要使用它的线程等待直到更新完成。

·         多个线程可以发起请求替换缓存块,只要它们不彼此干扰(也就是说,只要它们需要不同的索引块,并且使不同的缓存块被替换)

对键高速缓冲的共享访问允许服务器大大提高吞吐量。

7.4.6.2. 多键高速缓冲

对键高速缓冲的共享访问可以提高性能但不能完全消除线程之间的竟争。它们仍然竞争对键高速缓冲缓存区的访问进行管理的控制结构。为了进一步降低 键高速缓冲访问竟争,MySQL 5.1还提供了多个键高速缓冲,允许你为不同的键高速缓冲分配不同的表索引。

有多个键高速缓冲时,当为给定的MyISAM表处理查询时,服务器必须知道使用哪个缓存。默认情况,所有MyISAM表索引被缓存到默认 键高速缓冲中。要想为具体键高速缓冲分配表索引,应使用CACHE INDEX语句(参见13.5.5.1节,“CACHE INDEX语法”)

例如,下面的语句将表t1t2t3的索引分配给名为hot_cache的 键高速缓冲:

mysql> CACHE INDEX t1, t2, t3 IN hot_cache;
+---------+--------------------+----------+----------+
| Table   | Op                 | Msg_type | Msg_text |
+---------+--------------------+----------+----------+
| test.t1 | assign_to_keycache | status   | OK       |
| test.t2 | assign_to_keycache | status   | OK       |
| test.t3 | assign_to_keycache | status   | OK       |
+---------+--------------------+----------+----------+
 

可以用SET GLOBAL参数设置语句或使用服务器启动选项设置在CACHE INDEX语句中引用的键高速缓冲的大小来创建键高速缓冲。例如:

mysql> SET GLOBAL keycache1.key_buffer_size=128*1024;

要想删除键高速缓冲,将其大小设置为零:

mysql> SET GLOBAL keycache1.key_buffer_size=0;

请注意不能删除默认键高速缓冲。删除默认键高速缓冲的尝试将被忽略:

mysql> set global key_buffer_size = 0;
 
mysql> show variables like 'key_buffer_size';
+-----------------+---------+
| Variable_name   | Value   |
+-----------------+---------+
| key_buffer_size | 8384512 |
+-----------------+---------+
 

键高速缓冲变量是结构式系统变量,有一个名和组件。对于keycache1.key_buffer_sizekeycache1是缓存变量名,key_buffer_size是缓存组件。关于引用结构式 键高速缓冲系统变量所使用的语法的描述,参见9.4.1节,“结构式系统变量”

默认情况下,表索引被分配给服务器启动时创建的主要(默认)键高速缓冲。当 键高速缓冲被删除后,所有分配给它的索引被重新分配给默认键高速缓冲。

对于一个忙的服务器,我们建议采用使用三个键高速缓冲的策略:

·         占用为所有键高速缓冲分配的空间的20%的“热”键高速缓冲。该缓存用于频繁用于搜索但没有更新的表。

·         占用为所有键高速缓冲分配的空间的20%的“冷”键高速缓冲。该缓存用于中等大小、大量修改的表,例如临时表。

·         占用键高速缓冲空间的20%的“温”键高速缓冲。使用它作为默认 键高速缓冲,默认情况被所有其它表使用。

使用3个键高速缓冲有好处的一个原因是对一个键高速缓冲结构的访问不会阻挡对其它的访问。访问分配给一个缓存的表的查询不会与访问分配给其它缓存的表的查询竞争。由于其它原因也会提高性能:

·         热缓存只用于检索查询,因此其内容决不会被修改。结果是,无论何时需要从硬盘上拉入索引块,选择用于替换的缓存块的内容不需要先刷新。

·         对于分配给热缓存的索引,如果没有查询需要索引扫描,很有可能对应索引B-树的非叶子节点的索引块仍然在缓存中。

·         当更新的节点位于缓存中并且不需要先从硬盘读入时,为临时表频繁执行的更新操作会执行得更快。如果临时表的索引的大小可以与冷键高速缓冲相比较,很可能更新的节点位于缓存中。

CACHE INDEX在一个表和 键高速缓冲之间建立一种联系,但每次服务器重启时该联系被丢失。如果你想要每次服务器重启时该联系生效,一个发办法是使用选项文件:包括配置 键高速缓冲的变量设定值,和一个init-file选项用来命名包含待执行的CACHE INDEX语句的一个文件。例如:

key_buffer_size = 4G
hot_cache.key_buffer_size = 2G
cold_cache.key_buffer_size = 2G
init_file=/path/to/data-directory/mysqld_init.sql

每次服务器启动时执行mysqld_init.sql中的语句。该文件每行应包含一个SQL语句。下面的例子分配几个表,分别对应hot_cachecold_cache

CACHE INDEX a.t1, a.t2, b.t3 IN hot_cache
CACHE INDEX a.t4, b.t5, b.t6 IN cold_cache

7.4.6.3. 中点插入策略

默认情况,键高速缓冲管理系统采用LRU策略选择要收回的键高速缓冲块,但它也支持更复杂的方法,称之为“中点插入策略”。

当使用中点插入策略时,LRU链被分为两个部分:一条热子链和一条温子链。两部分之间的划分点不固定,但 键高速缓冲管理系统关注温部分不“太短”,总是包含至少key_cache_division_limit比例的 键高速缓冲块。key_cache_division_limit是结构式 键高速缓冲变量的一个组件,因此其值是一个可以根据每个缓存进行设置的参数。

当一个索引块从表中读入键高速缓冲,它被放入温子链的末端。经过一定量的访问后(访问块),它被提升给热子链。目前,需要用来提升一个块(3)的访问次数与所有索引块的相同。

提升到热子链的块被放到子链的末端。块然后在该子链中循环。如果块在子链的开头停留足够长的时间,它被降到温链。该时间由键高速缓冲key_cache_age_threshold组件的值确定。

对于包含N个块的 键高速缓冲,阈值表示,热子链开头的没有在最后N *key_cache_age_threshold/100次访问中被访问的块将被移动到温子链开头。该块然后变为第1个挤出的候选者,因为替换的块总是来自温子链的开头。

中点插入策略允许你将更有价值的块总是在缓存中。如果你想使用简单的LRU策略,使key_cache_division_limit值保持其默认值100

若执行的查询要求索引扫描有效推出所有索引块对应有数值的高级B-树节点的缓存,中点插入策略可以帮助提高性能。要想避免,必须使用中点插入策略,而key_cache_division_limit设置为远小于100。然后在索引扫描操作过程中,有数值的经常访问的节点被保留在热子链中。

7.4.6.4. 索引预加载

如果键高速缓冲内有足够的块以容纳整个索引的块,或者至少容纳对应其非叶节点的块,则在使用前,预装含索引块的键高速缓冲很有意义。预装可以以更有效的方式将表索引块放入 键高速缓冲缓存区中:通过顺序地从硬盘读取索引块。

不进行预装,块仍然根据查询需要放入键高速缓冲中。尽管块将仍然在缓存中(因为有足够的缓存区保存它们),它们以随机方式从硬盘上索取,而不是以顺序方式。

要想将索引预装到缓存中,使用LOAD INDEX INTO CACHE语句。例如,下面的语句可以预装表t1t2索引的节点(索引块)

mysql> LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES;
+---------+--------------+----------+----------+
| Table   | Op           | Msg_type | Msg_text |
+---------+--------------+----------+----------+
| test.t1 | preload_keys | status   | OK       |
| test.t2 | preload_keys | status   | OK       |
+---------+--------------+----------+----------+

IGNORE LEAVES修改器只允许预装索引非叶节点所用的块。这样,上述的语句预装t1中的所有索引块,但只预装t2中的非叶节点对应的块。

如果已经使用CACHE INDEX语句为一个索引分配了一个键高速缓冲,预装可以将索引块放入该缓存。否则,索引被装入默认键高速缓冲。

7.4.6.5. 键高速缓冲块大小

可以使用key_cache_block_size变量为具体的 键高速缓冲指定块缓存区的大小。这样允许为索引文件调节I/O操作的性能。

当读缓存区的大小等于原生操作系统I/O缓存区的大小时,可以获得I/O操作的最佳性能。但是将关键字节点的大小设置为等于I/O缓存区的大小并不总是能保证最佳整体性能。当读取大的叶节点时,服务器读入大量的不需要的数据,结果防止读入其它叶子的节点。

目前,你不能控制表内索引块的大小。该大小由服务器在创建.MYI索引文件时设置,取决于表定义中索引的关键字大小。在大多数情况下,它被设置为与I/O缓存区大小相等。

7.4.6.6. 重构键高速缓冲

键高速缓冲可以通过更新其参数值随时重新构建。例如:

mysql> SET GLOBAL cold_cachekey_buffer_size=4*1024*1024

如果你为key_buffer_sizekey_cache_block_size键高速缓冲组件分配的值与组件当前的值不同,服务器将毁掉缓存的旧结构并根据新值创建一个新的。如果缓存包含任何脏的块,服务器在销毁前将它们保存到硬盘上并重新创建缓存。如果你设置其它 键高速缓冲参数,则不会发生重新构建。

当重新构建键高速缓冲时,服务器首先将任何脏缓存区的内容刷新到硬盘上。之后,缓存内容不再需要。然而,重新构建并不阻塞需要使用分配给缓存的索引的查询。相反,服务器使用原生文件系统缓存直接访问表索引。文件系统缓存不如使用 键高速缓冲有效,因此尽管查询可以执行,但速度会减慢。缓存被重新构建后,它又可以缓存分配给它的索引了,并且索引不再使用文件系统缓存。