• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 缓冲和缓存

    为了最小化磁盘 I / O,MyISAM存储引擎采用了许多数据库管理系统所使用的策略。它使用缓存机制将最常访问的表块保留在内存中:

    • 对于索引块,维护了一种称为键缓存(或键缓冲区)的特殊结构。该结构包含许多块缓冲区,这些块缓冲区放置了最常用的索引块。
    • 对于数据块,MySQL不使用特殊的缓存。相反,它依赖于本机操作系统文件系统缓存。

    本节首先介绍MyISAM密钥缓存的基本操作。然后讨论了可提高关键高速缓存性能并使您更好地控制高速缓存操作的功能:

    • 多个会话可以同时访问缓存。
    • 您可以设置多个键高速缓存,并将表索引分配给特定的高速缓存。

    要控制键高速缓存的大小,请使用key_buffer_size系统变量。如果此变量设置为零,则不使用任何键缓存。如果该key_buffer_size值太小而无法分配最小数量的块缓冲区(8),则也不使用键高速缓存。

    当键高速缓存不可用时,仅使用操作系统提供的本机文件系统缓冲来访问索引文件。(换句话说,使用与表数据块相同的策略访问表索引块。)

    索引块是对MyISAM索引文件的连续访问单元。通常,索引块的大小等于索引B树的节点的大小。(索引在磁盘上使用B树数据结构表示。树底部的节点是叶节点。叶节点上方的节点是非叶节点。)

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

    当必须访问任何表索引块中的数据时,服务器首先检查键高速缓存的某些块缓冲区中是否可用。如果是这样,服务器将访问密钥缓存中的数据,而不是磁盘上的数据。也就是说,它从缓存中读取或写入缓存,而不是从磁盘读取或写入磁盘。否则,服务器将选择一个包含另一个表索引块的缓存块缓冲区,并用所需表索引块的副本替换那里的数据。一旦新的索引块位于缓存中,就可以访问索引数据。

    如果碰巧选择了要替换的块,则该块被视为“脏”。”在这种情况下,之前被取代时,其内容被刷新到它所来自的表索引。

    通常,服务器遵循LRU(最近最少使用)策略:选择要替换的块时,它会选择最近最少使用的索引块。为了使选择更加容易,密钥缓存模块将所有使用的块维护在按使用时间排序的特殊列表(LRU链)中。当访问一个块时,它是最近使用的块,并位于列表的末尾。当需要替换块时,列表开头的块是最近最少使用的块,成为驱逐的第一个候选者。

    InnoDB存储引擎还采用LRU算法来管理它的缓冲池。请参见“InnoDB缓冲池”。

    MyISAM密钥缓存

    为了最小化磁盘I / O,MyISAM存储引擎采用了许多数据库管理系统所使用的策略。它使用缓存机制将最常访问的表块保留在内存中:

    • 对于索引块,维护了一种称为键缓存(或键缓冲区)的特殊结构。该结构包含许多块缓冲区,这些块缓冲区放置了最常用的索引块。
    • 对于数据块,MySQL不使用特殊的缓存。相反,它依赖于本机操作系统文件系统缓存。

    本节首先介绍MyISAM密钥缓存的基本操作。然后讨论了可提高关键高速缓存性能并使您更好地控制高速缓存操作的功能:

    • 多个会话可以同时访问缓存。
    • 您可以设置多个键高速缓存,并将表索引分配给特定的高速缓存。

    要控制键高速缓存的大小,请使用key_buffer_size系统变量。如果此变量设置为零,则不使用任何键缓存。如果该key_buffer_size值太小而无法分配最小数量的块缓冲区(8),则也不使用键高速缓存。

    当键高速缓存不可用时,仅使用操作系统提供的本机文件系统缓冲来访问索引文件。(换句话说,使用与表数据块相同的策略访问表索引块。)

    索引块是对MyISAM索引文件的连续访问单元。通常,索引块的大小等于索引B树的节点的大小。(索引在磁盘上使用B树数据结构表示。树底部的节点是叶节点。叶节点上方的节点是非叶节点。)

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

    当必须访问任何表索引块中的数据时,服务器首先检查键高速缓存的某些块缓冲区中是否可用。如果是这样,服务器将访问密钥缓存中的数据,而不是磁盘上的数据。也就是说,它从缓存中读取或写入缓存,而不是从磁盘读取或写入磁盘。否则,服务器将选择一个包含另一个表索引块的缓存块缓冲区,并用所需表索引块的副本替换那里的数据。一旦新的索引块位于缓存中,就可以访问索引数据。

    如果碰巧选择了要替换的块,则该块被视为“脏”。”在这种情况下,之前被取代时,其内容被刷新到它所来自的表索引。

    通常,服务器遵循LRU(最近最少使用)策略:选择要替换的块时,它会选择最近最少使用的索引块。为了使选择更加容易,密钥缓存模块将所有使用的块维护在按使用时间排序的特殊列表(LRU链)中。当访问一个块时,它是最近使用的块,并位于列表的末尾。当需要替换块时,列表开头的块是最近最少使用的块,成为驱逐的第一个候选者。

    InnoDB存储引擎还采用LRU算法来管理它的缓冲池。请参见“InnoDB缓冲池”。

    共享密钥缓存访问

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

    • 多个会话可以访问未更新的缓冲区。
    • 正在更新的缓冲区会导致需要使用它的会话等待更新完成。
    • 多个会话可以发起导致缓存块替换的请求,只要它们彼此不干扰(也就是说,只要它们需要不同的索引块,从而导致替换不同的缓存块)即可。

    对密钥缓存的共享访问使服务器可以显着提高吞吐量。


    多个密钥缓存

    注意

    从MySQL 8.0开始,MyISAM不推荐使用此处讨论的用于引用多个键缓存的复合部分结构变量语法。

    共享访问密钥缓存可以提高性能,但不能完全消除会话之间的争用。它们仍在争夺管理对键高速缓存缓冲区的访问的控制结构。为了进一步减少密钥缓存访问争用,MySQL还提供了多个密钥缓存。此功能使您可以将不同的表索引分配给不同的键高速缓存。

    如果有多个键缓存,则服务器必须知道在处理给定MyISAM表的查询时要使用哪个缓存。默认情况下,所有MyISAM表索引都缓存在默认键缓存中。要将表索引分配给特定的键高速缓存,请使用该CACHE INDEX语句(请参见“ CACHE INDEX语句”)。例如,从表中下面的语句受让人指标t1t2以及t3向键缓存命名为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       	|
    +---------	+--------------------	+----------	+----------	+
    

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

    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是缓存组件。有关用于引用结构化密钥缓存系统变量的语法的描述,请参见“MySQL服务器系统变量”。

    默认情况下,表索引分配给服务器启动时创建的主(默认)键高速缓存。销毁键高速缓存时,分配给它的所有索引都将重新分配给默认键高速缓存。

    对于繁忙的服务器,可以使用涉及三个关键缓存的策略:

    • 一个“热”键高速占用分配给所有键高速缓冲空间的20%。将其用于大量用于搜索但未更新的表。
    • 一个“冷”键高速占用分配给所有键高速缓冲空间的20%。将此缓存用于中等大小的,经过大量修改的表,例如临时表。
    • 一个“温暖”键高速占用键高速缓冲空间的60%。将此用作默认键缓存,默认情况下将其用于所有其他表。

    使用三个密钥缓存的好处之一是,对一个密钥缓存结构的访问不会阻止对其他密钥缓存结构的访问。访问分配给一个缓存的表的语句不会与访问分配给另一缓存的表的语句竞争。由于其他原因也会导致性能提升:

    • 热缓存仅用于检索查询,因此永远不会修改其内容。因此,每当需要将索引块从磁盘中拉入时,就无需先清除用于替换的高速缓存块的内容。
    • 对于分配给热缓存的索引,如果没有查询需要进行索引扫描,则很有可能与索引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每次服务器启动时都会执行 in语句。该文件每行应包含一个SQL语句。以下示例为hot_cache和分别分配了几个表cold_cache

    CACHE INDEX db1.t1, db1.t2, db2.t3 IN hot_cache
    CACHE INDEX db1.t4, db2.t5, db2.t6 IN cold_cache
    

    中点插入策略

    默认情况下,密钥缓存管理系统使用简单的LRU策略来选择要逐出的密钥缓存块,但它还支持一种更复杂的方法,称为中点插入策略。

    使用中点插入策略时,LRU链分为两部分:热子列表和热子列表。两个部分之间的划分点不是固定的,但是密钥缓存管理系统要注意热部分不要“太短”,始终包含至少key_cache_division_limit百分之一的密钥缓存块。key_cache_division_limit是结构化键缓存变量的组成部分,因此其值是可以为每个缓存设置的参数。

    当将索引块从表中读取到键高速缓存中时,该索引块将置于热子列表的末尾。达到一定数量的点击次数(访问该块)后,它将被提升到热子列表。目前,对于所有索引块而言,提升一个块(3)所需的命中数都是相同的。

    提升为热子列表的块位于列表的末尾。然后,该块在该子列表中循环。如果该块在子列表的开头停留了足够长的时间,它将被降级到热子列表。此时间由key_cache_age_threshold密钥缓存的组件的值确定。

    阈值规定,对于包含N块的键高速缓存,将在最后一次N* key_cache_age_threshold / 100命中未访问的热子列表的开始处的块移到热子列表的开始处。然后,它将成为第一个驱逐候选对象,因为总是从热子列表的开头开始进行替换。

    中点插入策略使您可以将更多有价值的块始终保留在缓存中。如果您更喜欢使用普通LRU策略,请将该key_cache_division_limit值设置为其默认值100。

    当需要索引扫描的查询的执行有效地将与有价值的高级B树节点对应的所有索引块推出高速缓存时,中点插入策略有助于提高性能。为避免这种情况,您必须使用key_cache_division_limit设置为小于100 的中点插入策略。然后,在索引扫描操作期间,有价值的频繁命中的节点也会保留在热子列表中。

    索引预加载

    如果键高速缓存中有足够的块来容纳整个索引的块,或者至少包含与其非叶节点相对应的块,那么在开始使用键高速缓存之前,先将索引块预加载到索引中是有意义的。预加载使您能够以最有效的方式将表索引块放入键高速缓存缓冲区:通过从磁盘顺序读取索引块。

    在没有预加载的情况下,块仍会根据查询的需要放置在键高速缓存中。尽管这些块将保留在高速缓存中,但是由于有足够的缓冲区供所有它们使用,它们是从磁盘中以随机顺序而不是顺序获取的。

    要将索引预加载到缓存中,请使用以下LOAD INDEX INTO CACHE语句。例如,以下语句预加载表t1和的索引的节点(索引块)t2

    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语句将索引分配给键高速缓存,则预加载会将索引块放入该高速缓存中。否则,索引将加载到默认键缓存中。

    密钥缓存块大小

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

    当读取缓冲区的大小等于本机操作系统I / O缓冲区的大小时,可以实现I / O操作的最佳性能。但是,将关键节点的大小设置为等于I / O缓冲区的大小并不能始终确保最佳的整体性能。当读取大叶节点时,服务器会提取大量不必要的数据,从而有效地防止读取其他叶节点。

    要控制表的.MYI索引文件中块的大小,请在服务器启动时MyISAM使用该--myisam-block-size选项。

    重构密钥缓存

    密钥缓存可以随时通过更新其参数值进行重组。例如:

    mysql> SET GLOBAL cold_cache.key_buffer_size=4*1024*1024;
    

    如果您为key_buffer_sizekey_cache_block_size键高速缓存组件分配的值与该组件的当前值不同,则服务器将破坏高速缓存的旧结构,并根据新值创建一个新的结构。如果缓存中包含任何脏块,则服务器会在销毁并重新创建缓存之前将它们保存到磁盘。如果您更改其他关键缓存参数,则不会发生重组。

    重组键高速缓存时,服务器首先将所有脏缓冲区的内容刷新到磁盘。此后,缓存内容将不可用。但是,重组不会阻止需要使用分配给缓存的索引的查询。相反,服务器使用本机文件系统缓存直接访问表索引。文件系统缓存的效率不如使用键缓存,因此尽管执行查询,但可以预期会减慢速度。重组缓存后,它可再次用于缓存分配给它的索引,并且不再对索引使用文件系统缓存。

    缓存准备好的语句和存储的程序

    对于客户端可能在会话中多次执行的某些语句,服务器会将语句转换为内部结构,并缓存该结构以在执行期间使用。缓存使服务器能够更高效地执行,因为它避免了会话期间再次需要使用该语句时,可以避免重新转换该语句的开销。这些语句发生转换和缓存:

    • 准备好的语句,包括在SQL级别处理的PREPARE语句(使用该语句)和使用二进制客户端/服务器协议(使用mysql_stmt_prepare()C API函数)处理的语句。所述max_prepared_stmt_count系统变量控制语句的服务器高速缓存的总数量。(所有会话中准备好的语句的总数。)
    • 存储的程序(存储的过程和功能,触发器和事件)。在这种情况下,服务器将转换并缓存整个程序主体。该stored_program_cache系统变量指示存储的程序每个会话的服务器缓存的大致数量。

    服务器在每个会话的基础上为准备好的语句和存储的程序维护高速缓存。为一个会话缓存的语句无法被其他会话访问。会话结束时,服务器将丢弃为其缓存的所有语句。

    当服务器使用缓存的内部语句结构时,必须注意该结构不会过时。语句所使用的对象可能发生元数据更改,从而导致当前对象定义与内部语句结构中表示的定义不匹配。DDL语句会发生元数据更改,例如创建,删除,更改,重命名或截断表,分析,优化或修复表的语句。表内容的更改(例如,使用INSERTUPDATE)不会更改元数据,SELECT语句也不会更改。

    这是问题的例证。假设客户准备以下语句:

    PREPARE s1 FROM 'SELECT * FROM t1';
    

    SELECT *内部结构上表中的列的列表展开。如果使用修改了表中的列集ALTER TABLE,则prepared语句已过期。如果服务器在下次客户端执行时未检测到此更改s1,则prepared语句将返回错误的结果。

    为了避免由准备好的语句引用的表或视图的元数据更改引起的问题,服务器将检测到这些更改,并在下次执行该语句时自动重新准备该语句。即,服务器重新解析该语句并重建内部结构。从表定义高速缓存中清除引用的表或视图之后,也会重新进行解析,这是隐式地为高速缓存中的新条目腾出空间,或者是由于导致的显式FLUSH TABLES

    同样,如果存储程序使用的对象发生更改,则服务器将重新解析程序中受影响的语句。

    服务器还检测表达式中对象的元数据更改。这些可能会在陈述具体可用于存储程序,如DECLARE CURSOR或流量控制语句,如IFCASERETURN

    为了避免重新解析整个存储的程序,服务器仅在需要时才重新解析程序中受影响的语句或表达式。例子:

    • 假设表或视图的元数据已更改。对于SELECT *访问表或视图的程序,将进行重新解析,但对于SELECT *不访问表或视图的程序,则不会进行重新解析。
    • 当一条语句受到影响时,服务器将在可能的情况下仅部分对其进行重新分析。考虑以下CASE语句:

      CASE case_expr
        WHEN when_expr1 ...
        WHEN when_expr2 ...
        WHEN when_expr3 ...
        ...
      END CASE
      

      如果元数据更改仅影响,则将重新解析该表达式。而其他表达式则无法解析。WHEN when_expr3case_exprWHEN

    重新解析使用默认的数据库和SQL模式,该模式对原始转换为内部格式有效。

    服务器尝试最多进行三次重新解析。如果所有尝试均失败,则会发生错误。

    重新解析是自动的,但是在一定程度上会降低准备好的语句和存储程序的性能。

    对于准备好的语句,Com_stmt_reprepare状态变量跟踪重新准备的次数。


    上篇:优化器统计