第8章优化

目录

8.1优化概述
8.2优化SQL语句
8.2.1优化SELECT语句
8.2.2优化子查询,派生表,查看引用和公用表表达式
8.2.3优化INFORMATION_SCHEMA查询
8.2.4优化性能模式查询
8.2.5优化数据变更声明
8.2.6优化数据库权限
8.2.7其他优化技巧
8.3优化和索引
8.3.1 MySQL如何使用索引
8.3.2主键优化
8.3.3空间索引优化
8.3.4外键优化
8.3.5列索引
8.3.6多列索引
8.3.7验证索引使用情况
8.3.8 InnoDB和MyISAM索引统计信息收集
8.3.9 B树和哈希索引的比较
8.3.10索引扩展的使用
8.3.11生成列索引的优化程序使用
8.3.12隐形指数
8.3.13降序索引
8.3.14 TIMESTAMP列的索引查找
8.4优化数据库结构
8.4.1优化数据大小
8.4.2优化MySQL数据类型
8.4.3优化许多表格
8.4.4 MySQL中的内部临时表使用
8.5优化InnoDB表
8.5.1优化InnoDB表的存储布局
8.5.2优化InnoDB事务管理
8.5.3优化InnoDB只读事务
8.5.4优化InnoDB重做日志
8.5.5 InnoDB表的批量数据加载
8.5.6优化InnoDB查询
8.5.7优化InnoDB DDL操作
8.5.8优化InnoDB磁盘I / O.
8.5.9优化InnoDB配置变量
8.5.10为具有多个表的系统优化InnoDB
8.6优化MyISAM表
8.6.1优化MyISAM查询
8.6.2 MyISAM表的批量数据加载
8.6.3优化REPAIR TABLE语句
8.7优化MEMORY表
8.8了解查询执行计划
8.8.1使用EXPLAIN优化查询
8.8.2 EXPLAIN输出格式
8.8.3扩展EXPLAIN输出格式
8.8.4获取命名连接的执行计划信息
8.8.5估计查询性能
8.9控制查询优化器
8.9.1控制查询计划评估
8.9.2可切换的优化
8.9.3优化器提示
8.9.4索引提示
8.9.5优化器成本模型
8.9.6优化器统计
8.10缓冲和缓存
8.10.1 InnoDB缓冲池优化
8.10.2 MyISAM密钥缓存
8.10.3准备好的声明和存储程序的缓存
8.11优化锁定操作
8.11.1内部锁定方法
8.11.2表锁定问题
8.11.3并发插入
8.11.4元数据锁定
8.11.5外部锁定
8.12优化MySQL服务器
8.12.1优化磁盘I / O.
8.12.2使用符号链接
8.12.3优化内存使用
8.12.4优化网络使用
8.12.5资源组
8.13衡量绩效(基准)
8.13.1测量表达式和函数的速度
8.13.2使用您自己的基准
8.13.3使用performance_schema测量性能
8.14检查线程信息
8.14.1线程命令值
8.14.2一般线程状态
8.14.3复制主线程状态
8.14.4复制从站I / O线程状态
8.14.5复制从属SQL线程状态
8.14.6复制从站连接线程状态
8.14.7 NDB群集线程状态
8.14.8事件调度程序线程状态

本章介绍如何优化MySQL性能并提供示例。 优化涉及在多个级别配置,调整和测量性能。 根据您的工作角色(开发人员,DBA或两者的组合),您可以在单个SQL语句,整个应用程序,单个数据库服务器或多个联网数据库服务器的级别进行优化。 有时您可以主动并提前计划性能,而有时您可能会在出现问题后解决配置或代码问题。 优化CPU和内存使用还可以提高可伸缩性,允许数据库处理更多负载而不会降低速度。

8.1优化概述

数据库性能取决于数据库级别的多个因素,例如表,查询和配置设置。 这些软件构造导致硬件级别的CPU和I / O操作,您必须尽可能地最小化并尽可能高效。 在处理数据库性能时,首先要学习软件方面的高级规则和指南,并使用挂钟时间来衡量性能。 当您成为专家时,您将了解内部发生的更多信息,并开始测量诸如CPU周期和I / O操作之类的事情。

典型用户的目标是从现有的软件和硬件配置中获得最佳的数据库性能。 高级用户寻找改进MySQL软件本身的机会,或者开发自己的存储引擎和硬件设备来扩展MySQL生态系统。

在数据库级别进行优化

使数据库应用程序快速运行的最重要因素是其基本设计:

  • 表格结构合理吗? 特别是,列是否具有正确的数据类型,并且每个表是否具有适合工作类型的列? 例如,执行频繁更新的应用程序通常具有许多具有少量列的表,而分析大量数据的应用程序通常具有很少列的表。

  • 是否有适当的 索引 来提高查询效率?

  • 您是否为每个表使用适当的存储引擎,并利用您使用的每个存储引擎的优势和功能? 特别是,事务性存储引擎(例如 InnoDB 事务性存储引擎)的选择 MyISAM 对于性能和可伸缩性来说非常重要。

    注意

    InnoDB 是新表的默认存储引擎。 实际上,高级 InnoDB 性能特征意味着 InnoDB 表通常优于更简单的 MyISAM 表,尤其是对于繁忙的数据库。

  • 每个表是否使用适当的行格式? 此选择还取决于用于表的存储引擎。 特别是,压缩表使用较少的磁盘空间,因此需要较少的磁盘I / O来读取和写入数据。 压缩适用于具有 InnoDB 表的 所有类型的工作负载 以及只读 MyISAM 表。

  • 应用程序是否使用适当的 锁定策略 例如,通过允许可能的共享访问,以便数据库操作可以并发运行,并在适当时请求独占访问,以便关键操作成为最高优先级。 同样,存储引擎的选择也很重要。 InnoDB 存储引擎处理大部分锁定的问题,而不需要您的参与,允许在数据库更好的并发,减少试验和调整的金额,让您的代码。

  • 是否 正确 使用了用于缓存的 所有 内存区域 也就是说,足够大以容纳频繁访问的数据,但不能太大以至于它们会超载物理内存并导致分页。 要配置的主要内存区域是 InnoDB 缓冲池和 MyISAM 密钥缓存。

在硬件级别进行优化

随着数据库变得越来越繁忙,任何数据库应用程序最终都会达到硬件限制。 DBA必须评估是否可以调整应用程序或重新配置服务器以避免这些 瓶颈 ,或者是否需要更多硬件资源。 系统瓶颈通常来自这些来源:

  • 磁盘寻求。 磁盘需要一段时间才能找到一块数据。 对于现代磁盘,平均时间通常低于10毫秒,因此我们理论上可以做到大约100次寻找。 这个时间用新磁盘慢慢改善,并且很难针对单个表进行优化。 优化寻道时间的方法是将数据分配到多个磁盘上。

  • 磁盘读写。 当磁盘位于正确位置时,我们需要读取或写入数据。 使用现代磁盘,一个磁盘可提供至少10-20MB / s的吞吐量。 这比搜索更容易优化,因为您可以从多个磁盘并行读取。

  • CPU周期。 当数据在主存储器中时,我们必须处理它以获得我们的结果。 与内存量相比具有大表是最常见的限制因素。 但是对于小桌子,速度通常不是问题。

  • 内存带宽。 当CPU需要的数据量超过CPU缓存容量时,主内存带宽成为瓶颈。 对于大多数系统来说,这是一个不常见的瓶颈,但需要注意的是。

平衡可移植性和性能

要在可移植的MySQL程序中使用面向性能的SQL扩展,您可以在 /*! */ 注释分隔符 的语句中包含特定于MySQL的关键字 其他SQL服务器忽略注释的关键字。 有关编写注释的信息,请参见 第9.6节“注释语法”

8.2优化SQL语句

数据库应用程序的核心逻辑是通过SQL语句执行的,无论是直接通过解释器发出还是通过API在后台提交。 本节中的调整准则有助于加速各种MySQL应用程序。 这些准则涵盖了读取和写入数据的SQL操作,一般SQL操作的幕后开销,以及特定方案(如数据库监视)中使用的操作。

8.2.1优化SELECT语句

查询以 SELECT 语句 的形式 执行数据库中的所有查找操作。 调整这些语句是首要任务,无论是为动态网页实现亚秒响应时间,还是为了产生巨大的隔夜报告而缩短工作时间。

此外 SELECT 语句,进行查询调谐技术也适用于结构,如 CREATE TABLE...AS SELECT INSERT INTO...SELECT WHERE 在条款 DELETE 的语句。 这些语句具有额外的性能考虑因素,因为它们将写操作与面向读取的查询操作相结合。

NDB Cluster支持连接下推优化,从而将合格连接完整地发送到NDB Cluster数据节点,在NDB Cluster数据节点中可以将它们分布在它们之间并且并行执行。 有关此优化的更多信息,请参阅 NDB下推连接的条件

优化查询的主要考虑因素是:

  • SELECT ... WHERE 加快查询速度,首先要检查的是是否可以添加 索引 WHERE 子句中 使用的列上设置索引 ,以加快评估,过滤和结果的最终检索。 为避免浪费磁盘空间,请构建一小组索引,以加速应用程序中使用的许多相关查询。

    索引对于引用不同表的查询尤其重要,使用 联接 外键等功能 您可以使用该 EXPLAIN 语句来确定用于a的索引 SELECT 请参见 第8.3.1节“MySQL如何使用索引” 第8.8.1节“使用EXPLAIN优化查询”

  • 隔离并调整查询的任何部分,例如函数调用,这会占用过多时间。 根据查询的结构,可以为结果集中的每一行调用一次函数,甚至可以为表中的每一行调用一次函数,从而大大减轻任何低效率。

  • 最大限度地减少 查询中 全表扫描 ,尤其是对于大表。

  • 通过 ANALYZE TABLE 定期 使用 语句 使表统计信息保持最新 ,因此优化程序具有构建高效执行计划所需的信息。

  • 了解特定于每个表的存储引擎的调优技术,索引技术和配置参数。 双方 InnoDB MyISAM 有两套准则的实现和维持查询高性能。 有关详细信息,请参见 第8.5.6节“优化InnoDB查询” 第8.6.1节“优化MyISAM查询”

  • 您可以 InnoDB 使用 第8.5.3节“优化InnoDB只读事务”中 的技术 优化 表的 单查询事务

  • 避免以难以理解的方式转换查询,尤其是在优化程序自动执行某些相同转换的情况下。

  • 如果其中一个基本准则无法轻松解决性能问题,请通过阅读 EXPLAIN 计划并调整索引, WHERE 子句,连接子句等来 调查特定查询的内部详细信息 (当您达到一定的专业水平时,阅读 EXPLAIN 计划可能是您每次查询的第一步。)

  • 调整MySQL用于缓存的内存区域的大小和属性。 通过有效使用 InnoDB 缓冲池 MyISAM 密钥缓存和MySQL查询缓存,重复查询运行得更快,因为在第二次及以后的时间内从内存中检索结果。

  • 即使对于使用高速缓存存储区域快速运行的查询,您仍可以进一步优化,以便它们需要更少的高速缓存,从而使您的应用程序更具可伸缩性。 可伸缩性意味着您的应用程序可以处理更多的并发用户,更大的请求等,而不会出现性能大幅下降的情况。

  • 处理锁定问题,其中查询的速度可能会受到同时访问表的其他会话的影响。

8.2.1.1 WHERE子句优化

本节讨论可以为处理 WHERE 子句 进行的优化 这些示例使用 SELECT 语句,但相同的优化适用 WHERE DELETE UPDATE 语句中的 子句

注意

由于MySQL优化器的工作正在进行中,因此并未记录MySQL执行的所有优化。

您可能想要重写查询以更快地进行算术运算,同时牺牲可读性。 因为MySQL会自动执行类似的优化,所以通常可以避免这种工作,并使查询保持更易理解和可维护的形式。 MySQL执行的一些优化如下:

  • 删除不必要的括号:

       ((a AND b)AND c OR(((a AND b)AND(c AND d))))
    - >(a AND b AND c)OR(a AND b AND c AND d)
    
  • 恒定折叠:

       (a <b AND b = c)AND a = 5
    - > b> 5 AND b = c AND a = 5
    
  • 恒定条件去除:

       (b> = 5 AND b = 5)OR(b = 6 AND 5 = 5)OR(b = 7 AND 5 = 6)
    - > b = 5或b = 6
    

    在MySQL 8.0.14及更高版本中,这是在准备期间而不是在优化阶段期间进行的,这有助于简化连接。 有关 更多信息和示例, 请参见 第8.2.1.8节“外部连接优化”

  • 索引使用的常量表达式仅计算一次。

  • 从MySQL 8.0.16开始,对具有常量值的数值类型列进行比较,检查并折叠或删除无效或不合法值:

    #CREATE TABLE t(c TINYINT UNSIGNED NOT NULL);
      SELECT * FROM t WHERE c«256;
    -yes SELECT * FROM t WHERE 1;
    

    有关 更多信息 请参见 第8.2.1.13节“常量折叠优化”

  • COUNT(*) 在没有a的单个表上 WHERE 直接从表信息 MyISAM MEMORY 表中 检索 NOT NULL 当仅与一个表一起使用时, 也可以对任何 表达式执行 此操作

  • 早期检测无效常量表达式。 MySQL快速检测到某些 SELECT 语句是不可能的,并且不返回任何行。

  • HAVING WHERE 如果您不使用 GROUP BY 或聚合函数( COUNT() MIN() 等等), 则合并

  • 对于连接中的每个表, WHERE 构造 一个更简单 WHERE 的表来 快速 评估表,并尽快跳过行。

  • 在查询中的任何其他表之前首先读取所有常量表。 常量表是以下任何一种:

    • 一张空表或一行表。

    • 与a index WHERE 子句一起 使用的表 ,其中所有索引部分都与常量表达式进行比较并定义为 PRIMARY KEY UNIQUE NOT NULL

    以下所有表都用作常量表:

    SELECT * FROM t WHERE primary_key= 1;
    SELECT * FROM t1,t2
      在哪里t1。primary_key= 1和t2。primary_key= t1.id;
    
  • 通过尝试所有可能性,可以找到加入表格的最佳连接组合。 如果 ORDER BY GROUP BY 子句中的 所有列 都来自同一个表,则在加入时首先首选该表。

  • 如果存在 ORDER BY 子句和不同的 GROUP BY 子句,或者如果 ORDER BY 或者 GROUP BY 包含连接队列中第一个表以外的表中的列,则会创建临时表。

  • 如果使用 SQL_SMALL_RESULT 修饰符,MySQL使用内存临时表。

  • 查询每个表索引,并使用最佳索引,除非优化程序认为使用表扫描更有效。 有一次,根据最佳索引是否跨越超过表的30%使用扫描,但固定百分比不再决定使用索引或扫描之间的选择。 优化器现在更复杂,并且基于其他因素(例如表大小,行数和I / O块大小)进行估算。

  • 在某些情况下,MySQL甚至无需咨询数据文件即可从索引中读取行。 如果索引中使用的所有列都是数字,则仅使用索引树来解析查询。

  • 在输出每一行之前, HAVING 将跳过 与该 子句 不匹配的行

一些非常快的查询示例:

SELECT COUNT(*)FROM tbl_name;

SELECT MIN(key_part1),MAX(key_part1)FROM tbl_name;

SELECT MAX(key_part2)FROM tbl_name
  WHERE key_part1= constant;

SELECT ... FROM tbl_name
  ORDER BY key_part1key_part2... LIMIT 10;

SELECT ... FROM tbl_name
  ORDER BY key_part1DESC,key_part2DESC,... LIMIT 10;

MySQL仅使用索引树解析以下查询,假设索引列是数字:

SELECT key_part1key_part2FROM tbl_nameWHERE key_part1= val;

SELECT COUNT(*)FROM tbl_name
  WHERE key_part1= val1AND key_part2= val2;

SELECT key_part2FROM tbl_nameGROUP BY key_part1;

以下查询使用索引来按排序顺序检索行,而不使用单独的排序传递:

SELECT ... FROM tbl_name
  ORDER BY key_part1key_part2...;

SELECT ... FROM tbl_name
  ORDER BY key_part1DESC,key_part2DESC,...;

8.2.1.2范围优化

range 访问方法使用单个索引来检索包含一个或若干个索引值的时间间隔内表行的子集。 它可用于单部分或多部分索引。 以下部分描述了优化程序使用范围访问的条件。

单部分索引的范围访问方法

对于单部分索引,索引值间隔可以方便地由 WHERE 子句中 的相应条件 表示,表示为 范围条件 而不是 间隔”。

单部分索引的范围条件的定义如下:

  • 对于这两种 BTREE HASH 索引,使用时具有恒定值的关键部分的比较是一个范围条件 = <=> IN() IS NULL ,或 IS NOT NULL 运营商。

  • 另外,对于 BTREE 索引,使用时具有恒定值的关键部分的比较是一个范围条件 > < >= <= BETWEEN != ,或 <> 运营商,或者 LIKE 比较,如果该参数 LIKE 是一个常数字符串不与通配符开始。

  • 对于所有索引类型,多个范围条件与范围条件组合 OR AND 形成范围条件。

前面描述中的 常量值 表示以下之一:

  • 来自查询字符串的常量

  • 来自同一连接 的a const system table的

  • 不相关子查询的结果

  • 任何表达式完全由前面类型的子表达式组成

以下是 WHERE 子句中 具有范围条件的查询的一些示例

SELECT * FROM t1
  在哪里key_col> 1
  AND key_col<10;

SELECT * FROM t1
  WHERE key_col= 1key_col(15,18,20);

SELECT * FROM t1
  在哪里key_col'ab%'key_col'BETWEEN'bar'和'foo';

在优化器常量传播阶段,一些非常量值可以转换为常量。

MySQL尝试从 WHERE 每个可能索引 子句中 提取范围条件 在提取过程期间,丢弃不能用于构建范围条件的条件,组合产生重叠范围的条件,并且去除产生空范围的条件。

请考虑以下语句,其中 key1 是索引列 nonkey 且未编入索引:

SELECT * FROM t1 WHERE
  (key1 <'abc'AND(key1 LIKE'abcde%'或key1 LIKE'%b'))或者
  (key1 <'bar'和nonkey = 4)或
  (key1 <'uux'和key1>'z');

密钥的提取过程 key1 如下:

  1. 从原始 WHERE 条款 开始

    (key1 <'abc'AND(key1 LIKE'abcde%'或key1 LIKE'%b'))或者
    (key1 <'bar'和nonkey = 4)或
    (key1 <'uux'和key1>'z')
    
  2. 删除 nonkey = 4 key1 LIKE '%b' 因为它们不能用于范围扫描。 删除它们的正确方法是用它们替换它们 TRUE ,这样我们在进行范围扫描时不会错过任何匹配的行。 TRUE 产量 替换它们

    (key1 <'abc'AND(key1 LIKE'abcde%'或TRUE))或
    (key1 <'bar'和TRUE)或
    (key1 <'uux'和key1>'z')
    
  3. 折叠条件始终为真或假:

    • (key1 LIKE 'abcde%' OR TRUE) 总是如此

    • (key1 < 'uux' AND key1 > 'z') 总是假的

    用常数替换这些条件会产生:

    (key1 <'abc'和TRUE)或(key1 <'bar'和TRUE)或(FALSE)
    

    删除不必要的 TRUE FALSE 常量会产生:

    (key1 <'abc')或(key1 <'bar')
    
  4. 将重叠间隔组合成一个会产生用于范围扫描的最终条件:

    (key1 <'bar')
    

通常(并且如前面的示例所示),用于范围扫描的条件比该 WHERE 子句的 限制性更小 MySQL执行额外的检查以过滤掉满足范围条件但不满足完整 WHERE 子句的行。

范围条件提取算法可以处理 任意深度的 嵌套 AND / OR 构造,并且其输出不依赖于条件在 WHERE 子句中 出现的顺序

MySQL不支持合并 range 空间索引 访问方法的 多个范围 要解决此限制, 除了将每个空间谓词放在不同的语句中之外 ,您可以使用 UNION 具有相同 SELECT 语句的语句 SELECT

多部分索引的范围访问方法

多部分索引的范围条件是单部分索引的范围条件的扩展。 多部分索引上的范围条件将索引行限制在一个或多个关键元组间隔内。 使用索引中的排序在一组关键元组上定义关键元组间隔。

例如,考虑定义为的多部分索引 ,以及按键顺序列出的以下一组关键元组: key1(key_part1, key_part2, key_part3)

key_part1  key_part2  key_part3
  NULL 1'abc'
  NULL 1'xyz'
  NULL 2'foo'
   1 1'abc'
   1 1'xyz'
   1 2'abc'
   2 1'aaa'

条件 key_part1 = 1 定义了这个间隔:

(1,-INF,-INF)<=( ,key_part1 )<(1,+ INF,+ INF)
key_part2key_part3

间隔覆盖前面数据集中的第4,第5和第6个元组,并且可以由范围访问方法使用。

相反,条件 key_part3 = 'abc' 不定义单个间隔,并且不能由范围访问方法使用。

以下描述更详细地说明了范围条件如何适用于多部分索引。

  • 对于 HASH 索引,可以使用包含相同值的每个间隔。 这意味着只能为以下形式的条件生成间隔:

        key_part1 cmp const1key_part2 cmp const2
    和......
    AND ;
    key_partN cmp constN

    这里 const1 const2 ...是常数, cmp 是一个 = <=> 或者 IS NULL 比较运营商,以及条件覆盖所有指数部分。 (也就是说,有一些 N 条件,一个用于 N -part索引的 每个部分 。)例如,以下是三部分 HASH 索引 的范围条件

    key_part1= 1 AND key_part2IS NULL AND key_part3='foo'
    

    有关被认为是常量的定义,请参阅 单部分索引的范围访问方法

  • 对于一个 BTREE 索引,以一定间隔可能是条件组合可用 AND ,其中每个条件使用的恒定值的关键部分进行比较 = <=> IS NULL > < >= <= != <> BETWEEN ,或 (其中 LIKE 'pattern' 'pattern' 不以通配符开头)。 可以使用间隔,只要可以确定包含与条件匹配的所有行的单个密钥元组(或者如果使用 <> 或者 != 使用 两个间隔 )。

    只要比较运算符是 ,或 = 优化程序就会尝试使用其他关键部分来确定间隔 如果操作是 ,或者 ,优化器使用它,但认为没有更多的关键部分。 对于以下表达式,优化程序将使用 第一个比较。 它也使用 <=> IS NULL > < >= <= != <> BETWEEN LIKE = >= 从第二次比较,但没有考虑其他关键部分,并没有使用间隔构造的第三个比较:

    key_part1='foo'AND key_part2> = 10 AND key_part3> 10
    

    单个间隔是:

    ( '富',10,-INF)<( ,key_part1 )<( '富',+ INF,+ INF)
    key_part2key_part3

    创建的间隔可能包含比初始条件更多的行。 例如,前面的间隔包括 ('foo', 11, 0) 不满足原始条件的值。

  • 如果包含区间内包含的行集的条件与其组合 OR ,则它们形成一个条件,该条件覆盖其间隔的并集中包含的一组行。 如果条件与 AND 它们 组合 ,则它们形成一个条件,该条件覆盖其间隔的交集中包含的一组行。 例如,对于这个由两部分组成的索引:

    key_part1= 1 AND key_part2<2)OR(key_part1> 5)
    

    间隔是:

    (1,-inf)<(key_part1key_part2)<(1,2)
    (5,-inf)<(key_part1key_part2

    在此示例中,第一行的间隔使用左边界的一个关键部分和右边界的两个关键部分。 第二行的间隔仅使用一个关键部分。 输出中 key_len EXPLAIN 指示使用的密钥前缀的最大长度。

    在某些情况下, key_len 可能表示使用了关键部件,但这可能不是您所期望的。 假设 key_part1 并且 key_part2 可以 NULL 然后该 key_len 列显示以下条件的两个关键部分长度:

    key_part1> = 1 AND key_part2<2
    

    但是,事实上,条件转换为:

    key_part1> = 1并且key_part2不是NULL
    

有关如何执行优化以组合或消除单部分索引上的范围条件的间隔的说明,请参阅单部分索引的 范围访问方法 对多部分索引的范围条件执行类似步骤。

多值比较的等价范围优化

考虑这些表达式,其中 col_name 是索引列:

col_nameIN(val1,...,valNcol_name= val1OR ... OR col_name=valN

如果 col_name 等于多个值中的任何一个, 则每个表达式都为真 这些比较是等式范围比较(其中 范围 是单个值)。 优化程序估计读取限定行的成本以进行相等范围比较,如下所示:

  • 如果有唯一索引 col_name ,则每个范围的行估计值为1,因为最多一行可以具有给定值。

  • 否则,任何索引 col_name 都是非唯一的,并且优化器可以使用潜入索引或索引统计信息来估计每个范围的行数。

使用索引潜水时,优化程序会在范围的每一端进行潜水,并使用范围中的行数作为估计值。 例如,表达式 col_name IN (10, 20, 30) 具有三个相等范围,优化程序每个范围进行两次潜水以生成行估计。 每对潜水产生具有给定值的行数的估计。

索引潜水提供准确的行估计,但随着表达式中比较值的数量增加,优化程序需要更长时间才能生成行估计。 索引统计的使用不如索引潜水准确,但允许对大值列表进行更快的行估计。

eq_range_index_dive_limit 系统变量,可以配置在其优化从一个行估计策略到其他交换机值的数量。 要允许使用索引潜水进行最大 N 相等范围的 比较 ,请设置 eq_range_index_dive_limit N + 1.要禁用统计数据并始终使用索引潜水 N ,请将其设置 eq_range_index_dive_limit 为0。

要更新的最佳估计表索引统计信息,使用 ANALYZE TABLE

在MySQL 8.0之前,除了使用 eq_range_index_dive_limit 系统变量 之外,没有办法跳过使用索引潜水来估计索引的有用性 在MySQL 8.0中,对于满足所有这些条件的查询,可以进行索引潜水跳过:

  • 查询用于单个表,而不是多个表的连接。

  • 存在单索引 FORCE INDEX 索引提示。 我们的想法是,如果强制使用索引,那么从潜在的索引中获取额外开销就无法获得任何好处。

  • 该索引不是唯一的而不是 FULLTEXT 索引。

  • 没有子查询。

  • DISTINCT GROUP BY ORDER BY 条款存在。

对于 EXPLAIN FOR CONNECTION ,如果跳过索引潜水,则输出更改如下:

  • 对于传统输出, rows filtered 值是 NULL

  • 对于JSON输出, rows_examined_per_scan 并且 rows_produced_per_join 不显示, skip_index_dive_due_to_force true ,并且成本计算不准确。

如果没有 FOR CONNECTION EXPLAIN 则跳过索引潜水时输出不会更改。

在执行跳过索引潜水的查询后, INFORMATION_SCHEMA.OPTIMIZER_TRACE 表中 的相应行 包含 index_dives_for_range_access skipped_due_to_force_index

跳过扫描范围访问方法

请考虑以下情形:

CREATE TABLE t1(f1 INT NOT NULL,f2 INT NOT NULL,PRIMARY KEY(f1,f2));
插入t1值
  (1,1),(1,2),(1,3),(1,4),(1,5),
  (2,1),(2,2),(2,3),(2,4),(2,5);
INSERT INTO t1 SELECT f1,f2 + 5 FROM t1;
INSERT INTO t1 SELECT f1,f2 + 10 FROM t1;
INSERT INTO t1 SELECT f1,f2 + 20 FROM t1;
INSERT INTO t1 SELECT f1,f2 + 40 FROM t1;
分析表t1;

EXPLAIN SELECT f1,f2 FROM t1 WHERE f2> 40;

要执行此查询,MySQL可以选择索引扫描来获取所有行(索引包括要选择的所有列),然后应用 子句中 f2 > 40 条件 WHERE 以生成最终结果集。

范围扫描比完整索引扫描更有效,但在这种情况下不能使用,因为 f1 第一个索引列 没有条件 但是,从MySQL 8.0.13开始,优化器可以 f1 使用类似于松散索引扫描的称为Skip Scan的方法 对每个值执行多个范围扫描 (请参见 第8.2.1.16节“GROUP BY优化” ):

  1. 跳过第一个索引部分的不同值 f1 (索引前缀)。

  2. f2 > 40 对剩余索引部分上的条件的 每个不同前缀值执行子范围扫描

对于前面显示的数据集,算法的运行方式如下:

  1. 获取第一个关键部分( f1 = 1 的第一个不同值

  2. 根据第一个和第二个关键部分构造范围( f1 = 1 AND f2 > 40 )。

  3. 执行范围扫描。

  4. 获取第一个关键部分( f1 = 2 的下一个不同值

  5. 根据第一个和第二个关键部分构造范围( f1 = 2 AND f2 > 40 )。

  6. 执行范围扫描。

使用此策略会减少访问行的数量,因为MySQL会跳过不符合每个构造范围的行。 此Skip Scan访问方法适用于以下条件:

  • 表T具有至少一个具有形式的关键部分的复合索引([A_1,...,A_ k ,] B_1,...,B_ m ,C [,D_1,...,D_ n ])。 关键部分A和D可能是空的,但B和C必须是非空的。

  • 查询仅引用一个表。

  • 查询不使用 GROUP BY DISTINCT

  • 该查询仅引用索引中的列。

  • A_1,...,A_的 k 谓词必须是等式谓词,它们必须是常量。 这包括 IN() 运营商。

  • 查询必须是连接查询; 即, AND OR 条件: (cond1(key_part1) OR cond2(key_part1)) AND (cond1(key_part2) OR ...) AND ...

  • C上必须有一个范围条件。

  • 允许D列条件。 D上的条件必须与C上的范围条件一起使用。

EXPLAIN 输出中 指示使用Skip Scan 如下:

  • Using index for skip scan Extra 列中表示使用松散索引Skip Scan访问方法。

  • 如果索引可用于Skip Scan,则索引应在 possible_keys 列中 可见

"skip scan" 此格式 元素的 优化程序跟踪输出中指示使用Skip Scan

“skip_scan_range”:{
  “type”:“skip_scan”,
  “指数”: index_used_for_skip_scan
  “key_parts_used_for_access”:[ key_parts_used_for_access],
  “范围”:[ range]
}

您可能还会看到一个 "best_skip_scan_summary" 元素。 如果选择“跳过扫描”作为最佳范围访问变体, "chosen_range_access_summary" 则会写入a。 如果选择“跳过扫描”作为总体最佳访问方法, "best_access_path" 则存在元素。

使用Skip Scan取决于 系统变量 skip_scan 标志的 optimizer_switch 值。 请参见 第8.9.2节“可切换的优化” 默认情况下,此标志为 on 要禁用它,请设置 skip_scan off

除了使用 optimizer_switch 系统变量控制优化程序在会话范围内使用Skip Scan之外,MySQL还支持优化程序提示,以便在每个语句的基础上影​​响优化程序。 请参见 第8.9.3节“优化程序提示”

行构造函数表达式的范围优化

优化器能够将范围扫描访问方法应用于此表单的查询:

SELECT ... FROM t1 WHERE(col_1,col_2)IN(('a','b'),('c','d'));

以前,对于要使用的范围扫描,有必要将查询编写为:

SELECT ... FROM t1 WHERE(col_1 ='a'和col_2 ='b')
或(col_1 ='c'和col_2 ='d');

要使优化器使用范围扫描,查询必须满足以下条件:

  • 只使用 IN() 谓词,而不是 NOT IN()

  • IN() 谓词 的左侧 ,行构造函数仅包含列引用。

  • IN() 谓词 的右侧 ,行构造函数仅包含运行时常量,这些常量是在执行期间绑定到常量的文字或本地列引用。

  • IN() 谓词 的右侧 ,有多个行构造函数。

有关优化程序和行构造函数的更多信息,请参见 第8.2.1.21节“行构造函数表达式优化”

限制内存使用范围优化

要控制范围优化程序可用的内存,请使用 range_optimizer_max_mem_size 系统变量:

  • 值为0表示 无限制。

  • 值大于0时,优化程序会在考虑范围访问方法时跟踪消耗的内存。 如果要超过指定的限制,则放弃范围访问方法,并考虑其他方法,包括全表扫描。 这可能不太理想。 如果发生这种情况,会发生以下警告( N 当前 range_optimizer_max_mem_size 在哪里 ):

    警告3170内存容量的N字节数
                       超出'range_optimizer_max_mem_size'。范围
                       此查询未执行优化。
    
  • 对于 UPDATE DELETE 语句,如果优化器回退到全表扫描并且 sql_safe_updates 启用 系统变量,则会发生错误而不是警告,因为实际上没有使用任何键来确定要修改的行。 有关更多信息,请参阅 使用安全更新模式(--safe-updates)

对于超出可用范围优化内存并且优化程序回退到不太理想的计划的单个查询,增加该 range_optimizer_max_mem_size 值可以提高性能。

要估计处理范围表达式所需的内存量,请使用以下准则:

  • 对于诸如以下的简单查询,其中有一个候选键用于范围访问方法,每个谓词组合 OR 使用大约230个字节:

    SELECT COUNT(*)FROM t
    其中a = 1或a = 2或a = 3 OR .. a = N;
    
  • 类似地,对于诸如以下的查询,每个谓词组合 AND 使用大约125个字节:

    SELECT COUNT(*)FROM t
    在哪里a = 1 AND b = 1 AND c = 1 ... N;
    
  • 对于带 IN() 谓词 的查询

    SELECT COUNT(*)FROM t
    IN(1,2,...,M)和B IN(1,2,...,N);
    

    IN() 列表 中的每个文字值都 计为一个谓词组合 OR 如果有两个 IN() 列表,则组合的谓词 OR 数是每个列表中文字值的数量的乘积。 因此,与 OR 前一种情况 相结合的谓词数 M × N

8.2.1.3索引合并优化

指数合并 访问方法检索与多行 range 扫描和他们的结果合并到一个。 此访问方法仅合并来自单个表的索引扫描,而不是跨多个表扫描。 合并可以生成其基础扫描的联合,交叉或交叉联合。

可以使用Index Merge的示例查询:

SELECT * FROM tbl_nameWHERE key1= 10 OR key2= 20;

SELECT * FROM tbl_name
  WHERE(key1= 10 OR key2= 20)AND non_key= 30;

SELECT * FROM t1,t2
  WHERE(t1。IN key1(1,2)OR key2t1。LIKE' value%')
  和t2。key1= t1。some_col;

SELECT * FROM t1,t2
  在哪里t1。key1= 1
  AND(t2。key1= t1。some_col或者t2。key2= t1。some_col2);
注意

索引合并优化算法具有以下已知限制:

  • 如果您的查询具有 WHERE 带深度 AND / OR 嵌套 的复杂 子句 且MySQL未选择最佳计划,请尝试使用以下身份转换来分发术语:

    xAND y)OR z=>(xOR z)AND(yOR zxy)AND z=>(xAND z)OR(yAND z
  • 索引合并不适用于全文索引。

EXPLAIN 输出中,索引合并方法显示 index_merge type 列中。 在这种情况下,该 key 列包含使用的索引列表,并 key_len 包含这些索引的最长关键部分的列表。

Index Merge访问方法有几种算法,它们显示在 输出 Extra 字段中 EXPLAIN

  • Using intersect(...)

  • Using union(...)

  • Using sort_union(...)

以下部分更详细地描述了这些算法。 优化器根据各种可用选项的成本估算在不同的可能索引合并算法和其他访问方法之间进行选择。

索引合并交叉口访问算法

WHERE 子句在不同的键上组合 转换为多个范围条件 时,此访问算法适用 AND ,并且每个条件都是以下条件之一:

  • N 这个表单的 一个 -part表达式,其中索引具有完全 N 部分(即,所有索引部分都被覆盖):

    key_part1= const1AND key_part2= const2... AND key_partN=constN
    
  • InnoDB 的主键上的任何范围条件

例子:

SELECT * FROM innodb_table
  WHERE primary_key<10 AND key_col1= 20;

SELECT * FROM tbl_name
  WHERE key1_part1= 1 AND key1_part2= 2 AND key2= 2;

索引合并交集算法对所有使用的索引执行同时扫描,并生成从合并索引扫描接收的行序列的交集。

如果查询中使用的所有列都被使用的索引覆盖,则不会检索完整的表行( 在这种情况下 EXPLAIN 输出包含 Using index Extra 字段中)。 以下是此类查询的示例:

SELECT COUNT(*)FROM t1 WHERE key1 = 1 AND key2 = 1;

如果使用的索引未涵盖查询中使用的所有列,则仅在满足所有使用的键的范围条件时才检索完整行。

如果其中一个合并条件是 InnoDB 的主键上的条件 ,则它不用于行检索,而是用于过滤掉使用其他条件检索的行。

索引合并联盟访问算法

该算法的标准类似于索引合并交集算法的标准。 当表的 WHERE 子句在不同的键上组合时转换为多个范围条件 时,该算法适用 OR ,并且每个条件都是以下条件之一:

  • N 这个表单的 一个 -part表达式,其中索引具有完全 N 部分(即,所有索引部分都被覆盖):

    key_part1= const1AND key_part2= const2... AND key_partN=constN
    
  • InnoDB 的主键上的任何范围条件

  • 索引合并交集算法适用的条件。

例子:

SELECT * FROM t1
  WHERE key1= 1 OR key2= 2 OR key3= 3;

SELECT * FROM innodb_table
  WHERE(key1= 1 AND key2= 2)
     OR(key3='foo'AND key4='bar')AND key5= 5;
索引合并排序联合访问算法

WHERE 子句转换为多个范围条件 时,此访问算法适用 OR ,但索引合并并集算法不适用。

例子:

SELECT * FROM tbl_name
  WHERE key_col1<10 OR key_col2<20;

SELECT * FROM tbl_name
  WHERE(key_col1> 10 OR key_col2= 20)AND nonkey_col= 30;

sort-union算法和union算法之间的区别在于sort-union算法必须首先获取所有行的行ID,然后在返回任何行之前对它们进行排序。

影响指数合并优化

索引合并的使用是受价值 index_merge index_merge_intersection index_merge_union ,和 index_merge_sort_union 该旗 optimizer_switch 系统变量。 请参见 第8.9.2节“可切换的优化” 默认情况下,所有这些标志都是 on 要仅启用某些算法,请设置 index_merge off ,并仅启用应允许的其他 算法

除了使用 optimizer_switch 系统变量来控制优化程序在会话范围内使用索引合并算法之外,MySQL还支持优化程序提示以基于每个语句影响优化程序。 请参见 第8.9.3节“优化程序提示”

8.2.1.4发动机状态下推优化

这种优化提高了非索引列和常量之间直接比较的效率。 在这种情况下,将条件 下推 到存储引擎以进行评估。 此优化只能由 NDB 存储引擎使用。

对于NDB Cluster,此优化可以消除在群集的数据节点和发出查询的MySQL服务器之间通过网络发送不匹配行的需要,并且可以将查询的使用速度提高5到10倍。条件下推可能但不使用。

假设NDB Cluster表定义如下:

CREATE TABLE t1(
    一个INT,
    b INT,
    KEY(一)
)ENGINE = NDB;

条件下推可以用于查询,例如此处显示的查询,其中包括非索引列和常量之间的比较:

SELECT a,b FROM t1 WHERE b = 10;

条件下推的使用可以在输出中看到 EXPLAIN

MySQL的> EXPLAIN SELECT a,b FROM t1 WHERE b = 10\G
*************************** 1。排******************** *******
           id:1
  select_type:SIMPLE
        表:t1
         类型:全部
possible_keys:NULL
          key:NULL
      key_len:NULL
          ref:NULL
         行:10
        额外:使用推动条件的地方

但是,条件下推 不能 与这两个查询中的任何一个一起使用:

SELECT a,b FROM t1 WHERE a = 10;
SELECT a,b FROM t1 WHERE b + 1 = 10;

条件下推不适用于第一个查询,因为列上存在索引 a (索引访问方法将更有效,因此将优先选择条件下推。)条件下推不能用于第二个查询,因为涉及非索引列的比较 b 是间接的。 (但是,如果您 b + 1 = 10 b = 9 WHERE 条款中 减少条件,则可以应用条件下推 。)

当索引列与使用 > < 运算符 的常量进行比较时,也可以使用条件下推

MySQL的> EXPLAIN SELECT a, b FROM t1 WHERE a < 2\G
*************************** 1。排******************** *******
           id:1
  select_type:SIMPLE
        表:t1
         类型:范围
possible_keys:a
          关键:a
      key_len:5
          ref:NULL
         行:2
        额外:使用推动条件的地方

其他支持的条件下推比较包括以下内容:

  • column [NOT] LIKE pattern

    pattern 必须是包含要匹配的模式的字符串文字; 有关语法,请参见 第12.5.1节“字符串比较函数”

  • column IS [NOT] NULL

  • column IN (value_list)

    每个项目 value_list 必须是一个常量,字面值。

  • column BETWEEN constant1 AND constant2

    constant1 并且 constant2 每个都必须是一个恒定的字面值。

在前面列表中的所有情况中,条件可以转换为列和常量之间的一个或多个直接比较的形式。

默认情况下启用发动机状态下推。 要在服务器启动时禁用它,请设置 optimizer_switch 系统变量。 例如,在 my.cnf 文件中,使用以下行:

的[mysqld]
optimizer_switch = engine_condition_pushdown =关

在运行时,禁用条件下推,如下所示:

SET optimizer_switch ='engine_condition_pushdown = off';

限制。  发动机状态下推受以下限制:

  • 条件下推仅由 NDB 存储引擎 支持

  • 列可以仅与常量进行比较; 但是,这包括评估为常量值的表达式。

  • 比较中使用的列不能是任何 类型 BLOB TEXT 类型。

  • 要与列进行比较的字符串值必须使用与列相同的排序规则。

  • 不直接支持联接; 涉及多个表的条件在可能的情况下单独推送。 使用扩展 EXPLAIN 输出来确定实际按下哪些条件。 请参见 第8.8.3节“扩展EXPLAIN输出格式”

以前,条件下推仅限于引用条件被推送到的同一表中的列值的术语。 从NDB 8.0.16开始,查询计划中较早的表中的列值也可以从推送条件中引用。 这减少了在连接处理期间SQL节点必须处理的行数。 过滤也可以在LDM线程中并行执行,而不是在单个 mysqld 进程中执行。 这有可能大大提高查询性能。

在以下两种情况下,连接算法不能与先前表中的引用列组合:

  1. 当任何前面提到的表都在连接缓冲区中时。 在这种情况下,从扫描过滤表中检索的每一行都与缓冲区中的每一行进行匹配。 这意味着在生成扫描过滤器时,没有单个特定行可从中获取列值。

  2. 当列来自推送连接中的子操作时。 这是因为在生成扫描过滤器时尚未检索从连接中的祖先操作引用的行。

8.2.1.5指数条件下推优化

索引条件下推(ICP)是对MySQL使用索引从表中检索行的情况的优化。 如果没有ICP,存储引擎会遍历索引以查找基表中的行,并将它们返回给MySQL服务器,该服务器会评估 WHERE 条件。 启用ICP后,如果 WHERE 只使用索引中的列来评估 部分 条件,MySQL服务器会推送这部分内容。 WHERE 条件下到存储引擎。 然后,存储引擎使用索引条目评估推送的索引条件,并且仅当满足该条件时才从表中读取行。 ICP可以减少存储引擎必须访问基表的次数以及MySQL服务器必须访问存储引擎的次数。

指数条件下推优化的适用性受以下条件限制:

  • ICP用于 range ref eq_ref ,和 ref_or_null 访问方法时,有必要访问完整的表行。

  • ICP可用于 InnoDB MyISAM 表,包括分区 InnoDB MyISAM 表。

  • 对于 InnoDB 表,ICP仅用于二级索引。 ICP的目标是减少全行读取的数量,从而减少I / O操作。 对于 InnoDB 聚簇索引,已将完整记录读入 InnoDB 缓冲区。 在这种情况下使用ICP不会降低I / O.

  • 在虚拟生成列上创建的二级索引不支持ICP。 InnoDB 支持虚拟生成列上的二级索引。

  • 引用子查询的条件无法下推。

  • 无法推下涉及存储函数的条件。 存储引擎无法调用存储的函数。

  • 触发条件无法下推。 (有关触发条件的信息,请参见 第8.2.2.3节“使用EXISTS策略优化子查询” 。)

要了解此优化的工作原理,请首先考虑在不使用索引条件下推时索引扫描的进度:

  1. 获取下一行,首先读取索引元组,然后使用索引元组找到并读取整个表行。

  2. 测试 WHERE 适用于此表 条件 部分 根据测试结果接受或拒绝该行。

使用索引条件下推,扫描会像这样进行:

  1. 获取下一行的索引元组(但不是完整的表行)。

  2. 测试 WHERE 适用于此表 条件 部分, 并且只能使用索引列进行检查。 如果不满足条件,则继续下一行的索引元组。

  3. 如果满足条件,请使用索引元组来查找并读取整个表行。

  4. 测试 WHERE 适用于此表 条件 的剩余部分 根据测试结果接受或拒绝该行。

EXPLAIN 使用“索引条件下推”时, 输出显示 Using index condition Extra 列中。 它没有显示, Using index 因为当必须读取完整的表行时,这不适用。

假设一个表包含有关人员及其地址的信息,并且该表的索引定义为 INDEX (zipcode, lastname, firstname) 如果我们知道一个人的 zipcode 价值但不确定姓氏,我们可以这样搜索:

选择*来自人
  WHERE zipcode ='95054'
  AND lastname LIKE'%etrunia%'
  和地址LIKE'%Main Street%';

MySQL可以使用索引来扫描人 zipcode='95054' 第二部分( lastname LIKE '%etrunia%' )不能用于限制必须扫描的行数,因此如果没有Index Condition Pushdown,此查询必须为所有拥有的人检索完整的表行 zipcode='95054'

使用索引条件下推,MySQL lastname LIKE '%etrunia%' 在读取整个表行之前 检查该 部分。 这样可以避免读取与索引元组相对应的完整行,这些行匹配 zipcode 条件而不是 lastname 条件。

默认情况下启用索引条件下推。 可以 optimizer_switch 通过设置 index_condition_pushdown 标志 来控制 系统变量

SET optimizer_switch ='index_condition_pushdown = off';
SET optimizer_switch ='index_condition_pushdown = on';

请参见 第8.9.2节“可切换的优化”

8.2.1.6嵌套循环连接算法

MySQL使用嵌套循环算法或其变体在表之间执行连接。

嵌套循环连接算法

一个简单的嵌套循环连接(NLJ)算法一次一个循环地从第一个表中读取行,将每一行传递给一个嵌套循环,该循环处理连接中的下一个表。 这个过程重复多次,因为还有待连接的表。

假设三个表之间的连接 t1 t2 以及 t3 是使用以下类型的连接来执行:

表连接类型
t1范围
t2 ref
t3 ALL

如果使用简单的NLJ算法,则连接处理如下:

对于t1匹配范围中的每一行{
  对于t2中匹配引用键的每一行{
    对于t3中的每一行{
      如果行满足连接条件,则发送给客户端
    }
  }
}

因为NLJ算法一次一行地从外循环到内循环传递行,所以它通常多次读取在内循环中处理的表。

块嵌套循环连接算法

块嵌套循环(BNL)连接算法使用外部循环中读取的行的缓冲来减少必须读取内部循环中的表的次数。 例如,如果将10行读入缓冲区并将缓冲区传递给下一个内部循环,则可以将内循环中读取的每一行与缓冲区中的所有10行进行比较。 这将内表必须读取的次数减少一个数量级。

MySQL join缓存具有以下特征:

  • 当连接类型为 ALL index 换句话说,当不能使用任何可能的键,并且分别完成数据或索引行的完全扫描时),或者, 可以使用连接缓冲 range 缓冲的使用也适用于外连接,如 第8.2.1.11节“块嵌套循环和批量密钥访问连接”中所述

  • 永远不会为第一个非常量表分配连接缓冲区,即使它是类型 ALL index

  • 只有连接感兴趣的列存储在其连接缓冲区中,而不是整行。

  • join_buffer_size 系统变量来确定每个的大小联接缓冲液用于处理查询。

  • 为可以缓冲的每个连接分配一个缓冲区,因此可以使用多个连接缓冲区处理给定查询。

  • 在执行连接之前分配连接缓冲区,并在查询完成后释放。

对于之前针对NLJ算法(不使用缓冲)描述的示例连接,使用join buffering进行如下连接:

对于t1匹配范围中的每一行{
  对于t2中匹配引用键的每一行{
    存储在连接缓冲区中使用t1,t2中的列
    如果缓冲区已满{
      对于t3中的每一行{
        对于连接缓冲区中的每个t1,t2组合{
          如果行满足连接条件,则发送给客户端
        }
      }
      空连接缓冲区
    }
  }
}

如果缓冲区不为空{
  对于t3中的每一行{
    对于连接缓冲区中的每个t1,t2组合{
      如果行满足连接条件,则发送给客户端
    }
  }
}

如果 S 是每一个存储的大小 t1 t2 在加入缓冲液组合和 C 是组合在缓冲器中的数,次表的数量 t3 被扫描的是:

S* C)/ join_buffer_size + 1

t3 扫描降低为价值 join_buffer_size 时增加,最高可达点 join_buffer_size 是大到足以容纳所有上一行组合。 在那时,通过使其变大不会获得速度。

8.2.1.7嵌套连接优化

表达联接的语法允许嵌套联接。 以下讨论涉及 第13.2.10.2节“JOIN语法”中 描述的连接语法

table_factor 与SQL标准相比, 语法 扩展了。 后者只接受 table_reference ,而不是在一对括号内的列表。 如果我们将 table_reference 项目 列表中的每个逗号 视为等同于内部 联接,则这是保守扩展 例如:

SELECT * FROM t1 LEFT JOIN(t2,t3,t4)
                 ON(t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)

相当于:

SELECT * FROM t1 LEFT JOIN(t2 CROSS JOIN t3 CROSS JOIN t4)
                 ON(t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)

在MySQL中, CROSS JOIN 在语法上等同于 INNER JOIN ; 他们可以互相替换。 在标准SQL中,它们不等效。 INNER JOIN ON 条款 一起使用 ; CROSS JOIN 否则使用。

通常,在仅包含内部联接操作的联接表达式中可以忽略括号。 考虑这个连接表达式:

t1 LEFT JOIN(t2 LEFT JOIN t3 ON t2.b = t3.b或t2.b IS NULL)
   ON t1.a = t2.a

删除括号并将操作分组到左侧后,该连接表达式将转换为此表达式:

(t1 LEFT JOIN t2 ON t1.a = t2.a)LEFT JOIN t3
    ON t2.b = t3.b或t2.b IS NULL

然而,这两个表达并不相同。 看到这一点,假设表 t1 t2 以及 t3 具有以下状态:

  • t1 包含行 (1) (2)

  • t2 包含行 (1,101)

  • t3 包含行 (101)

在这种情况下,第一个表达式返回结果集包括行 (1,1,101,101) (2,NULL,NULL,NULL) ,而第二表达式返回的行 (1,1,101,101) (2,NULL,NULL,101)

MySQL的> SELECT *
       FROM t1
            LEFT JOIN
            (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
            ON t1.a=t2.a;
+ ------ + ------ + ------ + ------ +
| a | a | b | b |
+ ------ + ------ + ------ + ------ +
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | NULL |
+ ------ + ------ + ------ + ------ +

MySQL的> SELECT *
       FROM (t1 LEFT JOIN t2 ON t1.a=t2.a)
            LEFT JOIN t3
            ON t2.b=t3.b OR t2.b IS NULL;
+ ------ + ------ + ------ + ------ +
| a | a | b | b |
+ ------ + ------ + ------ + ------ +
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | 101 |
+ ------ + ------ + ------ + ------ +

在以下示例中,外部联接操作与内部联接操作一起使用:

t1 LEFT JOIN(t2,t3)ON t1.a = t2.a

该表达式无法转换为以下表达式:

t1 LEFT JOIN t2 ON t1.a = t2.a,t3

对于给定的表状态,这两个表达式返回不同的行集:

MySQL的> SELECT *
       FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a;
+ ------ + ------ + ------ + ------ +
| a | a | b | b |
+ ------ + ------ + ------ + ------ +
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | NULL |
+ ------ + ------ + ------ + ------ +

MySQL的> SELECT *
       FROM t1 LEFT JOIN t2 ON t1.a=t2.a, t3;
+ ------ + ------ + ------ + ------ +
| a | a | b | b |
+ ------ + ------ + ------ + ------ +
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | 101 |
+ ------ + ------ + ------ + ------ +

因此,如果我们在带有外连接运算符的连接表达式中省略括号,我们可能会更改原始表达式的结果集。

更准确地说,我们不能忽略左外连接操作的右操作数和右连接操作的左操作数中的括号。 换句话说,我们不能忽略外连接操作的内部表表达式的括号。 可以忽略其他操作数(外部表的操作数)的括号。

以下表达式:

(t1,t2)LEFT JOIN t3 ON P(t2.b,t3.b)

相当于这个表达式的任何表 t1,t2,t3 和任何条件 P 在属性 t2.b t3.b

t1,t2 LEFT JOIN t3 ON P(t2.b,t3.b)

每当连接表达式( joined_table )中 的连接操作的执行顺序 不是从左到右时,我们就讨论嵌套连接。 请考虑以下查询:

SELECT * FROM t1 LEFT JOIN(t2 LEFT JOIN t3 ON t2.b = t3.b)ON t1.a = t2.a
  在哪里t1.a> 1

SELECT * FROM t1 LEFT JOIN(t2,t3)ON t1.a = t2.a
  WHERE(t2.b = t3.b OR t2.b IS NULL)AND t1.a> 1

这些查询被认为包含这些嵌套连接:

t2 LEFT JOIN t3 ON t2.b = t3.b.
t2,t3

在第一个查询中,嵌套连接由左连接操作形成。 在第二个查询中,它由内部联接操作形成。

在第一个查询中,可以省略括号:连接表达式的语法结构将指示连接操作的相同执行顺序。 对于第二个查询,不能省略括号,尽管这里的连接表达式可以在没有它们的情况下明确解释。 在我们的扩展语法中, (t2, t3) 第二个查询 的括号 是必需的,尽管理论上可以在没有它们的情况下解析查询:我们仍然会为查询提供明确的语法结构,因为 LEFT JOIN ON 扮演了表达式的左右分隔符的角色 (t2,t3)

前面的例子证明了这些要点:

  • 对于仅涉及内部联接(而不是外部联接)的联接表达式,可以删除括号并从左到右计算联接。 实际上,可以按任何顺序评估表。

  • 通常,对于外连接或与内连接混合的外连接,情况也是如此。 删除括号可能会改变结果。

具有嵌套外连接的查询以与具有内连接的查询相同的管道方式执行。 更确切地说,利用了嵌套循环连接算法的变体。 回想一下嵌套循环连接执行查询的算法(参见 第8.2.1.6节“嵌套循环连接算法” )。 假设3个表的连接查询 T1,T2,T3 具有以下形式:

SELECT * FROM T1 INNER JOIN T2 ON P1(T1,T2)
                 INNER JOIN T3 ON P2(T2,T3)
  P(T1,T2,T3)

这里, P1(T1,T2) P2(T3,T3) 是一些连接条件(上表达),而 P(T1,T2,T3) 超过表中的列的条件 T1,T2,T3

嵌套循环连接算法将以下列方式执行此查询:

对于T1中的每一行t1 {
  对于T2中的每一行t2,使得P1(t1,t2){
    对于T3中的每一行t3,使得P2(t2,t3){
      IF P(t1,t2,t3){
         T:= T 1 || || T2 T3; 输出t;
      }
    }
  }
}

符号 t1||t2||t3 表示通过连接的行的列构成的行 t1 t2 t3 在以下某些示例中, NULL 表名称的显示表示一行 NULL 用于该表的每一列。 例如, t1||t2||NULL 指示通过连接的行的列构成的行 t1 t2 ,以及 NULL 对于每一列 t3 据说这样一行是完成的 NULL

现在考虑使用嵌套外连接的查询:

SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON P2(T2,T3))
              ON P1(T1,T2)
  P(T1,T2,T3)

对于此查询,修改嵌套循环模式以获取:

对于T1中的每一行t1 {
  BOOL f1:= FALSE;
  对于T2中的每一行t2,使得P1(t1,t2){
    BOOL f2:= FALSE;
    对于T3中的每一行t3,使得P2(t2,t3){
      IF P(t1,t2,t3){
        T:= T 1 || || T2 T3; 输出t;
      }
      F2 = TRUE;
      F1 = TRUE;
    }
    IF(!f2){
      IF P(t1,t2,NULL){
        T:= T 1 || || T2 NULL; 输出t;
      }
      F1 = TRUE;
    }
  }
  IF(!f1){
    IF P(t1,NULL,NULL){
      T:= T 1 || || NULL NULL; 输出t;
    }
  }
}

通常,对于外连接操作中第一个内部表的任何嵌套循环,引入一个在循环之前关闭并在循环之后检查的标志。 当对于来自外部表的当前行,找到与表示内部操作数的表的匹配时,该标志被打开。 如果在循环周期结束时标志仍然关闭,则找不到外部表的当前行的匹配项。 在这种情况下,行由 NULL 内部表的列的值 补充 结果行将传递给输出的最终检查或下一个嵌套循环,但前提是该行满足所有嵌入外连接的连接条件。

在该示例中,嵌入了由以下表达式表示的外连接表:

(T2 LEFT JOIN T3 ON P2(T2,T3))

对于具有内部联接的查询,优化程序可以选择不同的嵌套循环顺序,例如:

对于T3中的每一行t3 {
  对于T2中的每一行t2,使得P2(t2,t3){
    对于T1中的每一行t1,使得P1(t1,t2){
      IF P(t1,t2,t3){
         T:= T 1 || || T2 T3; 输出t;
      }
    }
  }
}

对于具有外连接的查询,优化器只能选择这样的顺序,其中外部表的循环位于内部表的循环之前。 因此,对于具有外连接的查询,只能有一个嵌套顺序。 对于以下查询,优化程序将评估两种不同的嵌套。 在两个嵌套中, T1 必须在外部循环中处理,因为它在外部联接中使用。 T2 T3 在内部联接中使用,因此必须在内部循环中处理联接。 但是,因为连接是内连接, T2 并且 T3 可以按任何顺序处理。

SELECT * T1 LEFT JOIN(T2,T3)ON P1(T1,T2)和P2(T1,T3)
  P(T1,T2,T3)

一个嵌套评估 T2 ,然后 T3

对于T1中的每一行t1 {
  BOOL f1:= FALSE;
  对于T2中的每一行t2,使得P1(t1,t2){
    对于T3中的每一行t3,使得P2(t1,t3){
      IF P(t1,t2,t3){
        T:= T 1 || || T2 T3; 输出t;
      }
      F1:= TRUE
    }
  }
  IF(!f1){
    IF P(t1,NULL,NULL){
      T:= T 1 || || NULL NULL; 输出t;
    }
  }
}

另一个嵌套评估 T3 ,然后 T2

对于T1中的每一行t1 {
  BOOL f1:= FALSE;
  对于T3中的每一行t3,使得P2(t1,t3){
    对于T2中的每一行t2,使得P1(t1,t2){
      IF P(t1,t2,t3){
        T:= T 1 || || T2 T3; 输出t;
      }
      F1:= TRUE
    }
  }
  IF(!f1){
    IF P(t1,NULL,NULL){
      T:= T 1 || || NULL NULL; 输出t;
    }
  }
}

在讨论内部联接的嵌套循环算法时,我们省略了一些细节,这些细节对查询执行性能的影响可能很大。 我们没有提到所谓的 推倒 条件。 假设我们的 WHERE 条件 P(T1,T2,T3) 可以用一个连接公式表示:

P(T1,T2,T2)= C1(T1)和C2(T2)和C3(T3)。

在这种情况下,MySQL实际上使用以下嵌套循环算法来执行带有内连接的查询:

对于T1中的每一行t1,使得C1(t1){
  对于T2中的每一行t2,使得P1(t1,t2)和C2(t2){
    对于T3中的每一行t3,使得P2(t2,t3)和C3(t3){
      IF P(t1,t2,t3){
         T:= T 1 || || T2 T3; 输出t;
      }
    }
  }
}

你看,每个合取的 C1(T1) C2(T2) C3(T3) 是最内环的推到最外环的地方进行评估。 如果 C1(T1) 是一个非常严格的条件,这种情况下推可能会大大减少 T1 传递给内部循环的 表中的行数 结果,查询的执行时间可能会大大改善。

对于具有外部联接的查询, WHERE 仅在发现外部表中的当前行与内部表中的匹配项之后才检查条件。 因此,从内嵌套循环中推出条件的优化不能直接应用于具有外连接的查询。 这里我们必须引入条件下推谓词,这些谓词由遇到匹配时打开的标志保护。

回想一下外连接的这个例子:

P(T1,T2,T3)= C1(T1)和C(T2)和C3(T3)

对于该示例,使用受保护的下推条件的嵌套循环算法如下所示:

对于T1中的每一行t1,使得C1(t1){
  BOOL f1:= FALSE;
  对于T2中的每一行t2
      这样P1(t1,t2)AND(f1?C2(t2):TRUE){
    BOOL f2:= FALSE;
    对于T3中的每一行t3
        这样P2(t2,t3)AND(f1 && f2?C3(t3):TRUE){
      IF(f1 && f2?TRUE:(C2(t2)AND C3(t3))){
        T:= T 1 || || T2 T3; 输出t;
      }
      F2 = TRUE;
      F1 = TRUE;
    }
    IF(!f2){
      IF(f1?TRUE:C2(t2)&& P(t1,t2,NULL)){
        T:= T 1 || || T2 NULL; 输出t;
      }
      F1 = TRUE;
    }
  }
  IF(!f1 && P(t1,NULL,NULL)){
      T:= T 1 || || NULL NULL; 输出t;
  }
}

通常,可以从诸如 P1(T1,T2) 和的 连接条件中提取下推谓词 P(T2,T3) 在这种情况下,下推谓词也受到一个标志的保护,该标志阻止检查 NULL 由相应的外连接操作生成 - 实现行 的谓词

如果由 WHERE 条件中 的谓词引起,则禁止在同一嵌套连接中通过键从一个内部表访问另一个内部表

8.2.1.8外连接优化

外连接包括 LEFT JOIN RIGHT JOIN

MySQL实现 如下: A LEFT JOIN B join_specification

  • B 设置为依赖于表 A 和所有 依赖的表 A

  • A 设置为依赖于 条件 B 中使用的 所有表(除外 LEFT JOIN

  • LEFT JOIN 条件用于决定如何从表中检索行 B (换句话说, WHERE 不使用 子句中的 任何条件 。)

  • 执行所有标准连接优化,但始终在所依赖的所有表之后读取表。 如果存在循环依赖关系,则会发生错误。

  • WHERE 执行 所有标准 优化。

  • 如果 A 匹配该 WHERE 子句 的行中有一行 ,但没有 B 匹配 ON 条件的 B 行,则会生成 一个额外的 行,并将所有列设置为 NULL

  • 如果您用于 LEFT JOIN 查找某些表中不存在的行,并且您有以下测试: col_name IS NULL WHERE 部件中,哪 col_name 一个是声明为的列 NOT NULL ,MySQL在找到后停止搜索更多行(对于特定的键组合)一行符合 LEFT JOIN 条件。

RIGHT JOIN 实现类似于 LEFT JOIN 表格角色颠倒的实现。 右连接转换为等效的左连接,如 第8.2.1.9节“外连接简化”中所述

对于a LEFT JOIN ,如果 WHERE 生成的 NULL 条件始终为false ,则将 LEFT JOIN 其更改为内部联接。 例如, WHERE 如果条款是在下面的查询错误的 t2.column1 NULL

SELECT * FROM t1 LEFT JOIN t2 ON(column1)WHERE t2.column2 = 5;

因此,将查询转换为内部联接是安全的:

SELECT * FROM t1,t2 WHERE t2.column2 = 5 AND t1.column1 = t2.column1;

在MySQL 8.0.14及更高版本中, WHERE 由常量文字表达式引起的 微不足道的 条件在准备期间被移除,而不是在优化的后期阶段被移除,此时连接已经被简化。 早期删除琐碎的条件允许优化器将外连接转换为内连接; 这可以改善包含 WHERE 子句中 包含琐碎条件的外连接的查询计划 ,例如:

SELECT * FROM t1 LEFT JOIN t2 ON condition_1WHERE condition_2OR 0 = 1

优化器现在在准备期间看到0 = 1始终为false,使得 OR 0 = 1 冗余,并将其删除,留下:

SELECT * FROM t1 LEFT JOIN t2 ON condition_1在哪里condition_2

现在,优化器可以将查询重写为内部联接,如下所示:

SELECT * FROM t1 JOIN t2 WHERE condition_1ANDcondition_2

现在优化器可以在表 t2 之前 使用表 t1 如果这样做会导致更好的查询计划。 要提供有关表连接顺序的提示,请使用优化程序提示; 请参见 第8.9.3节“优化程序提示” 或者,使用 STRAIGHT_JOIN ; 请参见 第13.2.10节“SELECT语法” 但是, STRAIGHT_JOIN 可能会阻止使用索引,因为它会禁用半连接转换; 请参见 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”

8.2.1.9外连接简化

FROM 在许多情况下,简化了查询子句中的 表表达式

在解析器阶段,具有右外连接操作的查询将转换为仅包含左连接操作的等效查询。 在一般情况下,执行转换以使此右连接:

(T1,...)RIGHT JOIN(T2,...)ON P(T1,...,T2,...)

成为等效的左连接:

(T2,...)LEFT JOIN(T1,...)ON P(T1,...,T2,...)

表单的所有内连接表达式 T1 INNER JOIN T2 ON P(T1,T2) 都被列表替换 T1,T2 P(T1,T2) 作为 WHERE 条件的连接(或嵌入连接的连接条件,如果有的话)连接。

当优化器评估外连接操作的计划时,它仅考虑计划,对于每个此类操作,在内部表之前访问外部表。 优化器选项是有限的,因为只有这样的计划才能使用嵌套循环算法执行外连接。

考虑一下这个表单的查询,其中 R(T2) 大大缩小了表中匹配行的数量 T2

SELECT * T1 LEFT JOIN T2 ON P1(T1,T2)
  P(T1,T2)和R(T2)

如果查询是按写入的方式执行的,那么优化器别无选择,只能在限制 T1 较多的 之前 访问限制较少的 T2 ,这可能会产生效率非常低的执行计划。

相反,如果 WHERE 条件为空拒绝 ,MySQL会将查询转换为不带外连接操作的查询 (也就是说,它将外连接转换为内连接。)如果一个条件计算为 FALSE UNKNOWN 为任何 NULL 为该操作生成的已完成行,则该 条件对于外连接操作被称为空拒绝

因此,对于此外连接:

T1 LEFT JOIN T2 ON T1.A = T2.A

诸如此类的条件被null拒绝,因为对于任何已完成的 NULL 行( T2 列设置为 NULL ), 它们不能为真

T2.B不是空的
T2.B> 3
T2.C <= T1.C
T2.B <2或T2.C> 1

诸如此类的条件不会被拒绝,因为它们对于已完成的 NULL 可能是真的

T2.B是空的
T1.B <3或T2.B不为空
T1.B <3或T2.B> 3

检查外部联接操作的条件是否为空拒绝的一般规则很简单:

  • 它是形式 A IS NOT NULL ,其中 A 是任何内部表的属性

  • 它是一个谓词,包含对内部表的引用,该内部表计算 UNKNOWN 其参数之一的时间 NULL

  • 它是一个包含null-rejected条件作为合取的连接

  • 它是零拒绝条件的分离

对于查询中的一个外部联接操作,条件可以为null,而对于另一个条件,则不为null-rejected。 在此查询中, WHERE 对于第二个外部联接操作 条件为空 - 拒绝,但对于第一个外部联接操作,条件不为空 - 拒绝:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A = T1.A
                 左接点T3接通T3.B = T1.B
  在哪里T3.C> 0

如果 WHERE 查询中的外连接操作 条件为空 - 拒绝,则外连接操作将由内连接操作替换。

例如,在前面的查询中,第二个外部联接是空的拒绝,可以由内部联接替换:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A = T1.A
                 INNER JOIN T3 ON T3.B = T1.B
  在哪里T3.C> 0

对于原始查询,优化程序仅评估与单个表访问顺序兼容的计划 T1,T2,T3 对于重写的查询,它还考虑访问顺序 T3,T1,T2

一个外连接操作的转换可以触发另一个外连接操作的转换。 因此,查询:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A = T1.A
                 LE3 JOIN T3 ON T3.B = T2.B
  在哪里T3.C> 0

首先转换为查询:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A = T1.A
                 INNER JOIN T3 ON T3.B = T2.B
  在哪里T3.C> 0

这相当于查询:

SELECT * FROM(T1 LEFT JOIN T2 ON T2.A = T1.A),T3
  T3.C> 0且T3.B = T2.B

剩余的外连接操作也可以由内连接替换,因为条件 T3.B=T2.B 为空拒绝。 这导致查询没有外部联接:

SELECT * FROM(T1 INNER JOIN T2 ON T2.A = T1.A),T3
  T3.C> 0且T3.B = T2.B

有时,优化器会成功替换嵌入式外连接操作,但无法转换嵌入外连接。 以下查询:

SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON T3.B = T2.B)
              在T2.A = T1.A
  在哪里T3.C> 0

转换为:

SELECT * FROM T1 LEFT JOIN
              (T2 INNER JOIN T3 ON T3.B = T2.B)
              在T2.A = T1.A
  在哪里T3.C> 0

这只能重写为仍然包含嵌入外连接操作的表单:

SELECT * FROM T1 LEFT JOIN
              (T2,T3)
              ON(T2.A = T1.A和T3.B = T2.B)
  在哪里T3.C> 0

在查询中转换嵌入式外连接操作的任何尝试都必须考虑嵌入外连接和 WHERE 条件的 连接 条件。 在此查询中, WHERE 嵌入式外连接 条件不会被拒绝,但嵌入外连接的连接条件 T2.A=T1.A AND T3.C=T1.C 为空拒绝:

SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON T3.B = T2.B)
              在T2.A = T1.A和T3.C = T1.C
  在哪里T3.D> 0或T1.D> 0

因此,查询可以转换为:

SELECT * FROM T1 LEFT JOIN
              (T2,T3)
              在T2.A = T1.A和T3.C = T1.C和T3.B = T2.B
  在哪里T3.D> 0或T1.D> 0

8.2.1.10多范围读取优化

当表很大并且没有存储在存储引擎的缓存中时,使用辅助索引上的范围扫描读取行可能导致对基表的许多随机磁盘访问。 通过磁盘扫描多范围读取(MRR)优化,MySQL尝试通过首先扫描索引并收集相关行的密钥来减少范围扫描的随机磁盘访问次数。 然后对键进行排序,最后使用主键的顺序从基表中检索行。 磁盘扫描MRR的动机是减少随机磁盘访问的数量,而是实现对基表数据的更顺序扫描。

多范围读取优化提供以下好处:

  • MRR使数据行能够按顺序而不是按随机顺序访问,具体取决于索引元组。 服务器获取一组满足查询条件的索引元组,根据数据行ID顺序对它们进行排序,并使用排序的元组按顺序检索数据行。 这使数据访问更有效,更便宜。

  • MRR允许批量处理需要通过索引元组访问数据行的操作的密钥访问请求,例如范围索引扫描和使用连接属性索引的等连接。 MRR迭代一系列索引范围以获得合格的索引元组。 随着这些结果的累积,它们用于访问相应的数据行。 在开始读取数据行之前,不必获取所有索引元组。

在虚拟生成列上创建的二级索引不支持MRR优化。 InnoDB 支持虚拟生成列上的二级索引。

以下方案说明了MRR优化何时有利:

场景A:MRR可 用于索引范围扫描和等连接操作的表 InnoDB MyISAM 表。

  1. 索引元组的一部分累积在缓冲区中。

  2. 缓冲区中的元组按其数据行ID排序。

  3. 根据排序的索引元组序列访问数据行。

场景B:MRR可 NDB 用于多范围索引扫描的表或通过属性执行等连接时。

  1. 范围的一部分(可能是单键范围)累积在提交查询的中心节点上的缓冲区中。

  2. 范围被发送到访问数据行的执行节点。

  3. 访问的行被打包到包中并发送回中央节点。

  4. 收到的包含数据行的包放在缓冲区中。

  5. 从缓冲区读取数据行。

使用MRR时 Extra EXPLAIN 输出中 显示 Using MRR

InnoDB MyISAM 如果不需要访问完整的表行来生成查询结果 则不要使用MRR。 如果结果可以完全基于索引元组中的信息(通过 覆盖索引 )生成,则情况就是这样; MRR没有任何好处。

两个 optimizer_switch 系统变量标志提供了使用MRR优化的接口。 mrr 标志控制是否启用MRR。 如果 mrr 是enabled( on ),则该 mrr_cost_based 标志控制优化器是否尝试在使用和不使用MRR( on 之间进行基于成本的选择, 或者尽可能使用MRR( off )。 默认情况下, mrr on mrr_cost_based on 请参见 第8.9.2节“可切换的优化”

对于MRR,存储引擎使用 read_rnd_buffer_size 系统变量 的值 作为其缓冲区可分配的内存量的指导。 引擎最多使用 read_rnd_buffer_size 字节数,并确定一次传递中要处理的范围数。

8.2.1.11块嵌套循环和批量密钥访问连接

在MySQL中,可以使用批量密钥访问(BKA)连接算法,该算法使用对连接表的索引访问和连接缓冲区。 BKA算法支持内连接,外连接和半连接操作,包括嵌套外连接。 BKA的优点包括提高连接性能,因为更高效的表扫描。 此外,先前仅用于内连接的块嵌套循环(BNL)连接算法已扩展,可用于外连接和半连接操作,包括嵌套外连接。

以下部分讨论了连接缓冲区管理,它是原始BNL算法扩展,扩展BNL算法和BKA算法的基础。 有关半连接策略的信息,请参见 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”

加入块嵌套循环和批量密钥访问算法的缓冲区管理

MySQL可以使用连接缓冲区不仅可以执行内部联接而无需对内部表进行索引访问,还可以执行子查询展平后出现的外部联接和半联接。 此外,当存在对内部表的索引访问时,可以有效地使用连接缓冲区。

当存储感兴趣的行列的值时,联接缓冲区管理代码稍微更有效地利用连接缓冲区空间:如果行的值为,则不在缓冲区中为行列分配额外的字节 NULL ,并且为任何值分配最小字节数该 VARCHAR 类型。

该代码支持两种类型的缓冲区,常规和增量。 假设使用连接缓冲区 B1 来连接表 t1 t2 并且 t3 使用连接缓冲区 将此操作的结果与表 连接 B2

  • 常规连接缓冲区包含每个连接操作数的列。 如果 B2 是一个普通的加入缓冲液,每行 r 投入 B2 由行的列 r1 B1 和匹配行的有趣列 r2 从表 t3

  • 增量连接缓冲区仅包含第二个连接操作数生成的表行中的列。 也就是说,它是第一个操作数缓冲区中一行的增量。 如果 B2 是增量加入缓冲液,它包含行的有趣列 r2 有链接到一起排 r1 B1

增量连接缓冲区始终相对于早期连接操作的连接缓冲区是增量的,因此第一个连接操作的缓冲区始终是常规缓冲区。 在刚刚给出的实例中,缓冲 B1 用来连接表 t1 t2 必须是常规缓冲区。

用于连接操作的增量缓冲区的每一行仅包含要连接的表中一行的有趣列。 这些列通过引用第一个连接操作数生成的表中匹配行的有趣列进行扩充。 增量缓冲区中的多行可以引用同一行, r 的列存储在先前的连接缓冲区中,只要所有这些行都匹配行 r

增量缓冲区可以减少从先前连接操作使用的缓冲区中复制列的频率。 这提供了缓冲空间的节省,因为在一般情况下,由第一连接操作数产生的行可以由第二连接操作数产生的若干行匹配。 不必从第一个操作数生成一行的多个副本。 由于复制时间的减少,增量缓冲区还可以节省处理时间。

系统变量 block_nested_loop batched_key_access 标志 optimizer_switch 控制优化器如何使用块嵌套循环和批量密钥访问连接算法。 默认情况下, block_nested_loop on batched_key_access off 请参见 第8.9.2节“可切换的优化” 也可以应用优化程序提示; 请参阅 块嵌套循环和批量密钥访问算法的优化程序提示

有关半连接策略的信息,请参见 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”

用于外连接和半连接的块嵌套循环算法

MySQL BNL算法的原始实现被扩展为支持外连接和半连接操作。

当使用连接缓冲区执行这些操作时,为缓冲区中的每一行提供匹配标志。

如果使用连接缓冲区执行外连接操作,则检查第二个操作数生成的表的每一行是否与连接缓冲区中的每一行匹配。 找到匹配项后,将形成一个新的扩展行(原始行加上第二个操作数中的列),并通过其余的连接操作发送以进一步扩展。 此外,启用缓冲区中匹配行的匹配标志。 在检查了要连接的表的所有行之后,将扫描连接缓冲区。 缓冲区中未启用其匹配标志的每一行都由 NULL 补充 扩展 NULL 第二个操作数中每列的值),并由剩余的连接操作发送以进一步扩展。

系统变量 block_nested_loop 标志 optimizer_switch 控制优化器如何使用块嵌套循环算法。 默认情况下 block_nested_loop on 请参见 第8.9.2节“可切换的优化” 也可以应用优化程序提示; 请参阅 块嵌套循环和批量密钥访问算法的优化程序提示

EXPLAIN 输出端,为表使用BNL的当被所指 Extra 值包含 Using join buffer (Block Nested Loop) 与所述 type 值是 ALL index ,或 range

有关半连接策略的信息,请参见 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”

批量密钥访问连接

MySQL实现了一种连接表的方法,称为批量密钥访问(BKA)连接算法。 当存在对第二个连接操作数生成的表的索引访问时,可以应用BKA。 与BNL连接算法一样,BKA连接算法使用连接缓冲区来累积由连接操作的第一个操作数生成的行的有趣列。 然后,BKA算法构建密钥以访问要为缓冲区中的所有行加入的表,并将这些密钥批量提交到数据库引擎以进行索引查找。 键通过多量程读取(MRR)接口提交给引擎(参见 第8.2.1.10节“多量程读取优化”) )。 在提交密钥之后,MRR引擎功能以最佳方式在索引中执行查找,获取由这些密钥找到的连接表的行,并开始向匹配行提供BKA连接算法。 每个匹配行与对连接缓冲区中的行的引用相耦合。

使用BKA时,值 join_buffer_size 定义了每个请求到存储引擎的批量密钥的大小。 缓冲区越大,对连接操作的右侧表的顺序访问就越多,这可以显着提高性能。

要使用BKA, 必须将系统变量 batched_key_access 标志 optimizer_switch 设置为 on BKA使用MRR,因此 mrr 标志也必须是 on 目前,MRR的成本估算过于悲观。 因此,也有必要对 mrr_cost_based off 用于要使用的BKA。 以下设置启用BKA:

MySQL的> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

MRR功能有两种执行方式:

  • 第一种方案用于传统的基于磁盘的存储引擎,例如 InnoDB MyISAM 对于这些引擎,通常将来自连接缓冲区的所有行的键一次提交给MRR接口。 特定于引擎的MRR函数对提交的密钥执行索引查找,从中获取行ID(或主键),然后根据BKA算法的请求逐个获取所有这些选定行ID的行。 返回的每一行都带有一个关联引用,该引用允许访问连接缓冲区中的匹配行。 MRR函数以最佳方式获取行:它们以行ID(主键)顺序获取。 这提高了性能,因为读取是按磁盘顺序而不是随机顺序。

  • 第二种方案用于远程存储引擎,例如 NDB 来自连接缓冲区的一部分行的密钥包及其关联由MySQL服务器(SQL节点)发送到MySQL Cluster数据节点。 作为回报,SQL节点接收匹配行的包(或多个包)以及相应的关联。 BKA连接算法获取这些行并构建新的连接行。 然后,一组新的密钥被发送到数据节点,并且返回的包中的行用于构建新的连接行。 该过程将继续,直到将来自联接缓冲区的最后一个密钥发送到数据节点,并且SQL节点已接收并加入与这些密钥匹配的所有行。

对于第一种情况,保留一部分连接缓冲区以存储由索引查找选择的行ID(主键),并作为参数传递给MRR功能。

没有特殊的缓冲区来存储为连接缓冲区中的行构建的密钥。 相反,为缓冲区中的下一行构建键的函数作为参数传递给MRR函数。

EXPLAIN 输出中,当 Extra 值包含 Using join buffer (Batched Key Access) type 值为 ref 时,表示将BKA用于表 eq_ref

块嵌套循环和批量密钥访问算法的优化器提示

除了使用 optimizer_switch 系统变量来控制优化程序在会话范围内使用BNL和BKA算法之外,MySQL还支持优化程序提示,以便在每个语句的基础上影​​响优化程序。 请参见 第8.9.3节“优化程序提示”

要使用BNL或BKA提示为外部联接的任何内部表启用联接缓冲,必须为外部联接的所有内部表启用联接缓冲。

8.2.1.12条件过滤

在连接处理中,前缀行是从连接中的一个表传递到下一个表的那些行。 通常,优化器尝试在连接顺序的早期放置具有低前缀计数的表,以保持行组合的数量不会快速增加。 在某种程度上,优化程序可以使用有关从一个表中选择并传递给下一个表的行的条件的信息,它可以更准确地计算行估计并选择最佳执行计划。

如果没有条件筛选,则表的前缀行计数基于 WHERE 子句根据优化程序选择的访问方法选择 的估计行数 条件筛选使优化器能够使用 WHERE 访问方法未考虑 子句 中的其他相关条件 ,从而改进其前缀行计数估计。 例如,即使可能存在可用于从连接中的当前表中选择行的基于索引的访问方法,但在该表中可能还存在其他条件。 WHERE 可以过滤(进一步限制)传递给下一个表的合格行的估计的子句。

只有在以下情况下,条件才会影响过滤估算:

  • 它指的是当前表。

  • 它取决于连接序列中早期表的常量值或值。

  • 访问方法尚未考虑它。

EXPLAIN 输出中, rows 列指示所选访问方法的行估计,该 filtered 列反映条件筛选的效果。 filtered 值以百分比表示。 最大值为100,这意味着不会对行进行过滤。 值从100开始减少表示过滤量增加。

前缀行计数(估计从连接中的当前表传递到下一个的行数)是 rows filtered 的乘积 也就是说,前缀行计数是估计的行计数,通过估计的过滤效果减少。 例如,如果 rows 是1000并且 filtered 是20%,则条件过滤将估计的行计数1000减少到前缀行计数1000×20%= 1000×.2 = 200。

请考虑以下查询:

选择 *
  来自员工JOIN部门ON employee.dept_no = department.dept_no
  WHERE employee.first_name ='John'
  AND employee.hire_date BETWEEN'2018-01-01'EN'2018-06-01';

假设数据集具有以下特征:

  • employee 表有1024行。

  • department 表有12行。

  • 两个表都有一个索引 dept_no

  • employee 表有一个索引 first_name

  • 8行符合以下条件 employee.first_name

    employee.first_name ='约翰'
    
  • 150行符合以下条件 employee.hire_date

    employee.hire_date BETWEEN'2018-01-01'和'2018-06-01'
    
  • 1行满足两个条件:

    employee.first_name ='约翰'
    AND employee.hire_date BETWEEN'2018-01-01'和'2018-06-01'
    

没有条件过滤, EXPLAIN 产生如下输出:

+ ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- +
| id | 表| 类型| possible_keys | 关键| ref | 行| 过滤|
+ ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- +
| 1 | 员工| ref | name,h_date,dept | 名字| const | 8 | 100.00 |
| 1 | 部门| eq_ref | 主要| 主要| dept_no | 1 | 100.00 |
+ ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- +

因为 employee name 索引 上的访问方法会 获取与名称匹配的8行 'John' 没有进行过滤( filtered 是100%),因此所有行都是下一个表的前缀行:前缀行计数是 rows × filtered = 8×100%= 8。

通过条件过滤,优化器还考虑 WHERE 了访问方法未考虑 子句中的 条件 在这种情况下,优化器使用试探法来估计所述的16.31%的滤波效果 BETWEEN 的条件下 employee.hire_date 结果, EXPLAIN 产生如下输出:

+ ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- +
| id | 表| 类型| possible_keys | 关键| ref | 行| 过滤|
+ ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- +
| 1 | 员工| ref | name,h_date,dept | 名字| const | 8 | 16.31 |
| 1 | 部门| eq_ref | 主要| 主要| dept_no | 1 | 100.00 |
+ ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- +

现在前缀行计数为 rows × filtered = 8×16.31%= 1.3,这更接近地反映了实际数据集。

通常,优化器不会为最后一个连接表计算条件过滤效果(前缀行数减少),因为没有下一个表要传递行。 发生异常 EXPLAIN :要提供更多信息,将为所有已连接的表(包括最后一个表)计算过滤效果。

要控制优化程序是否考虑其他过滤条件,请使用 系统变量 condition_fanout_filter 标志 optimizer_switch (请参见 第8.9.2节“可切换的优化” )。 默认情况下启用此标志,但可以禁用此标志以禁止条件筛选(例如,如果发现特定查询在没有它的情况下产生更好的性能)。

如果优化器过高估计条件过滤的影响,则性能可能比不使用条件过滤时更差。 在这种情况下,这些技术可能有所帮助:

  • 如果未对列建立索引,请对其进行索引,以便优化程序具有有关列值分布的一些信息,并可以改进其行估计值。

  • 同样,如果没有可用的列直方图信息,则生成直方图(请参见 第8.9.6节“优化程序统计信息” )。

  • 更改连接顺序。 完成此操作的方法包括连接顺序优化器提示(请参见 第8.9.3节“优化程序提示” ), STRAIGHT_JOIN 紧跟在 SELECT STRAIGHT_JOIN 连接运算符之后。

  • 禁用会话的条件筛选:

    SET optimizer_switch ='condition_fanout_filter = off';
    

    或者,对于给定查询,使用优化程序提示:

    SELECT / * + SET_VAR(optimizer_switch ='condition_fanout_filter = off')* / ...
    

8.2.1.13恒定折叠优化

常量值和列值之间的比较,其中常量值超出范围或相对于列类型的错误类型现在在查询优化期间而不是逐行处理而不是在执行期间处理一次。 可以以这种方式处理的比较是 > >= < <= <> / != = <=>

考虑以下语句创建的表:

CREATE TABLE t(c TINYINT UNSIGNED NOT NULL);

所述 WHERE 查询条件 SELECT * FROM t WHERE c < 256 包含积分常数256,其超出范围为 TINYINT UNSIGNED 柱。 以前,这是通过将两个操作数视为较大的类型来处理的,但现在,由于任何允许的值 c 小于常量, WHERE 表达式可以折叠为 WHERE 1 ,以便将查询重写为 SELECT * FROM t WHERE 1

这使得优化器可以 WHERE 完全 删除 表达式。 如果列可以 c 为空(即,仅定义为 TINYINT UNSIGNED ),则将重写查询,如下所示:

SELECT * FROM t WHERE ti not NULL

与支持的MySQL列类型相比,对常量执行折叠,如下所示:

  • 整数列类型。  将整数类型与以下类型的常量进行比较,如下所述:

    • 整数值。  如果常量超出列类型的范围,则比较将折叠为 1 IS NOT NULL ,如已显示。

      如果常量是范围边界,则比较折叠为 = 例如(使用与已定义相同的表):

      MySQL的> EXPLAIN SELECT * FROM t WHERE c >= 255;
      *************************** 1。排******************** *******
                 id:1
        select_type:SIMPLE
              表:t
         分区:NULL
               类型:全部
      possible_keys:NULL
                key:NULL
            key_len:NULL
                ref:NULL
               行:5
           过滤:20.00
              额外:使用在哪里
      1排,1警告(0.00秒)
      
      MySQL的> SHOW WARNINGS;
      *************************** 1。排******************** *******
        等级:注意
         代码:1003
      消息:/ *从`test``t`中选择#1 * / select`test``t``ti` AS`ti`,其中(`test``t``ti` = 255)
      1排(0.00秒)
      
    • 浮点或定点值。  如果常数是十进制类型之一(例如 DECIMAL REAL DOUBLE ,或 FLOAT ),并有一个非零小数部分,它可以是不相等的; 相应地折叠。 对于其他比较,根据符号向上或向下舍入为整数值,然后执行范围检查和处理,如已经描述的整数 - 整数比较。

      如果 REAL 值太小而无法表示, DECIMAL 则根据符号舍入为.01或-.01,然后作为a处理 DECIMAL

    • 字符串类型。  尝试将字符串值解释为整数类型,然后在整数值之间处理比较。 如果失败,请尝试将值作为a处理 REAL

  • DECIMAL或REAL列。  将十进制类型与以下类型的常量进行比较,如下所述:

    • 整数值。  对列值的整数部分执行范围检查。 如果没有折叠结果,将常量转换 DECIMAL 为与列值相同的小数位数,然后将其检查为a DECIMAL (参见下一个)。

    • DECIMAL或REAL值。  检查溢出(即,常量的整数部分中的数字是否多于列的十进制类型所允许的数字)。 如果是这样,折叠。

      如果常量具有比列的类型更重要的小数位数,则截断常量。 如果比较运算符是 = <> 折叠。 如果操作员是 >= <= ,则由于截断而调整操作员。 例如,如果列的类型是 DECIMAL(3,1) ,则 SELECT * FROM t WHERE f >= 10.13 变为 SELECT * FROM t WHERE f > 10.1

      如果常量的十进制数字少于列的类型,则将其转换为具有相同位数的常量。 对于 REAL 值的 下溢 (即,表示它的分数位数太少),将常量转换为十进制0。

    • 字符串值。  如果该值可以解释为整数类型,则按此处理。 否则,尝试将其处理为 REAL

  • FLOAT或DOUBLE列。  与常量相比较的值处理如下: FLOAT(m,n) DOUBLE(m,n)

    如果值溢出列的范围,则折叠。

    如果该值大于 n 小数,则截断,在折叠期间进行补偿。 对于 = <> 比较,倍至 TRUE FALSE IS [NOT] NULL 如前文所述; 对于其他运营商,调整运营商。

    如果该值超过 m 整数位数,则折叠。

限制。  在以下情况下不能使用此优化:

  1. 使用 BETWEEN 进行比较 IN

  2. 随着 BIT 使用日期或时间类型的列或列。

  3. 在准备语句的准备阶段,尽管可以在优化阶段应用实际执行准备好的语句。 这是因为在语句准备期间,常数的值尚不清楚。

8.2.1.14 IS NULL优化

MySQL能够执行相同的优化 ,它可以使用 例如,MySQL能使用索引和范围来搜索 col_name IS NULL col_name = constant_value NULL IS NULL

例子:

SELECT * FROM tbl_nameWHERE为key_colNULL;

SELECT * FROM tbl_nameWHERE key_col<=> NULL;

SELECT * FROM tbl_name
  WHERE key_col= const1OR key_col= const2OR为key_colNULL;

如果 WHERE 子句包含 声明为的列 条件,则 表达式将被优化掉。 当列可能 无论如何 产生时 (例如,如果它来自a右侧的表),则 不会发生此优化 col_name IS NULL NOT NULL NULL LEFT JOIN

MySQL还可以优化组合 ,这是一种在已解析子查询中很常见的形式。 显示 何时使用此优化。 col_name = expr OR col_name IS NULL EXPLAIN ref_or_null

此优化可以处理 IS NULL 任何关键部分。

假设列 a b 上有索引,优化的查询的一些示例 t2

SELECT * FROM t1 WHERE t1.a = exprOR t1.a IS NULL;

SELECT * FROM t1,t2 WHERE t1.a = t2.a OR t2.a IS NULL;

SELECT * FROM t1,t2
  WHERE(t1.a = t2.a OR t2.a IS NULL)AND t2.b = t1.b;

SELECT * FROM t1,t2
  在哪里t1.a = t2.a AND(t2.b = t1.b或t2.b IS NULL);

SELECT * FROM t1,t2
  WHERE(t1.a = t2.a AND t2.a IS NULL AND ...)
  或(t1.a = t2.a AND t2.a IS NULL AND ...);

ref_or_null 首先对引用键执行读操作,然后单独搜索具有 NULL 键值的

优化只能处理一个 IS NULL 级别。 在以下查询中,MySQL仅对表达式使用键查找, (t1.a=t2.a AND t2.a IS NULL) 并且无法使用关键部分 b

SELECT * FROM t1,t2
  WHERE(t1.a = t2.a AND t2.a IS NULL)
  或(t1.b = t2.b AND t2.b IS NULL);

8.2.1.15按优化顺序

本节描述MySQL何时可以使用索引来满足 ORDER BY 子句, 无法使用索引时使用 filesort 操作,以及优化器提供的执行计划信息 ORDER BY

一个 ORDER BY 有和没有 LIMIT 可能以不同的顺序返回行,在讨论 第8.2.1.18,“LIMIT查询优化”

使用索引来满足ORDER BY

在某些情况下,MySQL可能会使用索引来满足一个 ORDER BY 子句,并避免执行 filesort 操作时 涉及的额外排序

ORDER BY 只要索引的所有未使用部分和所有额外 ORDER BY 列都是 WHERE 子句 中的常量, 即使 索引与索引不完全匹配, 也可以使用 索引 如果索引不包含查询访问的所有列,则仅在索引访问比其他访问方法更便宜时才使用索引。

假设存在索引 ,则以下查询可以使用索引来解析该 部分。 优化程序是否实际执行此操作取决于读取索引是否比表扫描更有效,如果还必须读取索引中不存在的列。 (key_part1, key_part2) ORDER BY

  • 在此查询中,索引on 使优化器能够避免排序: (key_part1, key_part2)

    SELECT * FROM t1
      ORDER BY key_part1key_part2;
    

    但是,查询使用 SELECT * ,可以选择比 key_part1 更多的列 key_part2 在这种情况下,扫描整个索引并查找表行以查找不在索引中的列可能比扫描表并对结果进行排序更昂贵。 如果是这样,优化器可能不会使用索引。 如果 SELECT * 仅选择索引列,则将使用索引并避免排序。

    如果 t1 InnoDB 表,则表主键隐式地是索引的一部分,并且索引可用于解析 ORDER BY 此查询:

    SELECT pkkey_part1key_part2从T1
      ORDER BY key_part1key_part2;
    
  • 在此查询中, key_part1 是常量,因此通过索引访问的所有行都是 key_part2 有序的,并且 如果 子句具有足够的选择性以使索引范围扫描比表扫描更便宜, 索引将 避免排序 (key_part1, key_part2) WHERE

    SELECT * FROM t1
      WHERE key_part1= constant
      ORDER BY key_part2;
    
  • 在接下来的两个查询中,是否使用索引类似于 DESC 之前 显示 的相同查询

    SELECT * FROM t1
      ORDER BY key_part1DESC,key_part2DESC;
    
    SELECT * FROM t1
      WHERE key_part1= DESC constant
      订购key_part2;
    
  • a中的两列 ORDER BY 可以在相同方向(两个 ASC 或两个 DESC )或相反方向(一个 ASC ,一个 DESC 排序 索引使用的条件是索引必须具有相同的同质性,但不必具有相同的实际方向。

    如果查询混合 ASC DESC ,优化器可以使用的列的索引;如果该指数还采用了相应的混合升序和降序列:

    SELECT * FROM t1
      ORC BY key_part1DESC,key_part2ASC;
    

    优化器可以在( key_part1 key_part2 上使用索引 如果 key_part1 是降序并且 key_part2 正在升序。 它还可以在这些列上使用索引(使用向后扫描),如果它 key_part1 是递增的并且 key_part2 正在降序。 请参见 第8.3.13节“降序索引”

  • 在接下来的两个查询中, key_part1 将其与常量进行比较。 如果该 WHERE 子句具有足够的选择性以使索引范围扫描比表扫描更便宜, 则将使用该索引

    SELECT * FROM t1
      在哪里key_part1> ASC constant
      订购key_part1;
    
    SELECT * FROM t1
      在哪里key_part1<按DESC constant
      订购key_part1;
    
  • 在下一个查询中, ORDER BY 没有命名 key_part1 ,但所有选定的行都有一个常 key_part1 量值,因此仍然可以使用索引:

    SELECT * FROM t1
      WHERE key_part1= constant1AND key_part2> constant2
      ORDER BY key_part2;
    

在某些情况下,MySQL 无法 使用索引来解析 ORDER BY ,尽管它仍然可以使用索引来查找与该 WHERE 子句 匹配的行 例子:

  • 该查询用于 ORDER BY 不同的索引:

    SELECT * FROM t1 ORDER BY key1, key2;
    
  • 该查询用于 ORDER BY 索引的非连续部分:

    SELECT * FROM t1 WHERE key2= constantORDER BY key1_part1, key1_part3;
    
  • 用于获取行的索引与以下中使用的索引不同 ORDER BY

    SELECT * FROM t1 WHERE key2= constantORDER BY key1;
    
  • 该查询使用 ORDER BY 包含索引列名以外的术语的表达式:

    SELECT * FROM t1 ORDER BY ABS(key);
    SELECT * FROM t1 ORDER BY  - key;
    
  • 该查询连接了许多表,并且其中的列 ORDER BY 不是来自用于检索行的第一个非常量表。 (这是 EXPLAIN 输出中没有 const 连接类型 的第一个表 。)

  • 查询有不同的 ORDER BY GROUP BY 表情。

  • 只有该 ORDER BY 子句中 指定的列的前缀有一个索引 在这种情况下,索引不能用于完全解析排序顺序。 例如,如果仅 CHAR(20) 索引列 的前10个字节 ,则索引无法区分超过第10个字节的值,并且 filesort 需要a。

  • 索引不按顺序存储行。 例如,对于 表中 HASH 索引 ,这是正确的 MEMORY

用于排序的索引的可用性可能受到列别名的使用的影响。 假设列 t1.a 已编入索引。 在此语句中,选择列表中列的名称为 a 它指的是 t1.a ,引用 a 也是 ORDER BY 如此,因此 t1.a 可以使用 索引

选择一个FROM t1 ORDER BY a;

在此语句中,选择列表中列的名称也是 a ,但它是别名。 它指的是 ABS(a) ,在引用 a ORDER BY ,所以 t1.a 不能使用 索引

SELECT ABS(a)作为一个FROM t1 ORDER by a;

在以下语句中, ORDER BY 引用的名称不是选择列表中列的名称。 但是在 t1 命名中 有一个列 a ,所以 可以使用 ORDER BY 引用 t1.a 和索引 t1.a (当然,生成的排序顺序可能与顺序完全不同 ABS(a) 。)

选择ABS(a)AS b FROM t1 ORDER by a;

以前(MySQL 5.7及更低版本), GROUP BY 在某些条件下隐式排序。 在MySQL 8.0中,不再出现这种情况,因此 ORDER BY NULL 不再需要在最后 指定 抑制隐式排序(如前所述)。 但是,查询结果可能与以前的MySQL版本不同。 要生成给定的排序顺序,请提供一个 ORDER BY 子句。

使用filesort来满足ORDER BY

如果索引不能用于满足 ORDER BY 子句,则MySQL执行 filesort 读取表行并对其进行排序 操作。 A filesort 构成查询执行中的额外排序阶段。

为了获得 filesort 操作的 内存 ,从MySQL 8.0.12开始,优化器根据需要逐步分配内存缓冲区,直到 sort_buffer_size 系统变量 指示的大小 ,而不是 sort_buffer_size 像MySQL 8.0之前那样预先 分配固定数量的 字节。 0.12。 这使用户可以设置 sort_buffer_size 更大的值来加速更大的排序,而不用担心小排序的过多内存使用。 (对于Windows上的多个并发排序,可能不会出现此优势,因为它具有较弱的多线程 malloc 。)

一个 filesort 操作使用临时磁盘文件作为必要的,如果结果集是太大,无法在内存中。 某些类型的查询特别适合于完全内存中的 filesort 操作。 例如,优化器可以 filesort 用来有效地在内存中处理 ORDER BY 以下形式的查询(和子查询) ,而无需临时文件

SELECT ... FROM single_table... ORDER BY non_index_column[DESC] LIMIT [ M,] N;

此类查询在Web应用程序中很常见,只显示较大结果集中的几行。 例子:

SELECT col1,... FROM t1 ... ORDER BY name LIMIT 10;
SELECT col1,... FROM t1 ... ORDER BY RAND()LIMIT 15;
影响ORDER BY优化

对于 未使用的 ORDER BY 查询 filesort ,请尝试将 max_length_for_sort_data 系统变量 降低 到适合触发a的值 filesort (将此变量的值设置得过高的症状是高磁盘活动和低CPU活动的组合。)

要提高 ORDER BY 速度,请检查是否可以让MySQL使用索引而不是额外的排序阶段。 如果无法做到这一点,请尝试以下策略:

  • 增加 sort_buffer_size 变量值。 理想情况下,该值应足够大,以使整个结果集适合排序缓冲区(以避免写入磁盘和合并传递)。

    请考虑存储在排序缓冲区中的列值的大小受 max_sort_length 系统变量值的影响。 例如,如果元组存储长字符串列的值并且您增加了值 max_sort_length ,则排序缓冲区元组的大小也会增加,并且可能需要您增加 sort_buffer_size

    要监视合并传递的数量(合并临时文件),请检查 Sort_merge_passes 状态变量。

  • 增加 read_rnd_buffer_size 变量值,以便一次读取更多行。

  • tmpdir 系统变量 更改 为指向具有大量可用空间的专用文件系统。 变量值可以列出以循环方式使用的几个路径; 您可以使用此功能将负载分散到多个目录中。 : 在Unix上 用冒号字符( 分隔路径, ; 在Windows上用 分号字符( 分隔 路径 路径应命名位于不同 物理 磁盘 上的文件系统中的目录 ,而不是同一磁盘上的不同分区。

按订单执行计划信息

使用 EXPLAIN (参见 第8.8.1节“使用EXPLAIN优化查询” ),可以检查MySQL是否可以使用索引来解析 ORDER BY 子句:

  • 如果 输出 Extra EXPLAIN 不包含 Using filesort ,则使用索引并且 filesort 不执行a。

  • 如果 输出 Extra EXPLAIN 包含 Using filesort ,则不使用索引并 filesort 执行a。

此外,如果 filesort 执行a,则优化程序跟踪输出包括 filesort_summary 块。 例如:

“filesort_summary”:{
  “行”:100,
  “examine_rows”:100,
  “number_of_tmp_files”:0,
  “peak_memory_used”:25192,
  “sort_mode”:“<sort_key,packed_additional_fields>”
}

peak_memory_used 表示排序期间任何时候使用的最大内存。 这是一个值,但不一定与 sort_buffer_size 系统变量 的值一样大 在MySQL 8.0.12之前,输出显示 sort_buffer_size ,表示值 sort_buffer_size (在MySQL 8.0.12之前,优化器总是 sort_buffer_size 为排序缓冲区 分配 字节。从8.0.12开始,优化器逐步分配排序缓冲区内存,从少量开始并根据需要添加更多,最多为 sort_buffer_size 字节。)

sort_mode 值提供有关排序缓冲区中元组内容的信息:

  • <sort_key, rowid> :这表示排序缓冲区元组是包含原始表行的排序键值和行ID的对。 元组按排序键值排序,行ID用于从表中读取行。

  • <sort_key, additional_fields> :这表示排序缓冲区元组包含排序键值和查询引用的列。 元组按排序键值排序,列值直接从元组中读取。

  • <sort_key, packed_additional_fields> :与前一个版本类似,但是附加列紧密地打包在一起而不是使用固定长度编码。

EXPLAIN 不区分优化器是否执行 filesort 内存。 filesort 在优化器跟踪输出中可以看到 内存的使用 寻找 filesort_priority_queue_optimization 有关优化程序跟踪的信息,请参阅 MySQL内部:跟踪优化程序

8.2.1.16 GROUP BY优化

最满意的一般方式 GROUP BY 子句 是扫描整个表并创建一个新的临时表,其中每个组的所有行都是连续的,然后使用此临时表来发现组并应用聚合函数(如果有的话)。 在某些情况下,MySQL能够做得更好,并通过使用索引访问来避免创建临时表。

使用索引的最重要的前提条件 GROUP BY 是所有 GROUP BY 列都引用来自同一索引的属性,并且索引按顺序存储其键(例如,对于 BTREE 索引而不是 HASH 索引)。 是否可以通过索引访问替换临时表的使用还取决于查询中使用索引的哪些部分,为这些部分指定的条件以及所选的聚合函数。

有两种方法可以 GROUP BY 通过索引访问 来执行 查询,如以下各节所述。 第一种方法将分组操作与所有范围谓词(如果有)一起应用。 第二种方法首先执行范围扫描,然后对生成的元组进行分组。

松散的索引扫描也可以 GROUP BY 在某些条件下使用。 请参见 跳过扫描范围访问方法

松散的索引扫描

最有效的处理方法 GROUP BY 是使用索引直接检索分组列。 使用此访问方法,MySQL使用某些索引类型的属性(例如,键 BTREE )。 此属性允许在索引中使用查找组,而无需考虑索引中满足所有 WHERE 条件的 所有键 此访问方法仅考虑索引中的一小部分键,因此称为 松散索引扫描 当没有 子句包含范围谓词时(参见 连接类型 的讨论 WHERE 子句,则松散索引扫描会读取与组数一样多的密钥,这可能比所有密钥的数量小得多。 如果 WHERE range 第8.8.1节“使用EXPLAIN优化查询” ,松散索引扫描查找满足范围条件的每个组的第一个键,并再次读取尽可能少的键。 在以下条件下可以这样做:

  • 查询在一个表上。

  • GROUP BY 唯一名称构成的指数,没有其他列的最左边的前缀列。 (如果 GROUP BY 查询具有 DISTINCT 子句,则所有不同的属性引用形成索引的最左前缀的列。)例如,如果表 t1 具有索引 (c1,c2,c3) ,则在查询具有的情况下,松散索引扫描适用 GROUP BY c1, c2 如果查询具有 GROUP BY c2, c3 (列不是最左边的前缀)或 GROUP BY c1, c2, c4 c4 不在索引中), 则不适用

  • 选择列表中使用的唯一聚合函数(如果有)是 MIN() MAX() ,并且它们都引用同一列。 该列必须位于索引中,并且必须紧跟在列中的列之后 GROUP BY

  • 索引的任何其他部分都不是 GROUP BY 查询中引用的 那些部分 必须是常量(也就是说,它们必须以与常量相等的方式引用),但参数 MIN() MAX() 函数 除外

  • 对于索引中的列,必须为完整列值编制索引,而不仅仅是前缀。 例如,使用 c1 VARCHAR(20), INDEX (c1(10)) ,索引仅使用 c1 的前缀, 不能用于松散索引扫描。

如果“松散索引扫描”适用于查询,则 EXPLAIN 输出将显示 Using index for group-by Extra 列中。

假设 idx(c1,c2,c3) 表上 有索引 t1(c1,c2,c3,c4) 松散索引扫描访问方法可用于以下查询:

SELECT c1,c2 FROM t1 GROUP BY c1,c2;
SELECT DISTINCT c1,c2 FROM t1;
SELECT c1,MIN(c2)FROM t1 GROUP BY c1;
SELECT c1,c2 FROM t1 WHERE c1 < const GROUP BY c1,c2;
SELECT MAX(c3),MIN(c3),c1,c2 FROM t1 WHERE c2> constGROUP BY c1,c2;
SELECT c2 FROM t1 WHERE c1 < constGROUP BY c1,c2;
SELECT c1,c2 FROM t1 WHERE c3 = const GROUP BY c1,c2;

由于给出的原因,使用此快速选择方法无法执行以下查询:

  • 除了 MIN() 之外还有聚合函数 MAX()

    SELECT c1,SUM(c2)FROM t1 GROUP BY c1;
    
  • 中的列 GROUP BY 子句中 不构成索引的最左前缀:

    SELECT c1,c2 FROM t1 GROUP BY c2,c3;
    
  • 查询引用了一个密钥的一部分 GROUP BY 一部分,并且与常量不相等:

    SELECT c1,c3 FROM t1 GROUP BY c1,c2;
    

    如果要包含查询 ,可以使用松散索引扫描。 WHERE c3 = const

除了 已经支持 MIN() MAX() 引用 之外,松散索引扫描访问方法还可以应用于选择列表中的其他形式的聚合函数引用

假设 idx(c1,c2,c3) 表上 有索引 t1(c1,c2,c3,c4) 松散索引扫描访问方法可用于以下查询:

SELECT COUNT(DISTINCT c1),SUM(DISTINCT c1)FROM t1;

SELECT COUNT(DISTINCT c1,c2),COUNT(DISTINCT c2,c1)FROM t1;
紧密索引扫描

紧密索引扫描可以是完整索引扫描,也可以是范围索引扫描,具体取决于查询条件。

如果不满足松散索引扫描的条件,则仍可以避免为 GROUP BY 查询 创建临时表 如果 WHERE 子句中 有范围条件,则 此方法仅读取满足这些条件的键。 否则,它执行索引扫描。 因为此方法读取 WHERE 子句 定义的每个范围中的所有键 ,或者如果没有范围条件则扫描整个索引,因此称为 紧密索引扫描 使用紧密索引扫描时,仅在找到满足范围条件的所有键之后才执行分组操作。

为了使这个方法起作用,对于查询中的所有列,存在一个恒定的相等条件就足够了,该条件引用了密钥的部分之前或之间的部分 GROUP BY 密钥。 来自等式条件的常量填充 搜索关键字 中的任何 间隙 ,以便可以形成索引的完整前缀。 然后,这些索引前缀可用于索引查找。 如果 GROUP BY 结果需要排序,并且可以形成作为索引前缀的搜索键,MySQL也避免了额外的排序操作,因为在有序索引中使用前缀进行搜索已经按顺序检索了所有键。

假设 idx(c1,c2,c3) 表上 有索引 t1(c1,c2,c3,c4) 以下查询不适用于前面描述的松散索引扫描访问方法,但仍可使用紧密索引扫描访问方法。

  • 它有一个缺口 GROUP BY ,但它被条件覆盖 c2 = 'a'

    SELECT c1,c2,c3 FROM t1 WHERE c2 ='a'GROUP BY c1,c3;
    
  • GROUP BY 不与钥匙的第一部分开始,但它提供了该部分恒定的条件:

    SELECT c1,c2,c3 FROM t1 WHERE c1 ='a'GROUP BY c2,c3;
    

8.2.1.17 DISTINCT优化

DISTINCT ORDER BY 在许多情况下 结合 需要临时表。

因为 DISTINCT 可以使用 GROUP BY ,所以了解MySQL如何 使用 不属于所选列的列 ORDER BY HAVING 子句。 请参见 第12.20.3节“GROUP BY的MySQL处理”

在大多数情况下,一个 DISTINCT 条款可以被视为一个特例 GROUP BY 例如,以下两个查询是等效的:

SELECT DISTINCT c1,c2,c3 FROM t1
在哪里c1> const;

SELECT c1,c2,c3 FROM t1
在哪里c1> constGROUP BY c1,c2,c3;

由于这种等效性,适用于 GROUP BY 查询 的优化 也可以应用于带有 DISTINCT 子句的 查询 因此,有关 DISTINCT 查询 优化可能性的更多详细信息 ,请参见 第8.2.1.16节“GROUP BY优化”

与之 结合 使用时 ,MySQL会在找到 唯一行后 立即停止 LIMIT row_count DISTINCT row_count

如果不使用查询中指定的所有表中的列,MySQL会在找到第一个匹配项后立即停止扫描所有未使用的表。 在下面的例子中,假设 t1 之前使用过 t2 (你可以检查 EXPLAIN ),MySQL t2 t1 找到第一行时 停止从 (对于任何特定行 读取 t2

SELECT DISTINCT t1.a FROM t1,t2其中t1.a = t2.a;

8.2.1.18 LIMIT查询优化

如果只需要结果集中指定数量的行,请 LIMIT 在查询中 使用 子句,而不是获取整个结果集并丢弃额外数据。

MySQL有时会优化具有 子句且没有 子句 的查询 LIMIT row_count HAVING

  • 如果您只选择几行 LIMIT ,MySQL在某些情况下使用索引,通常它更喜欢进行全表扫描。

  • 如果你结合 使用 ,MySQL会在找到 排序结果 的第一 行后 立即停止排序 ,而不是整理整个结果。 如果使用索引完成排序,则速度非常快。 如果必须完成一个 文件排序,则选择与 没有该 子句 的查询匹配的所有行 ,并在 找到 第一个之前对其中的大部分或全部进行排序 在找到初始行之后,MySQL不会对结果集的任何剩余部分进行排序。 LIMIT row_count ORDER BY row_count LIMIT row_count

    此行为的一种表现形式是, ORDER BY 带有和不带 查询 LIMIT 可能会以不同的顺序返回行,如本节后面所述。

  • 如果你结合 使用 ,MySQL会在找到 唯一行后 立即停止 LIMIT row_count DISTINCT row_count

  • 在某些情况下, GROUP BY 可以通过按顺序读取索引(或对索引进行排序),然后计算摘要直到索引值更改来解析a。 在这种情况下, 不计算任何不必要的 值。 LIMIT row_count GROUP BY

  • 一旦MySQL向客户端发送了所需的行数,它就会中止查询,除非您使用 SQL_CALC_FOUND_ROWS 在这种情况下,可以使用检索行数 SELECT FOUND_ROWS() 请参见 第12.15节“信息功能”

  • LIMIT 0 快速返回一个空集。 这对于检查查询的有效性非常有用。 它还可用于获取使用MySQL API的应用程序中的结果列类型,该API使结果集元数据可用。 使用 mysql 客户端程序,您可以使用该 --column-type-info 选项显示结果列类型。

  • 如果服务器使用临时表来解析查询,则它使用该 子句计算需要多少空间。 LIMIT row_count

  • 如果未使用索引 ORDER BY 但是 LIMIT 也存在子句,则优化器可能能够避免使用合并文件并使用内存中 filesort 操作 对内存中的行进行排序

如果列中的多个行具有相同的值 ORDER BY ,则服务器可以按任何顺序自由返回这些行,并且可能根据整体执行计划的不同而不同。 换句话说,这些行的排序顺序相对于无序列是不确定的。

影响执行计划的一个因素是 LIMIT ,因此 ORDER BY 使用和不 使用 查询 LIMIT 可能会返回不同顺序的行。 考虑这个查询,它按 category 排序, 但对于 id rating 列是 不确定的

MySQL的> SELECT * FROM ratings ORDER BY category;
+ ---- + ---------- + -------- +
| id | 类别| 评级|
+ ---- + ---------- + -------- +
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
| 2 | 3 | 5.0 |
| 7 | 3 | 2.7 |
+ ---- + ---------- + -------- +

包括 LIMIT 可能影响每个 category 内的行的顺序 例如,这是一个有效的查询结果:

MySQL的> SELECT * FROM ratings ORDER BY category LIMIT 5;
+ ---- + ---------- + -------- +
| id | 类别| 评级|
+ ---- + ---------- + -------- +
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 4 | 2 | 3.5 |
| 3 | 2 | 3.7 |
| 6 | 2 | 3.5 |
+ ---- + ---------- + -------- +

在每种情况下,行都按 ORDER BY 排序 ,这是SQL标准所需的全部内容。

如果确保使用和不使用相同的行顺序很重要,请 LIMIT ORDER BY 子句中 包含其他列 以使订单具有确定性。 例如,如果 id 值是唯一的,则可以 通过如下排序 category id 顺序 显示 给定 值的

MySQL的> SELECT * FROM ratings ORDER BY category, id;
+ ---- + ---------- + -------- +
| id | 类别| 评级|
+ ---- + ---------- + -------- +
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
| 2 | 3 | 5.0 |
| 7 | 3 | 2.7 |
+ ---- + ---------- + -------- +

MySQL的> SELECT * FROM ratings ORDER BY category, id LIMIT 5;
+ ---- + ---------- + -------- +
| id | 类别| 评级|
+ ---- + ---------- + -------- +
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
+ ---- + ---------- + -------- +

8.2.1.19函数调用优化

MySQL函数在内部标记为确定性或非确定性。 如果给定参数的固定值,它可以为不同的调用返回不同的结果,那么函数是不确定的。 非确定性函数的例子: RAND() UUID()

如果函数被标记为非确定性的, WHERE 则会为每一行(从一个表中选择时)或行组合(从多表连接中选择时)评估子句中 对它的引用

MySQL还根据参数类型,参数是表列还是常量值来确定何时评估函数。 只要该列更改值,就必须计算将表列作为参数的确定性函数。

非确定性函数可能会影响查询性能。 例如,某些优化可能不可用,或者可能需要更多锁定。 以下讨论使用 RAND() 但也适用于其他非确定性函数。

假设一个表 t 有这个定义:

CREATE TABLE t(id INT NOT NULL PRIMARY KEY,col_a VARCHAR(100));

考虑这两个查询:

SELECT * FROM t WHERE id = POW(1,2);
SELECT * FROM t WHERE id = FLOOR(1 + RAND()* 49);

由于与主键的相等性比较,两个查询似乎都使用主键查找,但这仅适用于第一个:

  • 第一个查询总是产生最多一行,因为 POW() 常量参数是一个常量值,用于索引查找。

  • 第二个查询包含一个使用非确定性函数的表达式,该函数 RAND() 在查询中不是常量,但实际上对于表的每一行都有一个新值 t 因此,查询读取表的每一行,计算每行的谓词,并输出主键与随机值匹配的所有行。 这可能是零行,一行或多行,具体取决于 id 列值和 RAND() 序列中 的值

不确定性的影响不仅限于 SELECT 陈述。 UPDATE 语句使用非确定性函数来选择要修改的行:

UPDATE t SET col_a = some_exprWHERE id = FLOOR(1 + RAND()* 49);

据推测,目的是最多更新主键与表达式匹配的单个行。 但是,它可能会更新零行,一行或多行,具体取决于 id 列值和 RAND() 序列中 的值

刚才描述的行为对性能和复制有影响:

  • 由于非确定性函数不会生成常量值,因此优化程序无法使用可能适用的策略,例如索引查找。 结果可能是表扫描。

  • InnoDB 可能会升级到范围键锁,而不是对一个匹配的行进行单行锁定。

  • 不确定执行的更新对于复制是不安全的。

困难源于这样一个事实,即 RAND() 对表格的每一行评估一次函数。 要避免多项功能评估,请使用以下技术之一:

  • 将包含非确定性函数的表达式移动到单独的语句中,将值保存在变量中。 在原始语句中,将表达式替换为对变量的引用,优化程序可将其视为常量值:

    SET @keyval = FLOOR(1 + RAND()* 49);
    UPDATE t SET col_a = some_exprWHERE id = @keyval;
    
  • 将随机值分配给派生表中的变量。 这个技术使变量在用于 WHERE 子句 中的比较之前被赋值一次

    UPDATE / * + NO_MERGE(dt)* / t,(SELECT FLOOR(1 + RAND()* 49)AS r)AS dt
    SET col_a = some_exprWHERE id = dt.r;
    

如前所述, WHERE 子句中 的非确定性表达式 可能会阻止优化并导致表扫描。 但是, WHERE 如果其他表达式是确定性的 ,则可以部分地优化该 子句。 例如:

SELECT * FROM t WHERE partial_key = 5 AND some_column = RAND();

如果优化器可以 partial_key 用来减少所选择的行集, RAND() 则执行的次数会减少,这会减少非确定性对优化的影响。

8.2.1.20窗口函数优化

窗口函数会影响优化程序考虑的策略:

  • 如果子查询具有窗口函数,则禁用派生表合并子查询。 子查询始终具体化。

  • 半连接不适用于窗口函数优化,因为半连接适用于子查询, WHERE 并且 JOIN ... ON 不能包含窗口函数。

  • 优化器按顺序处理具有相同排序要求的多个窗口,因此可以在第一个窗口之后跳过排序。

  • 优化器不会尝试合并可在单个步骤中评估的窗口(例如,当多个 OVER 子句包含相同的窗口定义时)。 解决方法是在 WINDOW 子句中 定义窗口 并引用子句中的窗口名称 OVER

未用作窗口函数的聚合函数在最外面的可能查询中聚合。 例如,在这个查询中,MySQL看到 COUNT(t1.b) 外部查询中不存在的东西,因为它放在 WHERE 子句中:

SELECT * FROM t1 WHERE t1.a =(SELECT COUNT(t1.b)FROM t2);

因此,MySQL在子查询中聚合, t1.b 视为常量并返回行的计数 t2

WHERE HAVING 结果 替换 错误:

MySQL的> SELECT * FROM t1 HAVING t1.a = (SELECT COUNT(t1.b) FROM t2);
错误1140(42000):在没有GROUP BY的聚合查询中,表达式为#1
SELECT列表包含非聚合列'test.t1.a'; 这是
与sql_mode = only_full_group_by不兼容

发生错误是因为 COUNT(t1.b) 可以存在 HAVING ,因此会使外部查询聚合。

窗口函数(包括用作窗口函数的聚合函数)没有前面的复杂性。 它们总是在编写它们的子查询中聚合,而不是在外部查询中聚合。

窗口函数评估可能受 windowing_use_high_precision 系统变量 值的影响,该值 确定是否在不损失精度的情况下计算窗口操作。 默认情况下, windowing_use_high_precision 已启用。

对于某些移动帧聚合,可以应用反聚合函数来从聚合中移除值。 这可以提高性能,但可能会降低精度。 例如,将一个非常小的浮点值添加到一个非常大的值会导致非常小的值被 大值 隐藏 当稍后反转大值时,小值的效果会丢失。

由于反向聚合导致的精度损失仅是对浮点(近似值)数据类型的操作的因素。 对于其他类型,反向聚合是安全的; 这包括 DECIMAL ,允许小数部分,但是精确值类型。

为了更快地执行,MySQL在安全时始终使用反向聚合:

  • 对于浮点值,反向聚合并不总是安全的,可能会导致精度损失。 默认是避免反向聚合,反向聚合较慢但保留精度。 如果允许牺牲速度的安全性, windowing_use_high_precision 可以禁用以允许反向聚合。

  • 对于非浮点数据类型,逆聚合始终是安全的,无论 windowing_use_high_precision 如何都使用

  • windowing_use_high_precision MIN() 没有影响 MAX() ,在任何情况下都不使用反向聚合。

为的方差函数评价 STDDEV_POP() STDDEV_SAMP() VAR_POP() VAR_SAMP() ,和它们的同义词,可以发生在优化模式或默认模式的评价。 优化模式可能会在最后有效数字中产生略微不同的结果。 如果允许这样的差异, windowing_use_high_precision 可以禁用以允许优化模式。

因为 EXPLAIN ,窗口执行计划信息过于庞大,无法以传统输出格式显示。 要查看窗口信息,请使用 EXPLAIN FORMAT=JSON 并查找 windowing 元素。

8.2.1.21行构造函数表达式优化

行构造函数允许同时比较多个值。 例如,这两个语句在语义上是等价的:

SELECT * FROM t1 WHERE(column1,column2)=(1,1);
SELECT * FROM t1 WHERE column1 = 1 AND column2 = 1;

此外,优化器以相同的方式处理两个表达式。

如果行构造函数列未覆盖索引的前缀,则优化程序不太可能使用可用索引。 请考虑下表,其中包含一个主键 (c1, c2, c3)

CREATE TABLE t1(
  c1 INT,c2 INT,c3 INT,c4 CHAR(100),
  主键(c1,c2,c3)
);

在此查询中,该 WHERE 子句使用索引中的所有列。 但是,行构造函数本身不会覆盖索引前缀,结果是优化程序仅使用 c1 key_len=4 ,大小 c1 ):

MySQL的> EXPLAIN SELECT * FROM t1
       WHERE c1=1 AND (c2,c3) > (1,1)\G
*************************** 1。排******************** *******
           id:1
  select_type:SIMPLE
        表:t1
   分区:NULL
         类型:ref
possible_keys:PRIMARY
          关键:主要
      key_len:4
          ref:const
         行:3
     过滤:100.00
        额外:使用在哪里

在这种情况下,使用等效的非构造函数表达式重写行构造函数表达式可能会导致更完整的索引使用。 对于给定的查询,行构造函数和等效的非构造函数表达式是:

(c2,c3)>(1,1)
c2> 1 OR((c2 = 1)AND(c3> 1))

重写查询以使用非构造函数表达式会导致优化器使用index( key_len=12 中的所有三列

MySQL的> EXPLAIN SELECT * FROM t1
       WHERE c1 = 1 AND (c2 > 1 OR ((c2 = 1) AND (c3 > 1)))\G
*************************** 1。排******************** *******
           id:1
  select_type:SIMPLE
        表:t1
   分区:NULL
         类型:范围
possible_keys:PRIMARY
          关键:主要
      key_len:12
          ref:NULL
         行:3
     过滤:100.00
        额外:使用在哪里

因此,为了获得更好的结果,请避免将行构造函数与 AND / OR 表达式 混合 使用其中一个。

在某些情况下,优化器可以将范围访问方法应用于 IN() 具有行构造函数参数的表达式。 请参阅 行构造函数表达式的范围优化

8.2.1.22避免全表扫描

当MySQL使用 全表扫描 来解析查询 ,输出 EXPLAIN 显示 ALL type 列中 这通常在以下条件下发生:

  • 该表非常小,执行表扫描比使用键查找更麻烦。 这对于行少于10行且行长度较短的表来说很常见。

  • 索引列 ON or WHERE 子句 中没有可用的限制

  • 您正在将索引列与常量值进行比较,并且MySQL已经计算(基于索引树)常量覆盖了表的一部分,并且表扫描会更快。 请参见 第8.2.1.1节“WHERE子句优化”

  • 您正在使用具有低基数的键(许多行与键值匹配)通过另一列。 在这种情况下,MySQL假设通过使用密钥,它可能会执行许多密钥查找,并且表扫描会更快。

对于小型表,表扫描通常是合适的,并且性能影响可以忽略不计。 对于大型表,请尝试以下技术以避免优化程序错误地选择表扫描:

8.2.2优化子查询,派生表,查看引用和公用表表达式

MySQL查询优化器有不同的策略可用于评估子查询:

  • 对于 IN (或 =ANY )子查询,优化器具有以下选项:

    • 半连接

    • 实体化

    • EXISTS 战略

  • 对于 NOT IN (或 <>ALL )子查询,优化器具有以下选项:

    • 实体化

    • EXISTS 战略

对于派生表,优化器具有以下选项(也适用于视图引用和公用表表达式):

  • 将派生表合并到外部查询块中

  • 将派生表实现为内部临时表

以下讨论提供了有关上述优化策略的更多信息。

注意

使用子查询修改单个表 的限制 UPDATE DELETE 语句是优化程序不使用半连接或实现子查询优化。 作为一种解决方法,尝试将它们重写为 使用连接而不是子查询的 多表 UPDATE DELETE 语句。

8.2.2.1使用半连接转换优化子查询,派生表,视图引用和公用表表达式

半连接是一种准备时转换,它支持多种执行策略,例如表拉出,重复清除,首次匹配,松散扫描和物化。 优化程序使用半连接策略来改进子查询执行,如本节所述。

对于两个表之间的内部联接,连接从一个表返回一行,与另一个表中的匹配一样多次。 但是对于一些问题,唯一重要的信息是匹配,而不是匹配的数量。 假设有一些名为table的表 class roster 并且分别列出课程课程和班级名单(每个班级注册的学生)中的班级。 要列出实际注册学生的课程,您可以使用此连接:

SELECT class.class_num,class.class_name
来自INNER JOIN名单
WHERE class.class_num = roster.class_num;

但是,结果会为每个注册的学生列出每个班级一次。 对于被问到的问题,这是不必要的重复信息。

假设这 class_num class 表中 的主键, 可以通过使用重复抑制 SELECT DISTINCT ,但是先生成所有匹配的行只是为了稍后消除重复,效率很低。

使用子查询可以获得相同的无重复结果:

SELECT class_num,class_name
从班级
WHERE class_num IN(SELECT class_num FROM roster);

在这里,优化器可以识别该 IN 子句要求子查询只从 roster 表中 返回每个类号的一个实例 在这种情况下,查询可以使用 半连接 ; 也就是说,仅返回一个中的每一行的实例的操作 class 由按行匹配 roster

在MySQL 8.0.16及更高版本中, EXISTS 通过将 EXISTS 属于 WHERE 条件或连接条件的 IN 查询转换为 子查询 ,此策略也可以与 子查询 一起使用 ,如下所示:

在哪里... EXISTS(SELECT ... FROM ...)

- > WHERE ... 1 IN(SELECT 1 FROM ...)

在此之后,子查询操作可以作为半连接处理。

外部查询规范中允许外部联接和内部联接语法,表引用可以是基表,派生表,视图引用或公用表表达式。

在MySQL中,子查询必须满足这些条件才能作为半连接处理:

  • 它必须是 出现在 or 子句 顶层 IN (或 =ANY )子查询 ,可能作为 表达式中 的术语 例如: WHERE ON AND

    选择 ...
    来自ot1,......
    WHERE(oe1,...)IN(SELECT ie1,... FROM it1,... WHERE ...);
    

    这里, 表示在该查询的外侧和内侧的部分表,和 表示参照列中的外和内表中的表达式。 ot_i it_i oe_i ie_i

  • 它必须是 SELECT 没有 UNION 结构 的单一

  • 它不能包含 GROUP BY or HAVING 子句。

  • 它不能隐式分组(它必须不包含聚合函数)。

  • 它一定不能 ORDER BY LIMIT

  • 该语句不得 STRAIGHT_JOIN 在外部查询中 使用 连接类型。

  • STRAIGHT_JOIN 修改必须不存在。

  • 外表和内表的数量必须小于连接中允许的最大表数。

子查询可以是相关的或不相关的。 DISTINCT 是允许的, LIMIT 除非 ORDER BY 也使用。

如果子查询符合上述条件,MySQL会将其转换为半连接,并根据这些策略进行基于成本的选择:

  • 将子查询转换为连接,或使用表pullout并将查询作为子查询表和外部表之间的内部联接运行。 表pullout将表从子查询拉出到外部查询。

  • 重复的Weedout:运行半连接,就好像它是一个连接一样,并使用临时表删除重复的记录。

  • FirstMatch:当扫描内部表中的行组合并且存在给定值组的多个实例时,选择一个而不是全部返回它们。 这种“快捷方式”扫描并消除了不必要的行的产生。

  • LooseScan:使用索引扫描子查询表,该索引允许从每个子查询的值组中选择单个值。

  • 将子查询实现为用于执行连接的索引临时表,其中索引用于删除重复项。 在将临时表与外部表连接时,索引也可能稍后用于查找; 如果没有,则扫描表格。 有关实现的更多信息,请参见 第8.2.2.2节“使用实现优化子查询”

可以使用以下 optimizer_switch 系统变量标志 启用或禁用其中每个策略

  • semijoin 标志控制是否使用半连接。

  • 如果 semijoin 使能, firstmatch loosescan duplicateweedout ,和 materialization 旗帜能够在允许的半连接策略更精细的控制。

  • 如果 duplicateweedout 禁用半连接策略,则除非还禁用所有其他适用策略,否则不会使用该策略。

  • 如果 duplicateweedout 禁用,有时优化程序可能会生成远非最佳的查询计划。 这是由于在贪婪搜索期间进行启发式修剪,这可以通过设置来避免 optimizer_prune_level=0

默认情况下启用这些标志。 请参见 第8.9.2节“可切换的优化”

优化程序最大限度地减少了视图和派生表的处理差异。 这会影响使用 STRAIGHT_JOIN 修饰符的 IN 查询 和带有 可以转换为半连接 子查询 的视图 以下查询说明了这一点,因为处理中的更改会导致转换发生更改,从而导致执行策略不同:

创建视图与AS
选择 *
从t1
IN(选择b
           来自t2);

SELECT STRAIGHT_JOIN *
FROM t3 JOIN v ON t3.x = va;

优化器首先查看视图并将 IN 子查询转换为半连接,然后检查是否可以将视图合并到外部查询中。 由于 STRAIGHT_JOIN 外部查询中 修饰符会阻止半连接,因此优化程序会拒绝合并,从而导致使用实现表进行派生表评估。

EXPLAIN output表示使用半连接策略如下:

  • 对于扩展 EXPLAIN 输出,以下 SHOW WARNINGS 显示 的文本显示 重写的查询,该查询显示半连接结构。 (参见 第8.8.3节“扩展EXPLAIN输出格式” 。)从中可以了解哪些表从半连接中拉出。 如果子查询转换为半连接,您将看到子查询谓词已消失,其表和 WHERE 子句已合并到外部查询连接列表和 WHERE 子句中。

  • 对于重复Weedout临时表的使用是由指示 Start temporary End temporary Extra 列。 未被拉出并且在 EXPLAIN 输出行 范围内的 Start temporary 并将 End temporary 它们 rowid 放在临时表中。

  • FirstMatch(tbl_name) Extra 列中表示连接快捷方式。

  • LooseScan(m..n) Extra 列中表示使用LooseScan策略。 m 并且 n 是关键部件号。

  • 用于实现的临时表用于 select_type 值为的 MATERIALIZED 行和 table 值为的 <subqueryN>

8.2.2.2使用实现优化子查询

优化程序使用实现来实现更有效的子查询处理。 实现通过生成子查询结果作为临时表(通常在内存中)来加速查询执行。 MySQL第一次需要子查询结果时,它会将结果实现为临时表。 在任何后续需要结果的时候,MySQL再次引用临时表。 优化器可以使用哈希索引对表进行索引,以使查找快速且廉价。 索引包含唯一值以消除重复项并使表更小。

子查询实现在可能的情况下使用内存中的临时表,如果表变得太大,则会回退到磁盘上的存储。 请参见 第8.4.4节“MySQL中的内部临时表使用”

如果未使用实现,则优化程序有时会将非相关子查询重写为相关子查询。 例如,以下 IN 子查询是不相关的( where_condition 仅涉及列 t2 和不 包括列 t1 ):

SELECT * FROM t1
在哪里t1.a IN(SELECT t2.b FROM t2 WHERE where_condition);

优化器可能会将其重写为 EXISTS 相关子查询:

SELECT * FROM t1
WHERE EXISTS(从t2 WHERE where_condition和t1.a = t2.b中选择t2.b);

使用临时表的子查询实现避免了这种重写,并且可以仅执行一次子查询,而不是每次执行外部查询一次。

对于要在MySQL中使用的子查询实现, 必须启用 optimizer_switch 系统变量 materialization 标志。 (参见 第8.9.2节,“切换优化” )。随着 materialization 启用的标志,物化适用于子查询任何地方出现谓词(在选择列表中, WHERE ON GROUP BY HAVING ,或 ORDER BY ),对于属于任何这些用例谓词:

  • 当没有外部表达式 oe_i 或内部表达式 ie_i 可为空 时,谓词具有此形式 N 是1或更大。

    oe_1oe_2...,oe_N)[NOT] IN(SELECT ie_1i_2,...,ie_N...)
    
  • 当存在单个外部表达式 oe 和内部表达式 时,谓词具有此形式 ie 表达式可以为空。

    oe[NOT] IN(选择ie...)
    
  • 谓词是 IN 或者 NOT IN 的结果 具有与结果相同的含义 UNKNOWN NULL FALSE

以下示例说明了等效性 UNKNOWN FALSE 谓词式评估 的要求如何 影响是否可以使用子查询实现。 假设 where_condition 只涉及列 t2 而不是 t1 子查询是不相关的。

此查询取决于具体化:

SELECT * FROM t1
在哪里t1.a IN(SELECT t2.b FROM t2 WHERE where_condition);

在这里,没有关系是否 IN 断言返回 UNKNOWN FALSE 无论哪种方式, t1 查询结果中都不包含

不使用子查询实现的示例是以下查询,其中 t2.b 是可为空的列:

SELECT * FROM t1
WHERE(t1.a,t1.b)NOT IN(SELECT t2.a,t2.b FROM t2
                          在哪里where_condition);

以下限制适用于子查询实现的使用:

  • 内表达式和外表达式的类型必须匹配。 例如,如果两个表达式都是整数或两者都是十进制,则优化器可能能够使用实现,但如果一个表达式是整数而另一个表达式是十进制则不能。

  • 内在表达不能是一个 BLOB

使用 EXPLAIN with查询可以指示优化程序是否使用子查询实现:

  • 与不使用实现的查询执行相比, select_type 可能会更改 DEPENDENT SUBQUERY SUBQUERY 这表明,对于每个外行将执行一次的子查询,实现使子查询只执行一次。

  • 对于扩展 EXPLAIN 输出,以下显示的文本 SHOW WARNINGS 包括 materialize materialized-subquery

8.2.2.3使用EXISTS策略优化子查询

某些优化适用于使用 IN (或 =ANY )运算符测试子查询结果的比较。 本节讨论这些优化,特别是关于 NULL 价值所带来 的挑战 讨论的最后部分建议您如何帮助优化器。

考虑以下子查询比较:

outer_exprIN(inner_expr从...中选择subquery_where

MySQL的评估查询 从外到内。 也就是说,它首先获取外部表达式的值 outer_expr ,然后运行子查询并捕获它生成的行。

一个非常有用的优化是 通知 子查询,只有感兴趣的行是内部表达式 inner_expr 相等的行 outer_expr 这是通过将适当的相等性下推到子查询的 WHERE 子句中来实现的,以使其更具限制性。 转换后的比较如下所示:

EXISTS(选择1来自...在哪里subquery_whereouter_expr= inner_expr

转换后,MySQL可以使用下推的相等性来限制它必须检查以评估子查询的行数。

更一般地, N 值与返回 N -value行 的子查询 的比较 受到相同的转换。 如果 oe_i ie_i 表示相应的外部和内部表达式值,则此子查询比较:

oe_1,...,oe_N)IN
  (SELECT ie_1,...,ie_NFROM ... WHERE subquery_where

变为:

EXISTS(选择1来自...在哪里subquery_whereoe_1=ie_1
                          和......
                          AND oe_N= ie_N

为简单起见,以下讨论假设一对外部和内部表达值。

刚刚描述的转换有其局限性。 仅当我们忽略可能的 NULL 值时 它才有效 也就是说, 只要这两个条件都成立 下推 策略就会起作用:

  • outer_expr 而且 inner_expr 不可能 NULL

  • 你不必区分 NULL FALSE 子查询结果。 如果子查询是 子句中的 表达式 OR AND 表达式 的一部分 WHERE ,则MySQL假定您不关心。 优化器注意到 NULL 并且 FALSE 子查询结果无需区分的 另一个实例 是此构造:

    ......在哪里outer_exprsubquery

    在这种情况下,该 WHERE 条款拒绝该行是否 回报 IN (subquery) NULL FALSE

当这些条件中的任何一个或两个都不成立时,优化就更复杂了。

假设 outer_expr 已知它是非 NULL 值,但子查询不会产生一个行 outer_expr = inner_expr 然后 outer_expr IN (SELECT ...) 评估如下:

  • NULL 中,如果 SELECT 产生任何行,其中 inner_expr NULL

  • FALSE ,如果 SELECT 只产生非 NULL 值或什么都不产生

在这种情况下,查找行的方法 不再有效。 有必要找这样的行,但如果没有找到,还找行,其中 粗略地说,子查询可以转换为这样的东西: outer_expr = inner_expr inner_expr NULL

EXISTS(选择1来自...在哪里subquery_whereouter_expr= inner_exprOR为inner_expr空))

需要评估额外 IS NULL 条件是MySQL具有 ref_or_null 访问方法的原因:

MySQL的> EXPLAIN
       SELECT outer_expr IN (SELECT t2.maybe_null_key
                             FROM t2, t3 WHERE ...)
       FROM t1;
*************************** 1。排******************** *******
           id:1
  select_type:PRIMARY
        表:t1
...
*************************** 2.排******************** *******
           id:2
  select_type:DEPENDENT SUBQUERY
        表:t2
         type:ref_or_null
possible_keys:maybe_null_key
          key:maybe_null_key
      key_len:5
          ref:func
         行:2
        额外:使用在哪里; 使用索引
...

unique_subquery index_subquery 子查询,具体的访问方法也有 NULL 变体。

附加 OR ... IS NULL 条件使得查询执行稍微复杂一些(并且子查询中的一些优化变得不适用),但通常这是可以容忍的。

的情况更糟糕的时候 outer_expr 可能 NULL 根据SQL解释 NULL 未知值 ”, 应评估为: NULL IN (SELECT inner_expr ...)

  • NULL ,如果 SELECT 产生任何行

  • FALSE ,如果 SELECT 没有产生任何行

为了进行适当的评估,必须能够检查是否 SELECT 产生了任何行,因此 不能将其下推到子查询中。 这是一个问题,因为除非可以推迟相等,否则许多真实世界的子查询变得非常慢。 outer_expr = inner_expr

从本质上讲,必须有不同的方法来执行子查询,具体取决于 outer_expr

优化选择超速SQL合规性,所以它占的可能性 outer_expr 可能是 NULL

  • 如果 outer_expr NULL ,要评估以下表达式,有必要执行 SELECT 以确定它是否产生任何行:

    NULL IN(SELECT inner_exprFROM ... WHERE subquery_where

    有必要在 SELECT 这里 执行原件 ,没有任何前面提到的那种下推等式。

  • 另一方面,当 outer_expr 不是 NULL ,这个比较是绝对必要的:

    outer_exprIN(inner_expr从...中选择subquery_where

    转换为使用下推条件的表达式:

    EXISTS(选择1来自...在哪里subquery_whereouter_expr= inner_expr

    如果没有这种转换,子查询将会很慢。

为了解决是否将条件压入子查询的困境,条件包含在 触发器 函数中。 因此,表达式如下:

outer_exprIN(inner_expr从...中选择subquery_where

被转换成:

EXISTS(SELECT 1 FROM ... WHERE subquery_where
                          和trigcond(outer_expr= inner_expr))

更一般地说,如果子查询比较基于几对外部和内部表达式,则转换采用此比较:

oe_1,...,oe_N)IN(SELECT ie_1,...,ie_NFROM ... WHERE subquery_where

并将其转换为此表达式:

EXISTS(SELECT 1 FROM ... WHERE subquery_where
                          和trigcond(oe_1= ie_1
                          和......
                          AND trigcond(oe_N= ie_N

每个 都是一个特殊函数,其值为: trigcond(X)

  • X 链接 ”的 外表达 oe_i 不是 NULL

  • TRUE 链接 ”的 外表达 oe_i NULL

注意

触发器函数 不是 您创建的类型的触发器 CREATE TRIGGER

包含在 trigcond() 函数 内的等式 不是查询优化器的第一类谓词。 大多数优化都无法处理可能在查询执行时打开和关闭的谓词,因此他们认为任何 未知函数都会被忽略。 这些优化可以使用触发的等式: trigcond(X)

  • 参考优化: 可用于构建 表访问。 trigcond(X=Y [OR Y IS NULL]) ref eq_ref ref_or_null

  • 基于索引查找的子查询执行引擎: 可用于构造 访问。 trigcond(X=Y) unique_subquery index_subquery

  • 表条件生成器:如果子查询是多个表的连接,则会尽快检查触发条件。

当优化器使用触发条件创建某种基于索引查找的访问时(如前面列表的前两项),它必须具有关闭条件时的情况的回退策略。 此回退策略始终相同:执行全表扫描。 EXPLAIN 输出中,回退显示 Full scan on NULL key Extra 列中:

MySQL的> EXPLAIN SELECT t1.col1,
       t1.col1 IN (SELECT t2.key1 FROM t2 WHERE t2.col2=t1.col2) FROM t1\G
*************************** 1。排******************** *******
           id:1
  select_type:PRIMARY
        表:t1
        ...
*************************** 2.排******************** *******
           id:2
  select_type:DEPENDENT SUBQUERY
        表:t2
         type:index_subquery
possible_keys:key1
          key:key1
      key_len:5
          ref:func
         行:2
        额外:使用在哪里; 全键扫描NULL键

如果您运行 EXPLAIN 后跟 SHOW WARNINGS ,则可以看到触发条件:

*************************** 1。排******************** *******
  等级:注意
   代码:1003
消息:选择`test` .t1` .col1` AS`col1`,
         <in_optimizer>(`test`.`t1`.`col1`,
         t2中的<exists>(<index_lookup>(<cache>(`test` .t1` .col1`)
         在key1上检查NULL
         其中(`test`.`t2`.`col2` =`test`.`t1`.`col2`),其具有
         trigcond(<is_not_null_test>(`test` .t2` .key1`)))))AS
         `t1.col1 IN(从t2选择t2.key1,其中t2.col2 = t1.col2)`
         来自`test``t1`

触发条件的使用具有一些性能影响。 NULL IN (SELECT ...) 现在表达可能会导致全表扫描(这是慢)时,它以前没有。 这是为正确结果支付的价格(触发条件策略的目标是提高合规性,而不是速度)。

对于多表子查询,执行 NULL IN (SELECT ...) 速度特别慢,因为连接优化器不会针对外部表达式进行优化 NULL 它假设 NULL 左侧的 子查询评估 非常罕见,即使有统计数据表明不是这样。 另一方面,如果外部表达式可能 NULL 但实际上从未实际存在,则不存在性能损失。

为了帮助查询优化器更好地执行查询,请使用以下建议:

  • 声明一个列就像 NOT NULL 它确实一样。 通过简化列的条件测试,这也有助于优化器的其他方面。

  • 如果您不需要区分 NULL 来自 FALSE 子查询的结果,你可以很容易地避免慢的执行路径。 替换看起来像这样的比较:

    outer_exprIN(inner_expr从...中选择

    用这个表达式:

    outer_exprIS NOT NULL)AND(outer_exprIN(SELECT inner_exprFROM ...))
    

    然后 NULL IN (SELECT ...) 永远不会被评估,因为 AND 一旦表达式结果清楚, MySQL就会停止评估 部分。

    另一种可能的重写:

    EXISTS(inner_expr从...中选择)
            WHERE inner_expr= outer_expr

    当你需要分不清这将适用于 NULL FALSE 子查询结果,在这种情况下,你可能真的想 EXISTS

subquery_materialization_cost_based 所述的标志 optimizer_switch 系统变量使得能够在子查询物化和之间的选择控制 IN -到- EXISTS 子查询变换。 请参见 第8.9.2节“可切换的优化”

8.2.2.4使用合并或实现优化派生表,视图引用和公用表表达式

优化器可以使用两种策略(也适用于视图引用和公用表表达式)处理派生表引用:

  • 将派生表合并到外部查询块中

  • 将派生表实现为内部临时表

例1:

SELECT * FROM(SELECT * FROM t1)AS derived_t1;

通过合并派生表 derived_t1 ,该查询的执行类似于:

SELECT * FROM t1;

例2:

选择 *
  FROM t1 JOIN(SELECT t2.f1 FROM t2)AS derived_t2 ON t1.f2 = derived_t2.f1
  在哪里t1.f1> 0;

通过合并派生表 derived_t2 ,该查询的执行类似于:

SELECT t1。*,t2.f1
  FROM t1 JOIN t2 ON t1.f2 = t2.f1
  在哪里t1.f1> 0;

具有实现, derived_t1 derived_t2 在每个查询中被视为单独的表。

优化器以相同的方式处理派生表,视图引用和公用表表达式:它尽可能避免不必要的实现,这样可以将条件从外部查询推送到派生表,并生成更高效的执行计划。 (有关示例,请参见 第8.2.2.2节“使用实现优化子查询” 。)

如果合并将导致外部查询块引用超过61个基表,则优化程序将选择实现。

ORDER BY 如果这些条件都为真 ,则优化器将 派生表或视图引用中 子句 传播 到外部查询块:

  • 外部查询未分组或聚合。

  • 外部查询不指定 DISTINCT HAVING ORDER BY

  • 外部查询将此派生表或视图引用作为 FROM 子句中 的唯一源

否则,优化器会忽略该 ORDER BY 子句。

以下方法可用于影响优化程序是否尝试将派生表,视图引用和公用表表达式合并到外部查询块中:

  • MERGE NO_MERGE 优化器提示可以使用。 假设没有其他规则阻止合并,它们适用。 请参见 第8.9.3节“优化程序提示”

  • 同样,您可以使用 系统变量 derived_merge 标志 optimizer_switch 请参见 第8.9.2节“可切换的优化” 默认情况下,启用该标志以允许合并。 禁用该标志可防止合并并避免 ER_UPDATE_TABLE_USED 错误。

    derived_merge 标志也适用于不包含 ALGORITHM 子句的 视图 因此,如果 ER_UPDATE_TABLE_USED 使用等效于子查询的表达式的视图引用发生错误,则添加 ALGORITHM=TEMPTABLE 到视图定义会阻止合并并优先于该 derived_merge 值。

  • 可以通过在子查询中使用任何阻止合并的构造来禁用合并,尽管这些构造对实现的影响并不明确。 对于派生表,公用表表达式和视图引用,阻止合并的构造是相同的:

    • 聚集函数或窗函数( SUM() MIN() MAX() COUNT() ,等等)

    • DISTINCT

    • GROUP BY

    • HAVING

    • LIMIT

    • UNION 要么 UNION ALL

    • 选择列表中的子查询

    • 分配给用户变量

    • 仅引用文字值(在这种情况下,没有基础表)

如果优化器选择实现策略而不是合并派生表,它将按如下方式处理查询:

  • 优化器推迟派生表实现,直到在查询执行期间需要其内容。 这提高了性能,因为延迟实现可能导致根本不必执行此操作。 考虑将派生表的结果连接到另一个表的查询:如果优化器首先处理该另一个表并发现它不返回任何行,则不需要进一步执行连接,并且优化器可以完全跳过实现派生表的实现。

  • 在查询执行期间,优化器可以向派生表添加索引以加速从中检索行。

EXPLAIN 对于 SELECT 包含派生表 查询, 请考虑以下 语句

EXPLAIN SELECT * FROM(SELECT * FROM t1)AS derived_t1;

优化程序通过延迟派生表来避免实现派生表,直到 SELECT 执行 期间需要结果为止 在这种情况下,不执行查询(因为它出现在 EXPLAIN 语句中),因此永远不需要结果。

即使对于执行的查询,派生表实现的延迟也可能使优化器完全避免实现。 发生这种情况时,执行实现所需的时间会更快地执行查询。 请考虑以下查询,该查询将派生表的结果连接到另一个表:

选择 *
  FROM t1 JOIN(SELECT t2.f1 FROM t2)AS derived_t2
          ON t1.f2 = derived_t2.f1
  在哪里t1.f1> 0;

如果优化进程 t1 首先并且 WHERE 子句产生空结果,则连接必须为空并且不需要实现派生表。

对于派生表需要实现的情况,优化器可以向实现表添加索引以加速对其的访问。 如果这样的索引允许 ref 访问表,则可以大大减少查询执行期间读取的数据量。 请考虑以下查询:

选择 *
 FROM t1 JOIN(SELECT DISTINCT f1 FROM t2)AS derived_t2
         ON t1.f1 = derived_t2.f1;

优化器在列上构造索引 f1 derived_t2 如果这样做将允许使用 ref 最低成本执行计划 访问。 添加索引后,优化程序可以将具体化派生表视为具有索引的常规表,并且与生成的索引类似。 与没有索引的查询执行成本相比,索引创建的开销可以忽略不计。 如果 ref 访问会导致比其他访问方法更高的成本,则优化程序不会创建任何索引并且不会丢失任何内容。

对于优化程序跟踪输出,合并的派生表或视图引用不会显示为节点。 只有其基础表出现在顶部查询的计划中。

对于公用表表达式(CTE),派生表的具体化也是如此。 此外,以下考虑因素特别适用于CTE。

如果CTE由查询实现,则查询将实现一次,即使查询多次引用它。

递归CTE始终具体化。

如果CTE具体化,优化器会自动添加相关索引,如果它估计索引将加速顶级语句对CTE的访问。 这类似于派生表的自动索引,除了如果多次引用CTE,优化器可以创建多个索引,以最合适的方式加速每个引用的访问。

MERGE NO_MERGE 优化器提示可以应用到的CTE。 顶级语句中的每个CTE引用都可以有自己的提示,允许选择性地合并或实现CTE引用。 以下语句使用提示来指示 cte1 应合并并 cte2 应实现:

WITH
  cte1 AS(SELECT a,b FROM table1),
  cte2 AS(SELECT c,d FROM table2)
SELECT / * + MERGE(cte1)NO_MERGE(cte2)* / cte1.b,cte2.d
从cte1加入cte2
WHERE cte1.a = cte2.c;

ALGORITHM 对条款 CREATE VIEW 没有任何影响物化 WITH 前款 SELECT 视图定义语句。 请考虑以下声明:

CREATE ALGORITHM = {TEMPTABLE | MERGE}查看v1 AS ... ...选择...

ALGORITHM 值仅影响而 SELECT 不是 WITH 子句的实现。

在MySQL 8.0.16之前,如果 internal_tmp_disk_storage_engine=MYISAM 使用磁盘上临时表实现CTE的任何尝试都发生错误,因为对于CTE,用于磁盘内部临时表的存储引擎不可能 MyISAM 从MySQL 8.0.16开始,这不再是一个问题,因为 TempTable 现在总是 InnoDB 用于磁盘内部临时表。

如前所述,CTE如果具体化,即使多次引用,也会实现一次。 要指示一次性实现,优化程序跟踪输出包含 creating_tmp_table 一次或多次出现的事件 reusing_tmp_table

CTE与派生表类似, materialized_from_subquery 节点遵循引用。 这对于多次引用的CTE来说是正确的,因此不存在重复的 materialized_from_subquery 节点(这会给人一种子查询多次执行的印象,并产生不必要的详细输出)。 只有一个对CTE的引用有一个完整的 materialized_from_subquery 节点,其中包含子查询计划的描述。 其他引用具有减少的 materialized_from_subquery 节点。 同样的想法适用 EXPLAIN TRADITIONAL 格式 输出 :未显示其他引用的子查询。

8.2.3优化INFORMATION_SCHEMA查询

监视数据库的应用程序可能会频繁使用 INFORMATION_SCHEMA 表。 要最有效地为这些表编写查询,请使用以下一般准则:

  • 尝试仅查询 INFORMATION_SCHEMA 作为数据字典表的视图的表。

  • 尝试仅查询静态元数据。 选择列或使用动态元数据的检索条件以及静态元数据会增加处理动态元数据的开销。

注意

INFORMATION_SCHEMA 查询中 数据库和表名的比较行为 可能与您的预期不同。 有关详细信息,请参见 第10.8.7节“在INFORMATION_SCHEMA搜索中使用排序规则”

这些 INFORMATION_SCHEMA 表实现为数据字典表的视图,因此对它们的查询从数据字典中检索信息:

CHARACTER_SETS
CHECK_CONSTRAINTS
COLLATIONS
COLLATION_CHARACTER_SET_APPLICABILITY
活动
FILES
INNODB_COLUMNS
INNODB_DATAFILES
INNODB_FIELDS
INNODB_FOREIGN
INNODB_FOREIGN_COLS
INNODB_INDEXES
INNODB_TABLES
INNODB_TABLESPACES
INNODB_TABLESPACES_BRIEF
INNODB_TABLESTATS
KEY_COLUMN_USAGE
参数
PARTITIONS
REFERENTIAL_CONSTRAINTS
RESOURCE_GROUPS
例程
SCHEMATA
统计
TABLES
TABLE_CONSTRAINTS
TRIGGERS
VIEWS
VIEW_ROUTINE_USAGE
VIEW_TABLE_USAGE

某些类型的值(即使对于非视图 INFORMATION_SCHEMA 表)也可以通过数据字典中的查找来检索。 这包括诸如数据库和表名,表类型和存储引擎之类的值。

某些 INFORMATION_SCHEMA 表包含提供表统计信息的列:

STATISTICS.CARDINALITY
TABLES.AUTO_INCREMENT
TABLES.AVG_ROW_LENGTH
TABLES.CHECKSUM
TABLES.CHECK_TIME
TABLES.CREATE_TIME
TABLES.DATA_FREE
TABLES.DATA_LENGTH
TABLES.INDEX_LENGTH
TABLES.MAX_DATA_LENGTH
TABLES.TABLE_ROWS
TABLES.UPDATE_TIME

这些列表示动态表元数据; 也就是说,随着表格内容的变化而变化的信息。

默认情况下,MySQL的检索从这些列的缓存值 mysql.index_stats mysql.table_stats 这比直接从存储引擎检索统计数据更有效,当列查询字典表。 如果缓存的统计信息不可用或已过期,MySQL将从存储引擎中检索最新的统计信息,并将它们缓存在 mysql.index_stats mysql.table_stats 字典表中。 后续查询将检索缓存的统计信息,直到缓存的统计信息过期。

information_schema_stats_expiry 会话变量定义的时间段之前缓存统计到期。 默认值为86400秒(24小时),但时间段可以延长至一年。

要随时更新给定表的缓存值,请使用 ANALYZE TABLE

在这些情况下, 查询统计信息列不会在 mysql.index_stats mysql.table_stats 字典表中 存储或更新统计信息

information_schema_stats_expiry 是一个会话变量,每个客户端会话可以定义自己的到期值。 从存储引擎检索并由一个会话缓存的统计信息可供其他会话使用。

注意

如果 innodb_read_only 启用 系统变量,则 ANALYZE TABLE 可能会失败,因为它无法更新数据字典中使用的统计表 InnoDB 对于 ANALYZE TABLE 更新密钥分发的操作,即使操作更新表本身(例如,如果它是 MyISAM 表) ,也可能发生故障 要获取更新的分布统计信息,请设置 information_schema_stats_expiry=0

对于 INFORMATION_SCHEMA 作为数据字典表视图实现的表,基础数据字典表上的索引允许优化器构建有效的查询执行计划。 要查看优化程序所做的选择,请使用 EXPLAIN 要同时查看服务器用于执行 INFORMATION_SCHEMA 查询的查询,请 SHOW WARNINGS 立即 使用 以下内容 EXPLAIN

请考虑此语句,该语句标识 utf8mb4 字符集的 排序规则

MySQL的> SELECT COLLATION_NAME
       FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY
       WHERE CHARACTER_SET_NAME = 'utf8mb4';
+ ---------------------------- +
| COLLATION_NAME |
+ ---------------------------- +
| utf8mb4_general_ci |
| utf8mb4_bin |
| utf8mb4_unicode_ci |
| utf8mb4_icelandic_ci |
| utf8mb4_latvian_ci |
| utf8mb4_romanian_ci |
| utf8mb4_slovenian_ci |
...

服务器如何处理该语句? 要了解,请使用 EXPLAIN

MySQL的> EXPLAIN SELECT COLLATION_NAME
       FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY
       WHERE CHARACTER_SET_NAME = 'utf8mb4'\G
*************************** 1。排******************** *******
           id:1
  select_type:SIMPLE
        表:cs
   分区:NULL
         类型:const
possible_keys:PRIMARY,名字
          关键:名称
      key_len:194
          ref:const
         行:1
     过滤:100.00
        额外:使用索引
*************************** 2.排******************** *******
           id:1
  select_type:SIMPLE
        表:col
   分区:NULL
         类型:ref
possible_keys:character_set_id
          key:character_set_id
      key_len:8
          ref:const
         行:68
     过滤:100.00
        额外:NULL
2行,1个警告(0.01秒)

要查看用于满足该语句的查询,请使用 SHOW WARNINGS

MySQL的> SHOW WARNINGS\G
*************************** 1。排******************** *******
  等级:注意
   代码:1003
消息:/ *选择#1 * /选择`mysql` .col` .name` AS`COLLATION_NAME`
         来自`mysql``character_sets``cs`
         加入`mysql``collat​​ions``col`
         where((``mysql` .col``character_set_id` ='45')
         和('utf8mb4'='utf8mb4'))

如所指示 SHOW WARNINGS ,该服务器处理该查询上 COLLATION_CHARACTER_SET_APPLICABILITY 为对查询 character_sets collations 在数据字典表 mysql 系统数据库。

8.2.4优化性能模式查询

监视数据库的应用程序可能会频繁使用性能架构表。 要最有效地为这些表编写查询,请利用它们的索引。 例如,包含一个 WHERE 子句, 子句根据与索引列中特定值的比较来限制检索到的行。

大多数性能架构表都有索引。 不包含通常包含很少行或不太可能经常查询的表的表。 性能模式索引使优化器可以访问除全表扫描之外的执行计划。 这些索引还可以提高相关对象的性能,例如 sys 使用这些表的架构视图。

要查看给定的Performance Schema表是否具有索引及其含义,请使用 SHOW INDEX SHOW CREATE TABLE

MySQL的> SHOW INDEX FROM performance_schema.accounts\G
*************************** 1。排******************** *******
        表:帐户
   非独特:0
     Key_name:ACCOUNT
 Seq_in_index:1
  Column_name:USER
    排序规则:NULL
  基数:NULL
     Sub_part:NULL
       打包:NULL
         空:是的
   Index_type:HASH
      评论:
Index_comment:
      可见:是的
*************************** 2.排******************** *******
        表:帐户
   非独特:0
     Key_name:ACCOUNT
 Seq_in_index:2
  Column_name:HOST
    排序规则:NULL
  基数:NULL
     Sub_part:NULL
       打包:NULL
         空:是的
   Index_type:HASH
      评论:
Index_comment:
      可见:是的

MySQL的> SHOW CREATE TABLE performance_schema.rwlock_instances\G
*************************** 1。排******************** *******
       表:rwlock_instances
创建表:CREATE TABLE`rwlock_instances`(
  `NAME` varchar(128)NOT NULL,
  `OBJECT_INSTANCE_BEGIN` bigint(20)unsigned NOT NULL,
  `WRITE_LOCKED_BY_THREAD_ID` bigint(20)unsigned DEFAULT NULL,
  `READ_LOCKED_BY_COUNT` int(10)unsigned NOT NULL,
  PRIMARY KEY(`OBJECT_INSTANCE_BEGIN`),
  KEY`NAME`(`NAME`),
  KEY`WRITE_LOCKED_BY_THREAD_ID`(`WRITE_LOCKED_BY_THREAD_ID`)
)ENGINE = PERFORMANCE_SCHEMA DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci

要查看Performance Schema查询的执行计划以及它是否使用任何索引,请使用 EXPLAIN

MySQL的> EXPLAIN SELECT * FROM performance_schema.accounts
       WHERE (USER,HOST) = ('root','localhost')\G
*************************** 1。排******************** *******
           id:1
  select_type:SIMPLE
        表:帐户
   分区:NULL
         类型:const
possible_keys:ACCOUNT
          key:ACCOUNT
      key_len:278
          ref:const,const
         行:1
     过滤:100.00
        额外:NULL

EXPLAIN 输出表明优化器使用 accounts ACCOUNT 包括索引 USER HOST 列。

性能模式索引是虚拟的:它们是性能模式存储引擎的构造,不使用内存或磁盘存储。 性能模式将索引信息报告给优化器,以便它可以构建有效的执行计划。 性能模式反过来使用有关要查找内容的优化器信息(例如,特定键值),以便它可以执行有效的查找而无需构建实际的索引结构。 此实现提供了两个重要的好处:

  • 它完全避免了经常更新的表通常会产生的维护成本。

  • 它在查询执行的早期阶段减少了检索的数据量。 对于索引列的条件,性能模式仅有效地返回满足查询条件的表行。 如果没有索引,性能模式将返回表中的所有行,要求优化程序稍后针对每一行评估条件以生成最终结果。

性能模式索引是预定义的,不能删除,添加或更改。

性能模式索引与哈希索引类似。 例如:

  • 它们仅用于使用 = <=> 运算符的 相等比较

  • 它们是无序的。 如果查询结果必须具有特定的行排序特征,请包含 ORDER BY 子句。

有关哈希索引的其他信息,请参见 第8.3.9节“B树和哈希索引的比较”

8.2.5优化数据变更声明

这部分解释了如何加快数据更改语句: INSERT UPDATE ,和 DELETE 传统的OLTP应用程序和现代Web应用程序通常会执行许多小数据更改操作,其中并发性至关重要。 数据分析和报告应用程序通常会同时运行影响多行的数据更改操作,其中主要考虑因素是写入大量数据并使索引保持最新的I / O. 对于插入和更新大量数据(在行业中称为 提取 - 转换 - 加载 ),有时您使用模仿其效果的其他SQL语句或外部命令 INSERT UPDATE DELETE 陈述。

8.2.5.1优化INSERT语句

要优化插入速度,请将许多小型操作组合到一个大型操作中。 理想情况下,您进行单个连接,一次发送许多新行的数据,并将所有索引更新和一致性检查延迟到最后。

插入行所需的时间由以下因素决定,其中数字表示大致比例:

  • 连接:(3)

  • 向服务器发送查询:(2)

  • 解析查询:(2)

  • 插入行:(1×行的大小)

  • 插入索引:(1×索引数)

  • 结束:(1)

这没有考虑打开表的初始开销,每个并发运行的查询都会执行一次。

N 假设B树索引, 表的大小会减慢日志插入索引的速度

您可以使用以下方法来加速插入:

8.2.5.2优化UPDATE语句

更新语句优化为类似于 SELECT 具有写入的额外开销的查询。 写入速度取决于要更新的​​数据量和更新的索引数。 未更改的索引不会更新。

获得快速更新的另一种方法是延迟更新,然后在以后连续执行许多更新。 如果锁定表,一次执行多个更新比一次执行多个更新要快得多。

对于 MyISAM 使用动态行格式 表,将行更新为更长的总长度可能会拆分该行。 如果经常这样做, OPTIMIZE TABLE 偶尔 使用非常重要 请参见 第13.7.3.4节“OPTIMIZE TABLE语法”

8.2.5.3优化DELETE语句

删除 MyISAM 表中 各行所需的时间 与索引数完全成比例。 要更快地删除行,可以通过增加 key_buffer_size 系统变量 来增加密钥缓存的大小 请参见 第5.1.1节“配置服务器”

要删除 MyISAM 表中的 所有行 要快于 截断操作不是事务安全的; 在活动事务或活动表锁定过程中尝试一个错误时发生错误。 请参见 第13.1.37节“TRUNCATE TABLE语法” TRUNCATE TABLE tbl_name DELETE FROM tbl_name

8.2.6优化数据库权限

您的权限设置越复杂,所有SQL语句的开销就越大。 简化 GRANT 语句 建立的特权 使MySQL能够在客户端执行语句时减少权限检查开销。 例如,如果您不授予任何表级或列级权限,则服务器无需检查 tables_priv columns_priv 的内容 同样,如果您对任何帐户都没有资源限制,则服务器不必执行资源计数。 如果您的语句处理负载非常高,请考虑使用简化的授权结构来减少权限检查开销。

8.2.7其他优化技巧

本节列出了一些用于提高查询处理速度的其他技巧:

  • 如果您的应用程序发出多个数据库请求以执行相关更新,则将语句组合到存储例程中可以帮助提高性能。 同样,如果您的应用程序基于多个列值或大量数据计算单个结果,则将计算组合到UDF(用户定义的函数)可以帮助提高性能。 然后,由此产生的快速数据库操作可供其他查询,应用程序甚至用不同编程语言编写的代码重用。 有关更多信息 请参见 第24.2节“使用存储的例程” 第29.4节“向MySQL添加新函数”

  • 要解决 ARCHIVE 中发生的任何压缩问题 ,请使用 OPTIMIZE TABLE 请参见 第16.5节“ARCHIVE存储引擎”

  • 如果可能,将报告分类为 实时 统计 ,其中统计报告所需的数据仅从定期从实时数据生成的汇总表中创建。

  • 如果您的数据不符合行列表结构,则可以将数据打包并存储到 BLOB 列中。 在这种情况下,您必须在应用程序中提供打包和解压缩信息的代码,但这可能会节省I / O操作以读取和写入相关值集。

  • 使用Web服务器,将图像和其他二进制资产存储为文件,路径名存储在数据库中而不是文件本身。 大多数Web服务器在缓存文件方面比数据库内容更好,因此使用文件通常更快。 (尽管在这种情况下您必须自己处理备份和存储问题。)

  • 如果您需要非常高的速度,请查看低级MySQL接口。 例如,通过 直接 访问MySQL InnoDB MyISAM 存储引擎,与使用SQL接口相比,可以大幅提高速度。

  • 复制可以为某些操作提供性能优势。 您可以在复制服务器之间分发客户端检索以分割负载。 为避免在进行备份时减慢主服务器的速度,可以使用从服务器进行备份。 请参见 第17章, 复制

8.3优化和索引

提高操作性能的最佳方法 SELECT 是在查询中测试的一个或多个列上创建索引。 索引条目的作用类似于表行的指针,允许查询快速确定哪些行与 WHERE 子句 中的条件匹配 ,并检索这些行的其他列值。 可以索引所有MySQL数据类型。

尽管为查询中使用的每个可能列创建索引很有诱惑力,但不必要的索引会浪费空间并浪费时间让MySQL确定要使用哪些索引。 索引还会增加插入,更新和删除的成本,因为必须更新每个索引。 您必须找到适当的平衡,以使用最佳索引集实现快速查询。

8.3.1 MySQL如何使用索引

索引用于快速查找具有特定列值的行。 如果没有索引,MySQL必须从第一行开始,然后读取整个表以查找相关行。 表越大,成本越高。 如果表中有相关​​列的索引,MySQL可以快速确定要在数据文件中间寻找的位置,而无需查看所有数据。 这比按顺序读取每一行要快得多。

大多数MySQL索引( PRIMARY KEY UNIQUE INDEX FULLTEXT )存储在 B树 例外:空间数据类型的索引使用R树; MEMORY 表也​​支持 哈希索引 ; InnoDB 使用反向列表作为 FULLTEXT 索引。

通常,使用索引,如以下讨论中所述。 第8.3.9节“B树和哈希索引的比较” MEMORY 中描述了 哈希索引特有的特性(如 表中所

MySQL使用索引进行这些操作:

  • WHERE 快速 查找与 子句 匹配的行

  • 消除行的考虑。 如果在多个索引之间有选择,MySQL通常使用找到最小行数(最具 选择性的 索引)的索引。

  • 如果表具有多列索引,则优化程序可以使用索引的任何最左前缀来查找行。 例如,如果你有一个三列索引上 (col1, col2, col3) ,你有索引的搜索功能 (col1) (col1, col2) 以及 (col1, col2, col3) 有关更多信息,请参见 第8.3.6节“多列索引”

  • 在执行连接时从其他表中检索行。 如果声明它们的类型和大小相同,MySQL可以更有效地使用列上的索引。 在这种情况下, VARCHAR CHAR 被认为是相同的,如果它们被声明为相同的大小。 例如, VARCHAR(10) CHAR(10) 大小相同,但 VARCHAR(10) CHAR(15) 不是。

    对于非二进制字符串列之间的比较,两列应使用相同的字符集。 例如,将 utf8 列与 latin1 进行比较会 排除使用索引。

    不相似列的比较(例如,将字符串列与时间或数字列进行比较)可能会在没有转换的情况下无法直接比较值时阻止使用索引。 对于给定的值,如 1 在数值列,它可能比较等于在字符串列,例如任何数量的值 '1' ' 1' '00001' ,或 '01.e1' 这排除了对字符串列的任何索引的使用。

  • 查找 特定索引列的值 MIN() MAX() key_col 这是由预处理器优化的,该预处理器检查您是否正在使用 索引 之前出现的所有关键部分 在这种情况下,MySQL对每个 表达式 执行单个键查找, 并用常量替换它。 如果所有表达式都替换为常量,则查询立即返回。 例如: WHERE key_part_N = constant key_col MIN() MAX()

    SELECT MIN(key_part2),MAX(key_part2
      FROM tbl_nameWHERE key_part1= 10;
    
  • 如果对可用索引的最左前缀(例如, 进行排序或分组,则对表进行排序或分组 如果后面跟着所有关键部分 ,则按相反顺序读取密钥。 (或者,如果索引是降序索引, 则按 正向顺序读取密钥。)请参见 第8.2.1.15节“ORDER BY优化” 第8.2.1.16节“GROUP BY优化” 第8.3.13节“降序指数“ ORDER BY key_part1, key_part2 DESC

  • 在某些情况下,可以优化查询以在不咨询数据行的情况下检索值。 (为查询提供所有必要结果的 索引 称为 覆盖索引 。)如果查询仅使用表中包含某些索引的列,则可以从索引树中检索所选值以获得更快的速度:

    SELECT key_part3FROM tbl_name
      WHERE key_part1= 1
    

对于小型表或报表查询处理大多数或所有行的大型表的查询,索引不太重要。 当查询需要访问大多数行时,顺序读取比通过索引更快。 顺序读取可以最大限度地减少磁盘搜索,即使查询不需要所有行也是如此。 有关 详细信息 请参见 第8.2.1.22节“避免全表扫描”

8.3.2主键优化

表的主键表示您在最重要的查询中使用的列或列集。 它具有关联的索引,以实现快速查询性能。 查询性能受益于 NOT NULL 优化,因为它不能包含任何 NULL 值。 使用 InnoDB 存储引擎,表数据在物理上进行组织,以根据主键或列进行超快速查找和排序。

如果您的表很大且很重要,但没有明显的列或列集用作主键,则可以创建一个单独的列,其中包含自动增量值以用作主键。 使用外键连接表时,这些唯一ID可用作指向其他表中相应行的指针。

8.3.3空间索引优化

MySQL允许 SPATIAL NOT NULL 几何值列 创建 索引 (请参见 第11.5.10节“创建空间索引” )。 优化程序检查 SRID 索引列 属性,以确定要用于比较的空间参考系统(SRS),并使用适合SRS的计算。 (在MySQL 8.0之前,优化器 SPATIAL 使用笛卡尔计算 执行 索引值的 比较 ;如果列包含具有非笛卡尔SRID的值,则此类操作的结果是未定义的。)

要使比较正常工作, SPATIAL 索引中的 每个列都 必须受SRID限制。 也就是说,列定义必须包含显式 SRID 属性,并且所有列值必须具有相同的SRID。

优化程序 SPATIAL 考虑 SRID限制列的索引:

  • 限制为笛卡尔SRID的列的索引启用笛卡尔边界框计算。

  • 限制为地理SRID的列的索引启用地理边界框计算。

优化程序忽略 SPATIAL 没有 SRID 属性的 列上的索引 (因此不受SRID限制)。 MySQL仍然维护着这样的索引,如下所示:

  • 他们对表的修改(更新 INSERT UPDATE DELETE ,等等)。 更新发生就像索引是笛卡尔坐标一样,即使该列可能包含笛卡尔和地理值的混合。

  • 它们仅用于向后兼容(例如,在MySQL 5.7中执行转储和在MySQL 8.0中恢复的能力)。 由于 SPATIAL 非SRID限制的列上的索引对优化器没用,因此应修改每个此类列:

    • 验证列中的所有值是否具有相同的SRID。 要确定几何列中包含的SRID col_name ,请使用以下查询:

      SELECT DISTINCT ST_SRID(col_name)FROM tbl_name;
      

      如果查询返回多行,则该列包含多个SRID。 在这种情况下,请修改其内容,以使所有值具有相同的SRID。

    • 重新定义列以具有显式 SRID 属性。

    • 重新创建 SPATIAL 索引。

8.3.4外键优化

如果一个表有很多列,并且您查询了许多不同的列组合,那么将频率较低的数据拆分为每个都有几列的单独表可能会很有效,并通过复制数字ID将它们与主表关联起来。主表中的列。 这样,每个小表都可以有一个主键来快速查找其数据,您可以使用连接操作查询所需的列集。 根据数据的分布方式,查询可能会执行较少的I / O并占用较少的高速缓存,因为相关列在磁盘上打包在一起。 (为了最大限度地提高性能,查询尝试从磁盘中读取尽可能少的数据块;

8.3.5列索引

最常见的索引类型涉及单个列,在数据结构中存储该列的值的副本,允许快速查找具有相应列值的行。 B树数据结构可以让索引快速查找特定值,一组值,或值的范围,对应于运营商,如 = > BETWEEN IN ,等等,一在 WHERE 子句。

每个存储引擎定义每个表的最大索引数和最大索引长度。 请参见 第15章, InnoDB存储引擎 第16章, 备用存储引擎 所有存储引擎每个表至少支持16个索引,总索引长度至少为256个字节。 大多数存储引擎都有更高的限制

有关列索引的其他信息,请参见 第13.1.15节“CREATE INDEX语法”

索引前缀

使用 字符串列的索引规范中的语法,可以创建仅使用 的前 几个字符 的索引 以这种方式仅索引列值的前缀可以使索引文件更小。 索引 列时, 必须 为索引指定前缀长度。 例如: col_name(N) N BLOB TEXT

CREATE TABLE test(blob_col BLOB,INDEX(blob_col(10)));

对于 InnoDB 使用 REDUNDANT COMPACT 行格式的 表, 前缀最长可达767个字节 对于 InnoDB 使用 DYNAMIC COMPRESSED 行格式的 表, 前缀长度限制为3072字节 对于MyISAM表,前缀长度限制为1000个字节。

注意

前缀限制以字节为单位,而在前缀长度 CREATE TABLE ALTER TABLE CREATE INDEX 语句被解释为非二进制串类型的字符数( CHAR VARCHAR TEXT 二进制串类型()和字节数 BINARY VARBINARY BLOB )。 在为使用多字节字符集的非二进制字符串列指定前缀长度时,请考虑这一点。

如果搜索项超过索引前缀长度,则索引用于排除不匹配的行,并检查剩余的行以查找可能的匹配项。

有关索引前缀的其他信息,请参见 第13.1.15节“CREATE INDEX语法”

FULLTEXT索引

FULLTEXT 索引用于全文搜索。 只有 InnoDB MyISAM 存储引擎支持 FULLTEXT 索引和仅适用于 CHAR VARCHAR TEXT 列。 索引始终发生在整个列上,并且不支持列前缀索引。 有关详细信息,请参见 第12.9节“全文搜索功能”

优化适用于 FULLTEXT 针对单个 InnoDB 表的 某些类型的 查询 具有这些特征的查询特别有效:

  • FULLTEXT 仅返回文档ID或文档ID和搜索排名的查询。

  • FULLTEXT 查询按分数的降序对匹配的行进行排序,并应用 LIMIT 子句来获取前N个匹配的行。 要应用此优化,必须没有 WHERE 子句,只有一个 ORDER BY 子句按降序排列。

  • FULLTEXT 只检索 COUNT(*) 与搜索词匹配的行 值的查询,没有其他 WHERE 子句。 编码 WHERE 子句 ,没有任何 比较运算符。 WHERE MATCH(text) AGAINST ('other_text')> 0

对于包含全文表达式的查询,MySQL在查询执行的优化阶段评估这些表达式。 优化器不只是查看全文表达式并进行估计,它实际上是在开发执行计划的过程中对它们进行评估。

这种行为的含义就是这样 EXPLAIN 对于全文查询通常比在全局阶段没有进行表达式评估的非全文查询慢。

EXPLAIN 由于优化期间发生匹配, 可能会 Select tables optimized away Extra 列中 显示全文查询 ; 在这种情况下,在以后的执行期间不需要访问表。

空间索引

您可以在空间数据类型上创建索引。 MyISAM InnoDB 支持空间类型的R树索引。 其他存储引擎使用B树来索引空间类型(除了 ARCHIVE ,它不支持空间类型索引)。

MEMORY存储引擎中的索引

MEMORY 存储引擎使用 HASH 默认的索引,而且还支持 BTREE 索引。

8.3.6多列索引

MySQL可以创建复合索引(即多列索引)。 索引最多可包含16列。 对于某些数据类型,您可以索引列的前缀(请参见 第8.3.5节“列索引” )。

MySQL可以对测试索引中所有列的查询使用多列索引,或者只测试第一列,前两列,前三列等的查询。 如果在索引定义中以正确的顺序指定列,则单个复合索引可以加速同一表上的多种查询。

多列索引可以视为排序数组,其行包含通过连接索引列的值创建的值。

注意

作为复合索引的替代方法,您可以 根据其他列的信息 引入 散列 列。 如果此列很短,相当独特且已编制索引,则它可能比 许多列上 索引 更快 在MySQL中,使用这个额外的列很容易:

SELECT * FROM tbl_name
  WHERE hash_col= MD5(CONCAT(val1val2))
  AND col1= val1AND col2= val2;

假设一个表具有以下规范:

CREATE TABLE测试(
    id INT NOT NULL,
    last_name CHAR(30)NOT NULL,
    first_name CHAR(30)NOT NULL,
    PRIMARY KEY(id),
    INDEX名称(last_name,first_name)
);

name 指数是在一个索引 last_name first_name 列。 该索引可用于查询中的查找,这些查询指定已知范围内的 last_name first_name 组合的 值。 它也可以用于仅指定 last_name 值的 查询, 因为该列是索引的最左前缀(如本节后面所述)。 因此, name 索引用于以下查询中的查找:

SELECT * FROM test WHERE last_name ='Widenius';

SELECT * FROM test
  WHERE last_name ='Widenius'AND first_name ='Michael';

SELECT * FROM test
  WHERE last_name ='Widenius'
  AND(first_name ='Michael'OR / first_name ='Monty');

SELECT * FROM test
  WHERE last_name ='Widenius'
  AND first_name> ='M'AND first_name <'N';

但是, name 指数 不是 用于以下查询中的查找:

SELECT * FROM test WHERE first_name ='Michael';

SELECT * FROM test
  WHERE last_name ='Widenius'或first_name ='Michael';

假设您发出以下内容 SELECT 语句:

SELECT * FROM tbl_name
  WHERE col1 = val1AND col2 =val2 ;

如果 col1 和上 存在多列索引 col2 ,则可以直接获取相应的行。 如果 col1 和上 存在单独的单列索引 col2 ,则优化程序会尝试使用索引合并优化(请参见 第8.2.1.3节“索引合并优化” ),或尝试通过确定哪个索引排除更多行并使用它来查找限制性最强的索引获取行的索引。

如果表具有多列索引,则优化程序可以使用索引的任何最左前缀来查找行。 例如,如果你有一个三列索引上 (col1, col2, col3) ,你有索引的搜索功能 (col1) (col1, col2) 以及 (col1, col2, col3)

如果列不形成索引的最左前缀,则MySQL无法使用索引执行查找。 假设您有以下 SELECT 所示 语句:

SELECT * FROM tbl_nameWHERE col1 = val1;
SELECT * FROM tbl_nameWHERE col1 = val1AND col2 = val2;

SELECT * FROM tbl_nameWHERE col2 = val2;
SELECT * FROM tbl_nameWHERE col2 = val2AND col3 = val3;

如果存在索引 (col1, col2, col3) ,则只有前两个查询使用索引。 第三和第四个查询确实包括索引的列,但不使用索引来进行查找,因为 (col2) (col2, col3) 不是的最左边的前缀 (col1, col2, col3)

8.3.7验证索引使用情况

始终检查所有查询是否确实使用了您在表中创建的索引。 使用该 EXPLAIN 语句,如 第8.8.1节“使用EXPLAIN优化查询”中所述

8.3.8 InnoDB和MyISAM索引统计信息收集

存储引擎收集有关表的统计信息以供优化程序使用。 表统计信息基于值组,其中值组是具有相同键前缀值的一组行。 出于优化程序的目的,一个重要的统计数据是平均值组大小。

MySQL使用以下方式的平均值组大小:

  • 估计每次 ref 访问 必须读取的行数

  • 估计部分连接将产生多少行; 也就是说,此表单的操作将产生的行数:

    (...)JOIN tbl_nameON tbl_namekey=expr
    

随着索引的平均值组大小增加,索引对于这两个目的不太有用,因为每个查找的平均行数增加:为了使索引有利于优化目的,最好每个索引值都以小为目标表中的行数。 当给定的索引值产生大量行时,索引不太有用,MySQL不太可能使用它。

平均值组大小与表基数相关,表基数是值组的数量。 SHOW INDEX 语句显示基于的基数值 N/S ,其中 N 是表中的行数, S 是平均值组大小。 该比率产生表中的近似值组数。

为联接基础上, <=> 比较运营商, NULL 没有从任何其它值区别对待: NULL <=> NULL ,就像 任何其他 N <=> N N

但是,对于基于 = 运算符的连接, NULL 与非 NULL 不同 (或两者) 时不成立 这会影响 对表单比较的访问 :如果当前值为, MySQL不会访问该表 expr1 = expr2 expr1 expr2 NULL ref tbl_name.key = expr expr NULL ,因为比较不能为真。

对于 = 比较, NULL 表格中有 多少 无关紧要 出于优化目的,相关值是非平均大小 NULL 值组 但是,MySQL目前不支持收集或使用该平均大小。

对于 InnoDB MyISAM 表,您可以 分别 通过 innodb_stats_method myisam_stats_method 系统变量 控制表统计信息的收集 这些变量有三个可能的值,它们的不同之处如下:

  • 当变量设置为 nulls_equal all时 NULL 值都被视为相同(即,它们都形成单个值组)。

    如果 NULL 值组大小远远高于平均非 NULL 值组大小,则此方法会向上倾斜平均值组大小。 这使得优化器中的索引对于查找非 NULL 值的 连接的实际效果不如它 因此,该 nulls_equal 方法可能导致优化器 ref 在应该的时候 不使用索引进行 访问。

  • 当变量设置为时 nulls_unequal NULL 值不被视为相同。 相反,每个 NULL 值形成一个大小为1的单独值组。

    如果您有许多 NULL 值,则此方法会向下倾斜平均值组大小。 如果平均非 NULL 值组大小很大, 则将 NULL 每个 数值作为大小为1的组会导致优化器高估查找非 NULL 值的 联接的索引 值。 因此, 当其他方法可能更好时 ,该 nulls_unequal 方法可能导致优化器使用此索引进行 ref 查找。

  • 当变量设置为时 nulls_ignored NULL 将忽略值。

如果您倾向于使用许多使用 <=> 而不是 使用的连接 = ,则 NULL 值在比较中并不特殊,而且一个 NULL 等于另一个。 在这种情况下, nulls_equal 是适当的统计方法。

innodb_stats_method 系统变量具有全局值; myisam_stats_method 系统变量有全局和会话值。 设置全局值会影响相应存储引擎中表的统计信息收集。 设置会话值仅影响当前客户端连接的统计信息收集。 这意味着您可以通过设置会话值来强制使用给定方法重新生成表的统计信息,而不会影响其他客户端 myisam_stats_method

要重新生成 MyISAM 表统计信息,可以使用以下任一方法:

关于使用 innodb_stats_method 和的 一些警告 myisam_stats_method

  • 您可以强制显式收集表统计信息,如上所述。 但是,MySQL也可能会自动收集统计信息。 例如,如果在执行表的语句过程中,其中一些语句修改了表,MySQL可能会收集统计信息。 (例如,对于批量插入或删除,或某些 ALTER TABLE 语句, 可能会发生 这种情况。)如果发生这种情况,则使用任何值收集统计信息 innodb_stats_method myisam_stats_method 当时有。 因此,如果使用一种方法收集统计信息,但在稍后自动收集表的统计信息时将系统变量设置为另一种方法,则将使用另一种方法。

  • 无法确定使用哪种方法为给定表生成统计信息。

  • 这些变量仅适用于 InnoDB MyISAM 表。 其他存储引擎只有一种收集表统计信息的方法。 通常它更接近 nulls_equal 方法。

8.3.9 B树和哈希索引的比较

了解B树和哈希数据结构有助于预测不同查询在索引中使用这些数据结构的不同存储引擎上的执行情况,特别是对于 MEMORY 允许您选择B树或哈希索引 存储引擎。

B树指数特征

A B树索引可以在使用表达式中使用的对列的比较 = > >= < <= ,或 BETWEEN 运营商。 LIKE 如果参数to LIKE 是一个不以通配符开头的常量字符串,则 索引也可用于 比较 例如,以下 SELECT 语句使用索引:

SELECT * FROM tbl_nameWHERE key_colLIKE'Patrick%';
SELECT * FROM tbl_nameWHERE key_colLIKE'Pat%_ck%';

在第一个语句中,仅 考虑 具有行的行 在第二个语句中,仅 考虑 具有行的行 'Patrick' <= key_col < 'Patricl' 'Pat' <= key_col < 'Pau'

以下 SELECT 语句不使用索引:

SELECT * FROM tbl_nameWHERE key_colLIKE'%Patrick%';
SELECT * FROM tbl_nameWHERE key_colLIKE other_col;

在第一个语句中, LIKE 值以通配符开头。 在第二个语句中,该 LIKE 值不是常量。

如果您使用 超过三个字符,MySQL使用 Turbo Boyer-Moore算法 初始化字符串的模式,然后使用此模式更快地执行搜索。 ... LIKE '%string%' string

使用 col_name IS NULL 雇用索引 的搜索 是否 col_name 已编制索引。

不跨越 子句中 所有 AND 级别的 任何索引 WHERE 不用于优化查询。 换句话说,为了能够使用索引,必须在每个 AND 组中 使用索引的前缀

以下 WHERE 子句使用索引:

...... WHERE index_part1= 1 AND index_part2= 2 AND other_column= 3

    / * index= 1 OR index= 2 * /
...... WHERE index= 1或A = 10 AND index= 2

    / *优化如“ index_part1='hello'”* /
...... WHERE index_part1='你好'AND index_part3= 5

    / *可以使用索引index1但不能使用index2index3* /
...... WHERE index1= 1 AND index2= 2 OR index1= 3 AND index3= 3;

这些 WHERE 子句 使用索引:

    / * index_part1未使用* /
...... WHERE index_part2= 1 AND index_part3= 2

    / *在WHERE子句的两个部分中都没有使用索引* /
...... WHERE index= 1或A = 10

    / *没有索引跨越所有行* /
...... WHERE index_part1= 1 OR index_part2= 10

有时MySQL不使用索引,即使有索引也是如此。 发生这种情况的一种情况是,优化器估计使用索引将需要MySQL访问表中非常大比例的行。 (在这种情况下,表扫描可能会快得多,因为它需要较少的搜索。)但是,如果这样的查询 LIMIT 只用于检索某些行,MySQL无论如何都会使用索引,因为它可以更快地找到在结果中返回几行。

哈希指数特征

哈希索引与刚才讨论的有些不同:

  • 它们仅用于使用 = <=> 运算符的 相等比较 (但 速度 非常 快)。 它们不用于比较运算符,例如 < 找到一系列值。 依赖于这种类型的单值查找的系统被称为 键值存储 ; 要将MySQL用于此类应用程序,请尽可能使用哈希索引。

  • 优化器无法使用哈希索引来加速 ORDER BY 操作。 (此类索引不能用于按顺序搜索下一个条目。)

  • MySQL无法确定两个值之间大约有多少行(范围优化器使用它来决定使用哪个索引)。 如果将a MyISAM InnoDB 表更改为哈希索引 MEMORY 表, 则可能会影响某些查询

  • 只有整个键可用于搜索行。 (使用B树索引,键的任何最左边的前缀都可用于查找行。)

8.3.10索引扩展的使用

InnoDB 通过将主键列附加到每个辅助索引来自动扩展它们。 考虑这个表定义:

CREATE TABLE t1(
  i1 INT NOT NULL DEFAULT 0,
  i2 INT NOT NULL DEFAULT 0,
  d DATE DEFAULT NULL,
  PRIMARY KEY(i1,i2),
  INDEX k_d(d)
)ENGINE = InnoDB;

此表定义列上的主键 (i1, i2) 它还 k_d 在列上 定义了二级索引 (d) ,但在内部 InnoDB 扩展了此索引并将其视为列 (d, i1, i2)

在确定如何以及是否使用该索引时,优化程序会考虑扩展二级索引的主键列。 这可以带来更高效的查询执行计划和更好的性能。

优化器可以使用扩展二级索引 ref range index_merge 索引访问,松散索引扫描访问,参加和排序优化,以及 MIN() / MAX() 优化。

以下示例显示优化程序是否使用扩展二级索引如何影响执行计划。 假设 t1 填充了这些行:

插入t1值
(1,1,'1998-01-01'),(1,2,'1999-01-01'),
(1,3,'2000-01-01'),(1,4,'2001-01-01'),
(1,5,'2002-01-01'),(2,1,'1998-01-01'),
(2,2,'1999-01-01'),(2,3,'2000-01-01'),
(2,4,'2001-01-01'),(2,5,'2002-01-01'),
(3,1,'1998-01-01'),(3,2,'1999-01-01'),
(3,3,'2000-01-01'),(3,4,'2001-01-01'),
(3,5,'2002-01-01'),(4,1,'1998-01-01'),
(4,2,'1999-01-01'),(4,3,'2000-01-01'),
(4,4,'2001-01-01'),(4,5,'2002-01-01'),
(5,1,'1998-01-01'),(5,2,'1999-01-01'),
(5,3,'2000-01-01'),(5,4,'2001-01-01'),
(5,5,'2002-01-01');

现在考虑这个查询:

EXPLAIN SELECT COUNT(*)FROM t1 WHERE i1 = 3 AND d ='2000-01-01'

执行计划取决于是否使用扩展索引。

当优化器不考虑索引扩展时,它 k_d 将索引 视为 索引 (d) EXPLAIN 对于查询产生此结果:

MySQL的> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G
*************************** 1。排******************** *******
           id:1
  select_type:SIMPLE
        表:t1
         类型:ref
possible_keys:PRIMARY,k_d
          key:k_d
      key_len:4
          ref:const
         行:5
        额外:使用在哪里; 使用索引

当优化需要索引扩展到帐户,它把 k_d 作为 (d, i1, i2) 在这种情况下,它可以使用最左边的索引前缀 (d, i1) 来生成更好的执行计划:

MySQL的> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G
*************************** 1。排******************** *******
           id:1
  select_type:SIMPLE
        表:t1
         类型:ref
possible_keys:PRIMARY,k_d
          key:k_d
      key_len:8
          ref:const,const
         行:1
        额外:使用索引

在这两种情况下,都 key 表示优化器将使用二级索引, k_d EXPLAIN 输出显示使用扩展索引的这些改进:

  • key_len 从4个字节到8个字节去,表明键查找中使用的列 d i1 ,而不仅仅是 d

  • ref 从价值变动 const const,const ,因为键查找使用两个关键部分,没有之一。

  • rows 计数降低从5到1,表明 InnoDB 应该需要检查更少的行,以产生结果。

  • Extra 值从变化 Using where; Using index Using index 这意味着只能使用索引读取行,而无需查询数据行中的列。

使用扩展索引的优化器行为的差异也可以通过以下方式看出 SHOW STATUS

FLUSH TABLE t1;
冲洗状态;
SELECT COUNT(*)FROM t1 WHERE i1 = 3 AND d ='2000-01-01';
SHOW STATUS LIKE'handler_read%'

前面的语句包括 FLUSH TABLES FLUSH STATUS 刷新表缓存并清除状态计数器。

没有索引扩展, SHOW STATUS 产生以下结果:

+ ----------------------- + ------- +
| Variable_name | 价值|
+ ----------------------- + ------- +
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 5 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+ ----------------------- + ------- +

使用索引扩展, SHOW STATUS 生成此结果。 Handler_read_next 值从5减少到1,表明更有效地使用索引:

+ ----------------------- + ------- +
| Variable_name | 价值|
+ ----------------------- + ------- +
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 1 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+ ----------------------- + ------- +

系统变量 use_index_extensions 标志 optimizer_switch 允许在确定如何使用 InnoDB 表的二级索引 时控制优化程序是否将主键列考虑在内 默认情况下, use_index_extensions 已启用。 要检查禁用索引扩展是否会提高性能,请使用以下语句:

SET optimizer_switch ='use_index_extensions = off';

优化程序对索引扩展的使用受索引(16)中关键部分数量和最大密钥长度(3072字节)的通常限制。

8.3.11生成列索引的优化程序使用

MySQL支持生成列的索引。 例如:

CREATE TABLE t1(f1 INT,gc INT AS(f1 + 1)STORED,INDEX(gc));

生成的列 gc 被定义为表达式 f1 + 1 该列也被编入索引,优化器可以在执行计划构建期间考虑该索引。 在以下查询中,该 WHERE 子句引用 gc 并且优化器会考虑该列上的索引是否产生更有效的计划:

SELECT * FROM t1 WHERE gc> 9;

优化器可以使用生成列上的索引来生成执行计划,即使在没有按名称查询这些列的情况下也没有直接引用。 会发生此如果 WHERE ORDER BY GROUP BY 条款是指一些索引生成列的定义相匹配的表达式。 以下查询不直接引用 gc 但使用与以下定义匹配的表达式 gc

SELECT * FROM t1 WHERE f1 + 1> 9;

优化器识别出表达 f1 + 1 的定义相匹配 gc ,并且 gc 被索引,因此它认为执行计划在施工期间的索引。 你可以看到这个 EXPLAIN

MySQL的> EXPLAIN SELECT * FROM t1 WHERE f1 + 1 > 9\G
*************************** 1。排******************** *******
           id:1
  select_type:SIMPLE
        表:t1
   分区:NULL
         类型:范围
possible_keys:gc
          关键:gc
      key_len:5
          ref:NULL
         行:1
     过滤:100.00
        额外:使用索引条件

实际上,优化器已将表达式替换为与表达式 f1 + 1 匹配的生成列的名称。 EXPLAIN 显示 的扩展 信息中 可用的重写查询中也很明显 SHOW WARNINGS

MySQL的> SHOW WARNINGS\G
*************************** 1。排******************** *******
  等级:注意
   代码:1003
消息:/ * select#1 * / select`test` .t1` .f1` AS`f1`,`test` .t1``gc`
         AS`gc`来自`test` .t1` where(`test` .t1``gc`> 9)

以下限制和条件适用于优化程序使用生成的列索引:

  • 对于与生成的列定义匹配的查询表达式,表达式必须相同且必须具有相同的结果类型。 例如,如果生成的列表达式是 f1 + 1 ,则优化程序将在查询使用时识别匹配 1 + f1 ,或者将 f1 + 1 (整数表达式)与字符串进行比较。

  • 优化适用于这些操作符: = < <= > >= BETWEEN ,和 IN()

    对于除 BETWEEN 之外的运算符 IN() ,任一操作数都可以由匹配的生成列替换。 对于 BETWEEN IN() ,只有第一个参数可以由匹配的生成列替换,其他参数必须具有相同的结果类型。 BETWEEN 并且 IN() 尚不支持涉及JSON值的比较。

  • 必须将生成的列定义为至少包含函数调用或前一项中提到的运算符之一的表达式。 表达式不能包含对另一列的简单引用。 例如, gc INT AS (f1) STORED 仅包含列引用,因此 gc 不考虑 索引

  • 为了将字符串与索引生成的列进行比较,这些列从返回带引号的字符串的JSON函数计算值, JSON_UNQUOTE() 在列定义中需要从函数值中删除额外的引号。 (为了直接比较字符串和函数结果,JSON比较器处理引号删除,但索引查找不会发生这种情况。)例如,不要像这样编写列定义:

    doc_name TEXT AS(JSON_EXTRACT(jdoc,'$ .name'))已存储
    

    写这样:

    doc_name TEXT AS(JSON_UNQUOTE(JSON_EXTRACT(jdoc,'$ .name')))STORED
    

    使用后一个定义,优化器可以检测这两个比较的匹配:

    ... WHERE JSON_EXTRACT(jdoc,'$ .name')=' some_string'...
    ... WHERE JSON_UNQUOTE(JSON_EXTRACT(jdoc,'$。name'))=' some_string'...
    

    如果没有 JSON_UNQUOTE() 列定义,优化程序仅会检测到第一个比较的匹配项。

  • 如果优化器选择了错误的索引,则可以使用索引提示来禁用它,并强制优化器进行不同的选择。

8.3.12隐形指数

MySQL支持隐形索引; 也就是说,优化器未使用的索引。 该功能适用​​于主键以外的索引(显式或隐式)。

默认情况下,索引是可见的。 要明确控制指标能见度新的索引,使用一个 VISIBLE INVISIBLE 关键字作为指标定义的一部分 CREATE TABLE CREATE INDEX 或者 ALTER TABLE

CREATE TABLE t1(
  我是INT,
  j INT,
  k INT,
  INDEX i_idx(i)INVISIBLE
)ENGINE = InnoDB;
CREATE INDEX j_idx ON t1(j)INVISIBLE;
ALTER TABLE t1 ADD INDEX k_idx(k)INVISIBLE;

要更改现有索引的可见性,请 操作中 使用 VISIBLE INVISIBLE 关键字 ALTER TABLE ... ALTER INDEX

ALTER TABLE t1 ALTER INDEX i_idx INVISIBLE;
ALTER TABLE t1 ALTER INDEX i_idx VISIBLE;

可以从 INFORMATION_SCHEMA.STATISTICS 表或 SHOW INDEX 输出中 获得有关索引是可见还是不可见的信息 例如:

MySQL的> SELECT INDEX_NAME, IS_VISIBLE
       FROM INFORMATION_SCHEMA.STATISTICS
       WHERE TABLE_SCHEMA = 'db1' AND TABLE_NAME = 't1';
+ ------------ + ------------ +
| INDEX_NAME | IS_VISIBLE |
+ ------------ + ------------ +
| i_idx | 是的|
| j_idx | 没有|
| k_idx | 没有|
+ ------------ + ------------ +

不可见索引可以测试删除索引对查询性能的影响,而不会进行破坏性更改,如果索引需要,则必须撤消。 对于大型表而言,删除和重新添加索引可能是昂贵的,而使其不可见且可见是快速的就地操作。

如果优化器需要或使用实际上不可见的索引,有几种方法可以注意其缺席对表的查询的影响:

  • 包含引用不可见索引的索引提示的查询会发生错误。

  • 性能架构数据显示受影响查询的工作负载增加。

  • 查询具有不同的 EXPLAIN 执行计划。

  • 查询显示在以前未出现的慢查询日志中。

系统变量 use_invisible_indexes 标志 optimizer_switch 控制优化程序是否使用不可见索引来构建查询执行计划。 如果该标志是 off (缺省值),则优化器会忽略不可见的索引(与引入此标志之前的行为相同)。 如果该标志是 on ,则不可见索引保持不可见,但优化程序会将它们考虑在内以执行计划构建。

索引可见性不会影响索引维护。 例如,每次对表行的更改都会继续更新索引,并且唯一索引可防止将重复项插入到列中,无论索引是可见还是不可见。

没有显式主键的表可能仍然具有有效的隐式主键,如果 UNIQUE 有任何 索引 NOT NULL 在这种情况下,第一个这样的索引将相同的约束放在表行上作为显式主键,并且该索引不能变为不可见。 请考虑以下表定义:

创建表t2(
  我没有,
  j INT NOT NULL,
  独特的j_idx(j)
)ENGINE = InnoDB;

该定义不包含显式主键,但 NOT NULL 上的索引 j 将相同的约束放在行上作为主键,并且不能使其不可见:

MySQL的> ALTER TABLE t2 ALTER INDEX j_idx INVISIBLE;
错误3522(HY000):主键索引不能是不可见的。

现在假设将一个显式主键添加到表中:

更改表t2添加主要密钥(i);

显式主键不能隐藏。 此外,唯一索引 j 不再充当隐式主键,因此可以使其不可见:

MySQL的> ALTER TABLE t2 ALTER INDEX j_idx INVISIBLE;
查询正常,0行受影响(0.03秒)

8.3.13降序索引

MySQL支持降序索引: DESC 在索引定义中不再被忽略,但会导致按键降序存储键值。 以前,索引可能会以相反的顺序扫描,但性能会受到影响。 可以按正向顺序扫描降序索引,这样更有效。 当最有效的扫描顺序混合某些列的升序和其他列的降序时,降序索引还使优化器可以使用多列索引。

请考虑以下表定义,其中包含两列和四列两列索引定义,用于列上升序和降序索引的各种组合:

CREATE TABLE t(
  c1 INT,c2 INT,
  INDEX idx1(c1 ASC,c2 ASC),
  INDEX idx2(c1 ASC,c2 DESC),
  INDEX idx3(c1 DESC,c2 ASC),
  INDEX idx4(c1 DESC,c2 DESC)
);

表定义产生四个不同的索引。 优化器可以为每个 ORDER BY 子句 执行正向索引扫描 ,无需使用 filesort 操作:

ORDER BY c1 ASC,c2 ASC  - 优化器可以使用idx1
ORDER BY c1 DESC,c2 DESC  - 优化器可以使用idx4
ORDER BY c1 ASC,c2 DESC  - 优化器可以使用idx2
ORDER BY c1 DESC,c2 ASC  - 优化器可以使用idx3

降序索引的使用受以下条件限制:

  • 只有 InnoDB 存储引擎 支持降序索引 ,具有以下限制:

    • 如果索引包含降序索引键列或主键包含降序索引列,则不支持对辅助索引进行更改缓冲。

    • InnoDB SQL解析器不使用降序索引。 对于 InnoDB 全文搜索,这意味着 FTS_DOC_ID 索引表 所需 的索引不能定义为降序索引。 有关更多信息,请参见 第15.6.2.4节“InnoDB FULLTEXT索引”

  • 升序索引可用的所有数据类型都支持降序索引。

  • 降序索引支持普通(nongenerated)和生成的列(包括 VIRTUAL STORED )。

  • DISTINCT 可以使用包含匹配列的任何索引,包括降序键部分。

  • 具有降序关键部分的索引不用于 MIN() / MAX() 优化调用聚合函数但没有 GROUP BY 子句的查询。

  • 支持降序索引 BTREE 但不 支持 HASH 索引。 索引 FULLTEXT SPATIAL 索引 不支持降序 索引。

    明确指定 ASC DESC 为代号 HASH FULLTEXT SPATIAL 一个错误的索引结果。

8.3.14 TIMESTAMP列的索引查找

时间值 TIMESTAMP 作为UTC值 存储在 列中,插入到 TIMESTAMP 列中 和从 列中 检索的值 在会话时区和UTC之间转换。 (这与 CONVERT_TZ() 函数 执行的转换类型相同 。如果会话时区为UTC,则实际上没有时区转换。)

由于夏令时(DST)等本地时区更改的约定,UTC和非UTC时区之间的转换在两个方向上都不是一对一的。 不同的UTC值在另一个时区可能不同。 以下示例显示了在非UTC时区中变为相同的不同UTC值:

mysql> CREATE TABLE tstable (ts TIMESTAMP);
mysql> SET time_zone = 'UTC'; -- insert UTC values
mysql> 
mysql>INSERT INTO tstable VALUES
       ('2018-10-28 00:30:00'),
       ('2018-10-28 01:30:00');SELECT ts FROM tstable;
+ --------------------- +
| ts |
+ --------------------- +
| 2018-10-28 00:30:00 |
| 2018-10-28 01:30:00 |
+ --------------------- +
mysql> SET time_zone = 'MET'; -- retrieve non-UTC values
mysql>SELECT ts FROM tstable;
+ --------------------- +
| ts |
+ --------------------- +
| 2018-10-28 02:30:00 |
| 2018-10-28 02:30:00 |
+ --------------------- +
注意

要使用诸如 'MET' 或的 命名时区 'Europe/Amsterdam' ,必须正确设置时区表。 有关说明,请参见 第5.1.13节“MySQL服务器时区支持”

转换到 'MET' 时区 时,您可以看到两个不同的UTC值相同 这种现象可能导致给定 TIMESTAMP 列查询的 结果不同 ,具体取决于优化程序是否使用索引来执行查询。

假设查询使用 WHERE 子句 从前面显示的表中选择值, 以在 ts 列中 搜索 单个特定值,例如用户提供的时间戳文字:

SELECT ts FROM tstable
在哪里ts =' literal';

进一步假设查询在以下条件下执行:

  • 会话时区不是UTC并且具有DST转换。 例如:

    SET time_zone ='MET';
    
  • TIMESTAMP 由于DST转换, 存储在 列中的 唯一UTC值在 会话时区中不是唯一的。 (前面显示的示例说明了如何发生这种情况。)

  • 该查询指定在会话时区中进入DST的小时内的搜索值。

在这些条件下, WHERE 对于非索引和索引查找,子句中 的比较以 不同的方式发生,并导致不同的结果:

  • 如果没有索引或优化程序无法使用它,则会话时区中会进行比较。 优化器执行表扫描,在其中检索每个 ts 列值,将其从UTC转换为会话时区,并将其与搜索值(也在会话时区中解释)进行比较:

    MySQL的> SELECT ts FROM tstable
           WHERE ts = '2018-10-28 02:30:00';
    + --------------------- +
    | ts |
    + --------------------- +
    | 2018-10-28 02:30:00 |
    | 2018-10-28 02:30:00 |
    + --------------------- +
    

    因为存储的 ts 值被转换为会话时区,所以查询可以返回两个时间戳值,这两个时间戳值与UTC值不同但在会话时区中相等:在时钟更改时DST转换之前发生的一个值,和DST转换后发生的一个值。

  • 如果存在可用索引,则以UTC格式进行比较。 优化器执行索引扫描,首先将搜索值从会话时区转换为UTC,然后将结果与UTC索引条目进行比较:

    mysql> ALTER TABLE tstable ADD INDEX (ts);
    mysql>SELECT ts FROM tstable
           WHERE ts = '2018-10-28 02:30:00';
    + --------------------- +
    | ts |
    + --------------------- +
    | 2018-10-28 02:30:00 |
    + --------------------- +
    

    在这种情况下,(转换的)搜索值仅与索引条目匹配,并且因为不同存储的UTC值的索引条目也是不同的,所以搜索值只能匹配其中一个。

由于非索引和索引查找的优化器操作不同,查询在每种情况下都会产生不同的结果。 非索引查找的结果将返回在会话时区中匹配的所有值。 索引查找不能这样做:

  • 它在存储引擎中执行,该引擎仅知道UTC值。

  • 对于映射到相同UTC值的两个不同会话时区值,索引查找仅匹配相应的UTC索引条目并仅返回单个行。

在前面的讨论中,存储的数据集 tstable 恰好由不同的UTC值组成。 在这种情况下,所显示的表单的所有使用索引的查询最多匹配一个索引条目。

如果索引不是 UNIQUE ,则表(和索引)可以存储给定UTC值的多个实例。 例如,该 ts 列可能包含UTC值的多个实例 '2018-10-28 00:30:00' 在这种情况下,使用索引的查询将返回它们中的每一个(转换为 '2018-10-28 02:30:00' 结果集中 的MET值 )。 使用索引的查询仍然可以将转换后的搜索值与UTC索引条目中的单个值匹配,而不是匹配转换为会话时区中搜索值的多个UTC值。

如果返回 ts 在会话时区中匹配的 所有 很重要 ,则解决方法是禁止使用带有 IGNORE INDEX 提示 的索引

MySQL的> SELECT ts FROM tstable
       IGNORE INDEX (ts)
       WHERE ts = '2018-10-28 02:30:00';
+ --------------------- +
| ts |
+ --------------------- +
| 2018-10-28 02:30:00 |
| 2018-10-28 02:30:00 |
+ --------------------- +

对于两个方向上的时区转换,同样缺乏一对一映射也发生在其他上下文中,例如使用 FROM_UNIXTIME() UNIX_TIMESTAMP() 函数 执行的转换 请参见 第12.7节“日期和时间函数”

8.4优化数据库结构

在您作为数据库设计者的角色中,寻找组织模式,表和列的最有效方法。 在调整应用程序代码时,您可以最小化I / O,将相关项目保持在一起,并提前计划,以便在数据量增加时性能保持高水平。 从高效的数据库设计开始,团队成员可以更轻松地编写高性能的应用程序代码,并使数据库可以随着应用程序的发展和重写而持久。

8.4.1优化数据大小

设计表以最小化磁盘空间。 这可以通过减少写入磁盘和从磁盘读取的数据量来实现巨大的改进。 较小的表通常需要较少的主内存,而其内容在查询执行期间被主动处理。 表数据的任何空间缩减也会导致较小的索引可以更快地处理。

MySQL支持许多不同的存储引擎(表类型)和行格式。 对于每个表,您可以决定使用哪种存储和索引方法。 为您的应用选择合适的表格格式可以为您带来巨大的性能提升。 请参见 第15章, InnoDB存储引擎 第16章, 备用存储引擎

通过使用此处列出的技术,您可以获得更好的表性能并最大限度地减少存储空间:

表列

  • 尽可能使用最有效(最小)的数据类型。 MySQL有许多专门的类型可以节省磁盘空间和内存。 例如,如果可能,请使用较小的整数类型来获取较小的表。 MEDIUMINT 通常是一个更好的选择, INT 因为一 MEDIUMINT 列使用的空间减少了25%。

  • NOT NULL 如果可能, 声明列 它通过更好地使用索引并消除测试每个值是否的开销来使SQL操作更快 NULL 您还可以节省一些存储空间,每列一位。 如果您确实需要 NULL 表中的值,请使用它们。 只需避免允许 NULL 每列中的值 的默认设置

行格式

  • InnoDB DYNAMIC 默认情况下, 使用 行格式 创建表 要使用行格式 DYNAMIC ,请 语句中 显式 配置 innodb_default_row_format 或指定 ROW_FORMAT 选项 CREATE TABLE ALTER TABLE

    紧凑的行格式系列,包括 COMPACT DYNAMIC COMPRESSED ,减少行存储空间,代价是增加某些操作的CPU使用。 如果您的工作负载是受缓存命中率和磁盘速度限制的典型工作负载,则可能会更快。 如果这是一种受CPU速度限制的罕见情况,则可能会更慢。

    紧凑的行格式系列还可以 CHAR 在使用可变长度字符集(如 utf8mb3 或) 优化 列存储 utf8mb4 ROW_FORMAT=REDUNDANT 占用 ×字符集的最大字节长度。 许多语言可以主要使用单字节 字符 编写 ,因此固定的存储长度通常会浪费空间。 使用紧凑的行格式 系列, 在to 的范围内分配可变数量的存储空间 CHAR(N) N utf8 InnoDB N N ×通过去除尾随空格,这些列的字符集的最大字节长度。 最小存储长度是 N 字节,以便在典型情况下进行就地更新。 有关更多信息,请参见 第15.10节“InnoDB行格式”

  • 要通过以压缩格式存储表数据来进一步减小空间,请指定 ROW_FORMAT=COMPRESSED 何时创建 InnoDB 表,或 在现有 运行 myisampack 命令 MyISAM InnoDB 压缩表是可读写的,而 MyISAM 压缩表是只读的。)

  • 对于 MyISAM 表中,如果没有任何可变长度列( VARCHAR TEXT ,或 BLOB 列),一个固定大小的行格式被使用。 这更快但可能浪费一些空间。 请参见 第16.2.3节“MyISAM表存储格式” 即使您有 VARCHAR 包含该 CREATE TABLE 选项的 列, 也可以提示您希望具有固定长度的行 ROW_FORMAT=FIXED

索引

  • 表的主索引应尽可能短。 这使得每行的识别变得简单而有效。 对于 InnoDB 表,主键列在每个辅助索引条目中都是重复的,因此如果您有许多辅助索引,则短主键可以节省大量空间。

  • 仅创建提高查询性能所需的索引。 索引适用于检索,但会降低插入和更新操作的速度。 如果您主要通过搜索列的组合来访问表,请在它们上创建单个复合索引,而不是为每列创建单独的索引。 索引的第一部分应该是最常用的列。 如果 从表中选择时 总是 使用多列,则索引中的第一列应该是具有最多重复的列,以获得更好的索引压缩。

  • 如果长字符串列很可能在第一个字符数上有唯一的前缀,那么最好只使用MySQL支持在列的最左边部分创建索引来索引此前缀(参见 第13.1.15节) ,“CREATE INDEX语法” )。 较短的索引更快,不仅因为它们需要更少的磁盘空间,而且因为它们还会在索引缓存中为您提供更多的命中,从而减少磁盘搜索次数。 请参见 第5.1.1节“配置服务器”

加盟

  • 在某些情况下,分成两个经常扫描的表可能是有益的。 如果它是动态格式表,则尤其如此,并且可以使用较小的静态格式表,该表可用于在扫描表时查找相关行。

  • 在具有相同数据类型的不同表中声明具有相同信息的列,以基于相应列加速连接。

  • 保持列名简单,以便您可以在不同的表中使用相同的名称并简化连接查询。 例如,在名为的表中 customer ,使用列名 name 而不是 customer_name 要使您的名称可移植到其他SQL服务器,请考虑将它们保持为短于18个字符。

正常化

  • 通常,尽量保持所有数据不冗余(观察数据库理论中称为 第三范式的数据 )。 不是重复名称和地址等冗长的值,而是为它们分配唯一的ID,在多个较小的表中根据需要重复这些ID,并通过引用join子句中的ID来连接查询中的表。

  • 如果速度比磁盘空间更重要,并且保留多个数据副本的维护成本,例如在您分析大型表中的所有数据的商业智能场景中,您可以放宽规范化规则,复制信息或创建汇总表到获得更快的速度。

8.4.2优化MySQL数据类型

8.4.2.1优化数值数据

  • 对于唯一ID或可以表示为字符串或数字的其他值,首选数字列以字符串列。 由于较大的数值可以存储在比相应字符串更少的字节中,因此传输和比较它们的速度更快,占用的内存更少。

  • 如果使用数字数据,则在许多情况下从数据库访问信息(使用实时连接)比访问文本文件更快。 数据库中的信息可能以比文本文件更紧凑的格式存储,因此访问它涉及更少的磁盘访问。 您还可以在应用程序中保存代码,因为您可以避免解析文本文件以查找行和列边界。

8.4.2.2优化字符和字符串类型

对于字符和字符串列,请遵循以下准则:

  • 当您不需要特定于语言的排序规则功能时,请使用二进制排序顺序进行快速比较和排序操作。 您可以使用 BINARY 运算符在特定查询中使用二进制排序规则。

  • 比较来自不同列的值时,请尽可能声明具有相同字符集和排序规则的列,以避免在运行查询时进行字符串转换。

  • 对于小于8KB的列值,请使用binary VARCHAR 而不是 BLOB GROUP BY ORDER BY 条款可以生成临时表,这些临时表可以使用的 MEMORY 存储引擎,如果原来的表不包含任何 BLOB 列。

  • 如果表包含字符串列(如名称和地址),但许多查询不检索这些列,请考虑将字符串列拆分为单独的表,并在必要时使用带有外键的连接查询。 当MySQL从一行中检索任何值时,它会读取包含该行的所有列(以及可能的其他相邻行)的数据块。 仅使用最常用的列保持每行较小,允许更多行适合每个数据块。 这种紧凑的表减少了常见查询的磁盘I / O和内存使用。

  • 当您使用随机生成的值作为 InnoDB 表中 的主键时,请在其 前面加上一个升序值,如可能的当前日期和时间。 当连续的主值物理存储在彼此附近时, InnoDB 可以更快地插入和检索它们。

  • 有关 数字列通常优于等效字符串列的原因 请参见 第8.4.2.1节“优化数值数据”

8.4.2.3优化BLOB类型

  • 存储包含文本数据的大blob时,请考虑先压缩它。 当整个表格被 InnoDB 压缩时,请勿使用此技术 MyISAM

  • 对于具有多个列的表,要减少不使用BLOB列的查询的内存要求,请考虑将BLOB列拆分为单独的表,并在需要时使用连接查询引用它。

  • 由于检索和显示BLOB值的性能要求可能与其他数据类型非常不同,因此您可以将BLOB特定的表放在不同的存储设备上,甚至是单独的数据库实例上。 例如,要检索BLOB,可能需要大型顺序磁盘读取,这种读取更适合传统硬盘驱动器而不是 SSD设备

  • 有关 二进制 列有时优于等效BLOB列的 原因 请参见 第8.4.2.2节“优化字符和字符串类型” VARCHAR

  • 您可以将列值的哈希值存储在单独的列中,索引该列,并在查询中测试哈希值,而不是针对非常长的文本字符串测试相等性。 (使用 MD5() or CRC32() 函数生成哈希值。)由于哈希函数可以为不同的输入生成重复的结果,因此您仍然 在查询中 包含一个子句 以防止错误匹配; 性能优势来自较小的,易于扫描的散列值索引。 AND blob_column = long_string_value

8.4.3优化许多表格

用于快速保持个别查询的一些技术涉及在多个表之间拆分数据。 当表的数量达到数千甚至数百万时,处理所有这些表的开销成为新的性能考虑因素。

8.4.3.1 MySQL如何打开和关闭表

执行 mysqladmin status 命令时,您应该看到如下内容:

正常运行时间:426运行线程:1个问题:11082
重新加载:1张桌子:12

Open tables 如果您的表少于12个,那么12 值可能有点令人费解。

MySQL是多线程的,因此可能有许多客户端同时为给定的表发出查询。 为了最大限度地减少同一个表上具有不同状态的多个客户端会话的问题,该表由每个并发会话独立打开。 这使用额外的内存但通常会提高性能。 对于 MyISAM 表,每个打开表的客户端的数据文件都需要一个额外的文件描述符。 (相比之下,索引文件描述符在所有会话之间共享。)

table_open_cache max_connections 系统变量影响服务器保持打开的文件的最大数量。 如果增加这些值中的一个或两个,则可能会遇到操作系统对每个进程的打开文件描述符数量施加的限制。 许多操作系统允许您增加开放文件限制,尽管该方法因系统而异。 请参阅操作系统文档以确定是否可以增加限制以及如何增加限制。

table_open_cache 与...有关 max_connections 例如,对于200个并发运行的连接,请指定至少一个表缓存大小 ,其中 是您执行的任何查询中每个连接的最大表数。 您还必须为临时表和文件保留一些额外的文件描述符。 200 * N N

确保您的操作系统可以处理该 table_open_cache 设置 隐含的打开文件描述符的数量 如果 table_open_cache 设置得太高,MySQL可能会用完文件描述符并出现诸如拒绝连接或无法执行查询等症状。

还要考虑到 MyISAM 存储引擎需要为每个唯一的打开表提供两个文件描述符。 要增加MySQL可用的文件描述符数,请设置 open_files_limit 系统变量。 请参见 第B.4.2.17节“找不到文件和类似错误”

打开表的缓存保持在 table_open_cache 条目 级别 服务器在启动时自动调整缓存大小。 要显式设置大小,请 table_open_cache 在启动时 设置 系统变量。 MySQL可以临时打开比这更多的表来执行查询,如本节后面所述。

在以下情况下,MySQL会关闭一个未使用的表并将其从表缓存中删除:

当表缓存填满时,服务器使用以下过程来查找要使用的缓存条目:

  • 从最近最少使用的表开始,发布当前未使用的表。

  • 如果必须打开新表,但缓存已满且无法释放表,则会根据需要临时扩展缓存。 当缓存处于临时扩展状态并且表从已使用状态变为未使用状态时,表将关闭并从缓存中释放。

MyISAM 为每个并发访问打开 一个 表。 这意味着如果两个线程访问同一个表,或者一个线程在同一个查询中两次访问该表(例如,通过将表连接到自身),则需要打开两次表。 每个并发打开都需要表缓存中的条目。 任何 MyISAM 的第一次打开都 需要两个文件描述符:一个用于数据文件,另一个用于索引文件。 表的每次额外使用仅为数据文件提供一个文件描述符。 索引文件描述符在所有线程之间共享。

如果要使用该 语句 打开表, 则会为该线程分配专用的表对象。 此表对象不由其他线程共享,并且在线程调用 或线程终止 之前不会关闭 发生这种情况时,表将被放回表缓存中(如果缓存未满)。 请参见 第13.2.4节“HANDLER语法” HANDLER tbl_name OPEN HANDLER tbl_name CLOSE

要确定表缓存是否太小,请检查 Opened_tables 状态变量,该变量指示自服务器启动以来的表打开操作数:

MySQL的> SHOW GLOBAL STATUS LIKE 'Opened_tables';
+ --------------- + ------- +
| Variable_name | 价值|
+ --------------- + ------- +
| Opened_tables | 2741 |
+ --------------- + ------- +

如果值非常大或快速增加,即使您没有发出多个 FLUSH TABLES 语句,也请 table_open_cache 在服务器启动时 增加该 值。

8.4.3.2在同一数据库中创建多个表的缺点

如果 MyISAM 同一数据库目录中 有许多 表,则打开,关闭和创建操作很慢。 如果 SELECT 在许多不同的表上 执行 语句,则表缓存已满时会产生一些开销,因为对于每个必须打开的表,必须关闭另一个表。 您可以通过增加表缓存中允许的条目数来减少此开销。

8.4.4 MySQL中的内部临时表使用

在某些情况下,服务器在处理语句时创建内部临时表。 用户无法直接控制何时发生这种情况。

服务器在以下条件下创建临时表:

要确定语句是否需要临时表,请使用 EXPLAIN 并检查该 Extra 列是否显示 Using temporary (请参见 第8.8.1节“使用EXPLAIN优化查询” )。 对于派生或具体化的临时表, EXPLAIN 不一定会说 Using temporary 对于使用窗口功能,陈述 EXPLAIN FORMAT=JSON 总是提供有关窗口步骤的信息。 如果窗口函数使用临时表,则为每个步骤指示。

当服务器创建内部临时表(在内存或磁盘上)时,它会递增 Created_tmp_tables 状态变量。 如果服务器在磁盘上创建表(最初或通过转换内存表),它会递增 Created_tmp_disk_tables 状态变量。

某些查询条件会阻止使用内存中的临时表,在这种情况下,服务器会使用磁盘上的表:

  • 表中存在a BLOB TEXT 列。 但是, TempTable 存储引擎是MySQL 8.0中内存内部临时表的默认存储引擎,从MySQL 8.0.13开始支持二进制大对象类型。 请参阅 内部临时表存储引擎

  • SELECT 如果 UNION UNION ALL 使用的 列表中 最大长度大于512的字符串列(二进制字符串的字节数,非二进制字符串的字符数)

  • SHOW COLUMNS DESCRIBE 语句中使用 BLOB 作为用于某些列的类型,从而用于结果的临时表是磁盘上的表。

对于 UNION 满足特定 条件的 语句, 服务器不使用临时表 相反,它仅从临时表创建中保留执行结果列类型转换所必需的数据结构。 该表未完全实例化,并且没有写入或读取行; 行直接发送到客户端。 结果是减少了内存和磁盘要求,并且在第一行发送到客户端之前的延迟较小,因为服务器不需要等到最后一个查询块执行。 EXPLAIN 和优化器跟踪输出反映了这个执行策略: UNION RESULT 查询块不存在,因为该块对应于从临时表中读取的部分。

这些条件在 UNION 没有临时表的 情况下有资格 进行评估:

  • 工会是 UNION ALL ,不是 UNION UNION DISTINCT

  • 没有全球 ORDER BY 条款。

  • 联合不是 {INSERT | REPLACE} ... SELECT ... 语句 的顶级查询块

内部临时表存储引擎

内部临时表可以在内存中保持并且通过被处理 TempTable MEMORY 存储引擎,或者由存储在磁盘上 InnoDB 存储引擎。

用于内存内部临时表的存储引擎

internal_tmp_mem_storage_engine 会话变量定义了用于在存储器内的临时表的存储引擎。 允许的值是 TempTable (默认值)和 MEMORY

TempTable 存储引擎提供了有效的存储 VARCHAR VARBINARY 列。 从MySQL 8.0.13开始支持存储其他二进制大对象类型。 temptable_max_ram 配置选项定义了可以由占据的RAM的最大数量 TempTable 存储引擎启动的形式内存映射临时文件分配从磁盘空间(默认值)或之前 InnoDB 的磁盘上的内部临时表。 默认 temptable_max_ram 设置为1GiB。 temptable_use_mmap 变量(在MySQL 8.0.16中引入)控制TempTable存储引擎是否使用内存映射文件或 InnoDB temptable_max_ram 超出限制 时的磁盘内部临时表 默认设置为 temptable_use_mmap=ON

TempTable 存储引擎 使用内存映射的临时文件 作为内存临时表的溢出机制受以下规则的约束:

  • 临时文件在 tmpdir 变量 定义的目录中创建

  • 临时文件在创建和打开后立即删除,因此不会在 tmpdir 目录中 保持可见 临时文件占用的空间由临时文件打开时由操作系统保存。 TempTable 存储引擎 关闭临时文件 mysqld 关闭进程 时,将 回收该空间

  • 数据永远不会在RAM和临时文件之间,RAM内或临时文件之间移动。

  • 如果空间在定义的限制范围内可用,则新数据将存储在RAM中 temptable_max_ram 否则,新数据将存储在临时文件中。

  • 如果在将表的某些数据写入临时文件后RAM中的空间可用,则可以将剩余的表数据存储在RAM中。

如果 TempTable 存储引擎配置为使用 InnoDB 磁盘内部临时表作为溢出机制( temptable_use_mmap=OFF ),则超出 temptable_max_ram 限制 的内存表将 转换为 InnoDB 磁盘内部临时表,并且属于该表的所有行都将被移动从内存到 InnoDB 磁盘内部临时表。 internal_tmp_disk_storage_engine (在MySQL 8.0.16移除)变量设置已经在没有影响 TempTable 存储引擎溢出机制。

如果TempTable存储引擎经常超出 变量 定义的内存限制 并在临时目录中为内存映射文件使用过多空间, 请考虑使用 InnoDB 磁盘内部临时表作为 TempTable 溢出机制 temptable_max_ram 这可能是由于使用大型内部临时表或大量使用内部临时表而发生的。 InnoDB 磁盘内部临时表在会话临时表空间中创建,默认情况下驻留在数据目录中。 有关更多信息,请参见 第15.6.3.5节“临时表空间”

memory/temptable/physical_ram memory/temptable/physical_disk 性能架构器械可以用于监测 TempTable 从内存和磁盘空间分配。 memory/temptable/physical_ram 报告分配的RAM量。 memory/temptable/physical_disk 报告当内存映射文件用作TempTable溢出机制( temptable_use_mmap=ON 时从磁盘分配的空间量 如果 physical_disk 仪器报告的值不是0且内存映射文件用作TempTable溢出机制,则 temptable_max_ram 在某个时刻达到阈值。 可以在性能模式内存汇总表中查询数据,例如 memory_summary_global_by_event_name 看到 第26.12.16.10节“内存汇总表”

MEMORY 存储引擎用于内存临时表时,如果内存临时表变得太大,它会自动将内存临时表转换为磁盘表。 内存临时表的最大大小由 tmp_table_size max_heap_table_size 定义 ,以较小者为准。 这与使用 MEMORY 显式创建的表 不同 CREATE TABLE 对于此类表,只有 max_heap_table_size 变量决定了表的增长程度,并且没有转换为磁盘格式。

用于磁盘内部临时表的存储引擎

从MySQL 8.0.16开始,服务器始终使用 InnoDB 存储引擎来管理磁盘上的内部临时表。

在MySQL 8.0.15及更早版本中,该 internal_tmp_disk_storage_engine 变量用于定义用于磁盘内部临时表的存储引擎。 在MySQL 8.0.16中删除了此变量,用于此目的的存储引擎不再是用户可配置的。

在MySQL 8.0.15及更早版本中:对于公用表表达式(CTE),用于磁盘内部临时表的存储引擎不能 MyISAM 如果 internal_tmp_disk_storage_engine=MYISAM ,使用磁盘临时表实现CTE的任何尝试都会发生错误。

在MySQL 8.0.15及更早版本中:使用时 internal_tmp_disk_storage_engine=INNODB ,生成超出 InnoDB 行或列限制 的磁盘内部临时表的查询会 返回 行大小太大 错误 太多 解决方法是设置 internal_tmp_disk_storage_engine MYISAM

内部临时表存储格式

当内存内部临时表由 TempTable 存储引擎 管理时 ,包含 VARCHAR 列, VARBINARY 列或其他二进制大对象类型列的行(从MySQL 8.0.13开始支持)在内存中由一组单元格表示,每个单元格包含NULL标志,数据长度和数据指针。 列值在数组之后以连续顺序放置在单个内存区域中,没有填充。 阵列中的每个单元使用16个字节的存储空间。 TempTable 存储引擎超出 temptable_max_ram 限制并开始从磁盘分配空间作为内存映射文件 时,应用相同的存储格式 InnoDB 磁盘内部临时表。

当存储器内部临时表由 MEMORY 存储引擎 管理时 ,使用固定长度的行格式。 VARCHAR VARBINARY 列值填充到最大列长度,实际上将它们存储为 CHAR BINARY 列。

在MySQL 8.0.16之前,磁盘内部临时表由 InnoDB MyISAM 存储引擎 管理 (取决于 internal_tmp_disk_storage_engine 设置)。 两个引擎都使用动态宽度行格式存储内部临时表。 与使用固定长度行的磁盘表相比,列只占用所需的存储空间,从而减少了磁盘I / O,空间要求和处理时间。 从MySQL 8.0.16开始, internal_tmp_disk_storage_engine 不受支持,磁盘上的内部临时表总是由处理 InnoDB

使用 MEMORY 存储引擎时,语句最初可以创建内存中的内部临时表,然后在表变得太大时将其转换为磁盘上的表。 在这种情况下,可以通过跳过转换并在磁盘上创建内部临时表来实现更好的性能。 big_tables 变量可用于强制内部临时表的磁盘存储。

8.5优化InnoDB表

InnoDB 是MySQL客户通常在生产数据库中使用的存储引擎,其中可靠性和并发性很重要。 InnoDB 是MySQL中的默认存储引擎。 本节介绍如何优化 InnoDB 表的 数据库操作

8.5.1优化InnoDB表的存储布局

  • 一旦您的数据达到稳定的大小,或者增长的表增加了数十或几百兆字节,请考虑使用该 OPTIMIZE TABLE 语句重新组织表并压缩任何浪费的空间。 重组的表需要较少的磁盘I / O来执行全表扫描。 这是一种简单的技术,可以在其他技术(如改进索引使用或调整应用程序代码)不切实际时提高性能。

    OPTIMIZE TABLE 复制表的数据部分并重建索引。 其好处来自于改进了索引中数据的打包,减少了表空间和磁盘上的碎片。 好处取决于每个表中的数据。 您可能会发现某些人而不是其他人获得了显着的收益,或者在您下次优化表之前,收益会随着时间的推移而降低。 如果表很大或者正在重建的索引不适合缓冲池,则此操作可能很慢。 向表中添加大量数据后的第一次运行通常比以后的运行慢得多。

  • InnoDB ,长 PRIMARY KEY (具有冗长值的单个列,或形成长复合值的多个列)浪费了大量磁盘空间。 在指向同一行的所有辅助索引记录中,行的主键值重复。 (请参见 第15.6.2.1节“聚簇和辅助索引” 。) AUTO_INCREMENT 如果主键很长,则 创建一 列作为主键,或者索引长 VARCHAR 的前缀 而不是整列。

  • 使用 VARCHAR 数据类型而不是 CHAR 存储可变长度字符串或具有多个 NULL 值的 列总是占据 字符来存储数据,即使该字符串是较短,或者其值 较小的表更适合缓冲池并减少磁盘I / O. CHAR(N) N NULL

    当使用 COMPACT 行格式(默认 InnoDB 格式)和可变长度字符集(如 utf8 或)时 sjis 列占用可变的空间量,但仍至少为 字节。 CHAR(N) N

  • 对于较大的表或包含大量重复文本或数字数据的表,请考虑使用 COMPRESSED 行格式。 将数据放入缓冲池或执行全表扫描需要较少的磁盘I / O. 在做出永久性决策之前,请使用 COMPRESSED COMPACT 行格式 相比较 来衡量可以实现的压缩量

8.5.2优化InnoDB事务管理

要优化 InnoDB 事务处理,请在事务功能的性能开销和服务器的工作负载之间找到理想的平衡点。 例如,如果应用程序每秒提交数千次,则可能会遇到性能问题,如果仅每2-3小时提交一次,则会遇到性能问题。

  • 默认的MySQL设置 AUTOCOMMIT=1 可能会对繁忙的数据库服务器施加性能限制。 在可行的情况下,通过发出 SET AUTOCOMMIT=0 START TRANSACTION 声明 将几个相关的数据更改操作包装到单个事务中 ,然后 COMMIT 在进行所有更改之后进行声明。

    InnoDB 如果该事务对数据库进行了修改,则必须在每次事务提交时将日志刷新到磁盘。 当每次更改后都提交时(与默认的自动提交设置一样),存储设备的I / O吞吐量会限制每秒潜在操作的数量。

  • 或者,对于仅包含单个 SELECT 语句的 事务, 启用此选项 AUTOCOMMIT 有助于 InnoDB 识别只读事务并对其进行优化。 有关要求 请参见 第8.5.3节“优化InnoDB只读事务”

  • 插入,更新或删除大量行后,避免执行回滚。 如果大型事务正在降低服务器性能,则将其回滚可能会使问题变得更糟,可能需要花费几倍的时间来执行原始数据更改操作。 杀死数据库进程没有帮助,因为在服务器启动时会再次启动回滚。

    为了尽量减少此问题发生的可能性:

    • 增加 缓冲池 的大小, 以便可以缓存所有数据更改更改,而不是立即写入磁盘。

    • 进行设置, innodb_change_buffering=all 以便除插入外还缓冲更新和删除操作。

    • 考虑 COMMIT 在大数据更改操作期间定期 发出 语句,可能会破坏单个删除或更新为对较少行进行操作的多个语句。

    要在发生失控回滚后摆脱它,请增加缓冲池,使回滚变为CPU限制并快速运行,或者 终止 服务器并重新启动 innodb_force_recovery=3 ,如 第15.17.2节“InnoDB恢复”中所述

    预计此问题很少会出现在默认设置中 innodb_change_buffering=all ,该 设置 允许将更新和删除操作缓存在内存中,从而使它们在第一时间更快地执行,并且如果需要还可以更快地回滚。 确保在处理具有许多插入,更新或删除的长时间运行事务的服务器上使用此参数设置。

  • 如果发生崩溃,您可以承受丢失一些最新提交的事务,则可以将 innodb_flush_log_at_trx_commit 参数 设置 为0. InnoDB 尝试每秒刷新一次日志,尽管无法保证刷新。

  • 修改或删除行时, 不会立即或甚至在事务提交后立即 删除行和关联的 撤消日志 旧数据将保留,直到先前或同时启动的事务完成,以便这些事务可以访问已修改或已删除行的先前状态。 因此,长时间运行的事务可以防止 InnoDB 清除由不同事务更改的数据。

  • 在长时间运行的事务中修改或删除行时, 如果读取相同的行,则 使用 READ COMMITTED REPEATABLE READ 隔离级别的 其他事务 必须执行更多工作来重建旧数据。

  • 当长时间运行的事务修改表时,从其他事务对该表的查询不会使用 覆盖索引 技术。 通常可以从辅助索引检索所有结果列的查询,而不是从表数据中查找适当的值。

    如果发现二级索引页面 PAGE_MAX_TRX_ID 太新,或者二级索引中的记录被删除标记,则 InnoDB 可能需要使用聚簇索引查找记录。

8.5.3优化InnoDB只读事务

InnoDB 可以避免与 已知为只读 事务 设置 事务ID TRX_ID 字段) 相关的开销 仅需要一个事务ID 的事务 ,可能执行写操作或 锁定读取 诸如 SELECT ... FOR UPDATE 消除不必要的事务ID会减少每次查询或数据更改语句构造 读取视图 时所咨询的内部数据结构的大小

InnoDB 在以下情况下检测只读事务:

  • 该事务以该 START TRANSACTION READ ONLY 语句 启动 在这种情况下,尝试更改数据库(for InnoDB MyISAM 或其他类型的表)会导致错误,并且事务将继续处于只读状态:

    ERROR 1792(25006):无法在READ ONLY事务中执行语句。
    

    您仍然可以在只读事务中更改特定于会话的临时表,或者为它们发出锁定查询,因为这些更改和锁对任何其他事务都不可见。

  • autocommit 设置后,使交易保证是一条语句,而单个语句组成的事务是 非锁定 SELECT 的语句。 也就是说, SELECT 不使用 FOR UPDATE LOCK IN SHARED MODE 子句。

  • 该事务在没有该 READ ONLY 选项的 情况下启动 ,但是尚未执行明确锁定行的更新或语句。 在需要更新或显式锁定之前,事务将保持只读模式。

因此,对于读取密集型应用,如报表生成器,您可以调整的序列, InnoDB 由内而外将它们分组查询 START TRANSACTION READ ONLY COMMIT ,或通过打开 autocommit 运行之前设置 SELECT 语句,或简单地避免与查询穿插任何数据更改语句。

有关信息 START TRANSACTION ,并 autocommit 请参见 13.3.1节,“START TRANSACTION,COMMIT和ROLLBACK语法”

注意

符合自动提交,非锁定和只读(AC-NL-RO)的事务不在某些内部 InnoDB 数据结构之外,因此不在 SHOW ENGINE INNODB STATUS 输出中 列出

8.5.4优化InnoDB重做日志

请考虑以下有关优化重做日志记录的准则:

  • 使重做日志文件变大,甚至与 缓冲池 一样大 何时 InnoDB 将重做日志文件写满,它必须将修改后的缓冲池内容写入 检查点的 磁盘 小的重做日志文件会导致许多不必要的磁盘写入。 虽然历史上大的重做日志文件导致冗长的恢复时间,但现在恢复速度更快,您可以放心地使用大型重做日志文件。

    使用 innodb_log_file_size innodb_log_files_in_group 配置选项 配置重做日志文件的大小和数量 有关修改现有重做日志文件配置的信息,请参阅 更改 重做日志文件 的数量或大小

  • 考虑增加 日志缓冲区 的大小 大型日志缓冲区使大型 事务 能够运行,而无需在事务 提交 之前将日志写入磁盘 因此,如果您有更新,插入或删除许多行的事务,则使日志缓冲区更大可以节省磁盘I / O. 使用 innodb_log_buffer_size 配置选项 配置日志缓冲区大小 ,可以在MySQL 8.0中动态配置。

  • 配置 innodb_log_write_ahead_size 配置选项以避免 read-on-write 此选项定义重做日志的预写块大小。 设置 innodb_log_write_ahead_size 为匹配操作系统或文件系统缓存块大小。 由于重做日志的预写块大小与操作系统或文件系统高速缓存块大小不匹配,重做日志块未完全高速缓存到操作系统或文件系统时会发生写入时写入。

    有效值 innodb_log_write_ahead_size InnoDB 日志文件块大小的 倍数 (2 n )。 最小值是 InnoDB 日志文件块大小(512)。 指定最小值时不会发生预写。 最大值等于该 innodb_page_size 值。 如果为该值指定的值 innodb_log_write_ahead_size 大于该 innodb_page_size 值,则该 innodb_log_write_ahead_size 设置将截断为该 innodb_page_size 值。

    innodb_log_write_ahead_size 相对于操作系统或文件系统高速缓存块大小 设置该 值太低会导致写入时读取。 fsync 由于一次写入多个块,因此 将值设置得过高可能会对 日志文件写入的性能 产生轻微影响

  • 通过等待刷新重做的用户线程优化旋转延迟的使用。 旋转延迟有助于减少延迟。 在低并发期间,减少延迟可能不是优先级,并且避免在这些时段期间使用自旋延迟可以减少能量消耗。 在高并发期间,您可能希望避免在旋转延迟上消耗处理能力,以便可以将其用于其他工作。 以下系统变量允许设置高水位值和低水位值,这些值定义了使用自旋延迟的边界。

    • innodb_log_wait_for_flush_spin_hwm :定义最大平均日志刷新时间,超过该时间,用户线程在等待刷新的重做时不再旋转。 默认值为400微秒。

    • innodb_log_spin_cpu_abs_lwm :定义在等待刷新重做时用户线程不再旋转的最小CPU使用量。 该值表示为CPU核心使用量的总和。 例如,默认值80是单个CPU核心的80%。 在具有多核处理器的系统上,值150表示100%使用一个CPU核心加50%使用第二个CPU核心。

    • innodb_log_spin_cpu_pct_hwm :定义在等待刷新的重做时用户线程不再旋转的最大CPU使用量。 该值表示为所有CPU核心的总处理能力的百分比。 默认值为50%。 例如,两个CPU核心的100%使用率是具有四个CPU核心的服务器上组合CPU处理能力的50%。

      innodb_log_spin_cpu_pct_hwm 配置选项方面处理器的亲和性。 例如,如果服务器有48个内核但 mysqld 进程仅固定为4个CPU内核,则忽略其他44个CPU内核。

8.5.5 InnoDB表的批量数据加载

这些性能提示补充了 第8.2.5.1节“优化INSERT语句”中 快速插入的一般准则

  • 导入数据时 InnoDB ,请关闭自动提交模式,因为它会为每个插入执行磁盘日志刷新。 要在导入操作期间禁用自动提交,请使用 SET autocommit COMMIT 语句将 括起来:

    SET autocommit = 0;
    ... SQL import statements ...
    承诺;
    

    mysqldump的 选项 --opt 创建这样的快速导入到转储文件 InnoDB 表,即使没有与他们包装 SET autocommit COMMIT 报表。

  • 如果您 UNIQUE 对辅助密钥 约束,则可以通过在导入会话期间临时关闭唯一性检查来加速表导入:

    SET unique_checks = 0;
    ... SQL import statements ...
    SET unique_checks = 1;
    

    对于大表,这会节省大量磁盘I / O,因为 InnoDB 可以使用其更改缓冲区批量写入二级索引记录。 确保数据不包含重复键。

  • 如果 FOREIGN KEY 表中 约束,则可以通过在导入会话期间关闭外键检查来加速表导入:

    SET foreign_key_checks = 0;
    ... SQL import statements ...
    SET foreign_key_checks = 1;
    

    对于大表,这可以节省大量磁盘I / O.

  • INSERT 如果需要插入多行, 请使用多行 语法来减少客户端和服务器之间的通信开销:

    插入可用的值(1,2),(5,5),......;
    

    此提示适用于插入任何表,而不仅仅是 InnoDB 表。

  • 在使用自动增量列进行批量插入时,设置 innodb_autoinc_lock_mode 为2(交错)而不是1(连续)。 有关 详细信息 请参见 第15.6.1.4节“InnoDB中的AUTO_INCREMENT处理”

  • 执行批量插入时,按 PRIMARY KEY 顺序 插入行会更快 InnoDB 表使用 聚簇索引 ,这使得按顺序使用数据相对较快 PRIMARY KEY PRIMARY KEY 顺序 执行批量插入 对于不完全适合缓冲池的表尤为重要。

  • 要在将数据加载到 InnoDB FULLTEXT 索引 时获得最佳性能 ,请执行以下步骤:

    1. FTS_DOC_ID 在表创建时 定义一个 BIGINT UNSIGNED NOT NULL ,其中包含一个名为的唯一索引 FTS_DOC_ID_INDEX 例如:

      CREATE TABLE t1(
      FTS_DOC_ID BIGINT无符号NOT NULL AUTO_INCREMENT,
      title varchar(255)NOT NULL DEFAULT'',
      text mediumtext NOT NULL,
      PRIMARY KEY(`FTS_DOC_ID`)
      )ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
      在t1上创建UNIQUE INDEX FTS_DOC_ID_INDEX(FTS_DOC_ID);
      
    2. 将数据加载到表中。

    3. FULLTEXT 加载数据后 创建 索引。

    注意

    FTS_DOC_ID 在创建表 时添加 列时,请确保在 FTS_DOC_ID 更新 FULLTEXT 索引列时更新列,因为 FTS_DOC_ID 必须随每个 INSERT 每个单调增加 UPDATE 如果您选择不添加 FTS_DOC_ID at table创建时间并 InnoDB 为您管理DOC ID, InnoDB 则会 FTS_DOC_ID 在下一次 CREATE FULLTEXT INDEX 调用时 将其添加 为隐藏列 但是,这种方法需要进行表重建,这会影响性能。

8.5.6优化InnoDB查询

要调整 InnoDB 表的 查询,请 在每个表上创建一组适当的索引。 有关 详细信息 请参见 第8.3.1节“MySQL如何使用索引” 请遵循以下 InnoDB 索引 指南

  • 因为每个 InnoDB 表都有一个 主键 (无论您是否请求一个 主键 ),请为每个表指定一组主键列,这些列用于最重要和时间关键的查询。

  • 不要在主键中指定太多或太长的列,因为这些列值在每个辅助索引中都是重复的。 当索引包含不必要的数据时,读取此数据的I / O和用于缓存它的内存会降低服务器的性能和可伸缩性。

  • 不要 为每列 创建单独的 二级索引 ,因为每个查询只能使用一个索引。 很少测试的列或具有少量不同值的列的索引可能对任何查询都没有帮助。 如果对同一个表有很多查询,测试不同的列组合,请尝试创建少量 连接索引 而不是大量单列索引。 如果索引包含结果集所需的所有列(称为 覆盖索引 ),则查询可能会完全避免读取表数据。

  • 如果索引列不能包含任何 NULL 值,请 NOT NULL 在创建表时将 其声明为 当知道每个列是否包含 NULL 时,优化器可以更好地确定哪个索引最有效地用于查询

  • 您可以 InnoDB 使用 第8.5.3节“优化InnoDB只读事务”中 的技术 优化 表的 单查询事务

8.5.7优化InnoDB DDL操作

  • 对表和索引(很多DDL操作 CREATE ALTER DROP 语句)可以在网上进行。 有关 详细信息 请参见 第15.12节“InnoDB和在线DDL”

  • 在线DDL支持添加二级索引意味着您通常可以通过创建没有二级索引的表来加快创建和加载表及相关索引的过程,然后在加载数据后添加二级索引。

  • 使用 TRUNCATE TABLE 空表,不 外键约束,可以使一个 语句的工作像一个普通的 声明,在这种情况下,命令序列喜欢 可能是最快的。 DELETE FROM tbl_name TRUNCATE DELETE DROP TABLE CREATE TABLE

  • 因为主键是每个 InnoDB 的存储布局的组成部分 ,并且更改主键的定义涉及重新组织整个表,所以始终将主键设置为 CREATE TABLE 语句的 一部分 ,并提前计划以便您不需要 ALTER DROP 之后的主键。

8.5.8优化InnoDB磁盘I / O.

如果您遵循SQL操作的数据库设计和调优技术的最佳实践,但由于磁盘I / O活动繁重,您的数据库仍然很慢,请考虑这些磁盘I / O优化。 如果Unix top 工具或Windows任务管理器显示您的工作负载的CPU使用百分比低于70%,则您的工作负载可能是磁盘绑定的。

  • 增加缓冲池大小

    当表数据缓存在 InnoDB 缓冲池中时,可以通过查询重复访问它,而无需任何磁盘I / O. 使用 innodb_buffer_pool_size 选项 指定缓冲池的大小 此内存区域非常重要,通常建议将 innodb_buffer_pool_size 其配置为系统内存的50%到75%。 有关更多信息,请参见 第8.12.3.1节“MySQL如何使用内存”

  • 调整冲洗方法

    在某些版本的GNU / Linux和Unix中,使用Unix fsync() 调用( InnoDB 默认情况下使用)和类似方法将 文件刷新到磁盘的 速度非常慢。 如果数据库写入性能存在问题,请使用 innodb_flush_method 参数设置为 on进行基准测试 O_DSYNC

  • 配置写缓冲区的阈值大小

    默认情况下,在 InnoDB 创建新数据文件(例如新日志文件或表空间文件)时,只有在完全写入文件后才会将写入缓冲区的内容刷新到磁盘,这可能导致发生大量磁盘写入活动立刻。 要强制进行较小的定期刷新,请使用 innodb_fsync_threshold (在MySQL 8.0.13中引入)为写缓冲区定义阈值大小(以字节为单位)。 达到阈值大小时,写缓冲区的内容将刷新到磁盘。 默认值0强制使用默认行为。

    在多个MySQL实例使用相同的存储设备的情况下,指定写入缓冲区阈值大小以强制较小的定期刷新可能是有益的。 例如,创建新的MySQL实例及其关联的数据文件可能会导致磁盘写入活动大量涌现,从而影响使用相同存储设备的其他MySQL实例的性能。 配置写入缓冲区阈值大小有助于避免磁盘写入活动中出现此类浪涌。

  • 在Linux上使用带有本机AIO的noop或截止时间I / O调度程序

    InnoDB 使用Linux上的异步I / O子系统(本机AIO)来执行数据文件页面的预读和写入请求。 此行为由 innodb_use_native_aio 配置选项 控制,该 选项默认启用。 对于本机AIO,I / O调度程序的类型对I / O性能的影响更大。 通常,建议使用noop和deadline I / O调度程序。 执行基准测试以确定哪个I / O调度程序为您的工作负载和环境提供最佳结果。 有关更多信息,请参见 第15.8.6节“在Linux上使用异步I / O”

  • 在Solaris 10 for x86_64体系结构上使用直接I / O.

    InnoDB 在Solaris 10 for x86_64体系结构(AMD Opteron)上 使用 存储引擎时,请使用直接I / O InnoDB 相关文件以避免性能下降 InnoDB 要将直接I / O用于存储 InnoDB 相关文件 的整个UFS文件系统,请使用 forcedirectio 选项 安装它 ; mount_ufs(1M) (Solaris 10 / x86_64上的缺省值 使用此选项。)要仅将直接I / O应用于 InnoDB 文件操作而不是整个文件系统,请进行设置 innodb_flush_method = O_DIRECT 使用此设置, InnoDB 调用 directio() 而不是 fcntl() 用于数据文件的I / O(不用于日志文件的I / O)。

  • 对Solaris 2.6或更高版本的数据和日志文件使用原始存储

    在任何版本的Solaris 2.6及更高版本和任何平台(sparc / x86 / x64 / amd64)上 InnoDB 使用具有较大 innodb_buffer_pool_size 存储引擎时,请 InnoDB 在原始设备或单独的直接I / O UFS上 使用 数据文件和日志文件 进行基准测试 文件系统,使用 forcedirectio 如前所述 mount选项。 innodb_flush_method 如果希望对日志文件执行直接I / O ,则必须使用mount选项而不是设置 。)Veritas文件系统VxFS的用户应使用 convosync=direct mount选项。

    不要 MyISAM 在直接I / O文件系统上 放置其他MySQL数据文件,例如 表格。 不得 可执行文件或库 放在直接I / O文件系统上。

  • 使用其他存储设备

    可以使用其他存储设备来设置RAID配置。 有关相关信息,请参见 第8.12.1节“优化磁盘I / O”

    或者, InnoDB 表空间数据文件和日志文件可以放在不同的物理磁盘上。 有关更多信息,请参阅以下部分:

  • 考虑非旋转存储

    非旋转存储通常为随机I / O操作提供更好的性能; 和用于顺序I / O操作的旋转存储。 在旋转和非旋转存储设备之间分发数据和日志文件时,请考虑主要在每个文件上执行的I / O操作的类型。

    面向随机I / O的文件通常包括 每表文件 一般表空间 数据文件, 撤消表空间 文件和 临时表空间 文件。 顺序面向I / O的文件包括 InnoDB 系统表空间 文件(由于 doublewrite缓冲 更改缓冲 )和日志文件,如 二进制日志 文件和 重做日志 文件。

    使用非旋转存储时,请查看以下配置选项的设置:

    • innodb_checksum_algorithm

      crc32 选项使用更快的校验和算法,建议用于快速存储系统。

    • innodb_flush_neighbors

      此选项可优化旋转存储设备的I / O. 禁用它用于非旋转存储或混合旋转和非旋转存储。 默认情况下禁用它。

    • innodb_io_capacity

      默认设置200通常足以用于低端非旋转存储设备。 对于更高端的总线连接设备,请考虑更高的设置,例如1000。

    • innodb_io_capacity_max

      默认值2000适用于使用非旋转存储的工作负载。 对于高端,总线连接的非旋转存储设备,请考虑更高的设置,例如2500。

    • innodb_log_compressed_pages

      如果重做日志位于非旋转存储上,请考虑禁用此选项以减少日志记录。 请参阅 禁用压缩页面的记录

    • innodb_log_file_size

      如果重做日志位于非旋转存储上,请配置此选项以最大化缓存和写入组合。

    • innodb_page_size

      请考虑使用与磁盘内部扇区大小匹配的页面大小。 早期的SSD设备通常具有4KB的扇区大小。 一些较新的设备具有16KB的扇区大小。 默认 InnoDB 页面大小为16KB。 保持页面大小接近存储设备块大小可以最大限度地减少重写到磁盘的未更改数据量。

    • binlog_row_image

      如果二进制日志位于非旋转存储上且所有表都具有主键,请考虑将此选项设置 minimal 为减少日志记录。

    确保为您的操作系统启用了TRIM支持。 它通常默认启用。

  • 增加I / O容量以避免积压

    如果由于 InnoDB 检查点 操作 导致吞吐量周期性下降 ,请考虑增加 innodb_io_capacity 配置选项 的值 较高的值会导致更频繁的 刷新 ,从而避免可能导致吞吐量下降的积压工作。

  • 如果冲洗不落后,则I / O容量会降低

    如果系统没有落后于 InnoDB 刷新 操作,请考虑降低 innodb_io_capacity 配置选项 的值 通常,您将此选项值保持尽可能低,但不要低到导致吞吐量周期性下降,如前面的项目中所述。 在您可以降低选项值的典型情况下,您可能会在以下输出中看到如下组合 SHOW ENGINE INNODB STATUS

    • 历史清单长度低,低于几千。

    • 插入缓冲区合并靠近插入的行。

    • 缓冲池中的已修改页面始终远低于 innodb_max_dirty_pages_pct 缓冲池。 (在服务器未进行批量插入时进行测量;在批量插入期间,修改后的页面百分比显着上升是正常的。)

    • Log sequence number - Last checkpoint 小于7/8或理想情况下小于 InnoDB 日志文件 总大小的6/8

  • 在Fusion-io设备上存储系统表空间文件

    您可以通过 在支持原子写入的Fusion-io设备上 存储系统表空间文件( ibdata files 来利用与双写缓冲区相关的I / O优化 在这种情况下,doublewrite buffering( innodb_doublewrite )会自动禁用,Fusion-io原子写入将用于所有数据文件。 此功能仅在Fusion-io硬件上受支持,仅适用于Linux上的Fusion-io NVMFS。 要充分利用此功能, 建议 进行 innodb_flush_method 设置 O_DIRECT

    注意

    由于双写缓冲区设置是全局的,因此对于驻留在非Fusion-io硬件上的数据文件也禁用双写缓冲。

  • 禁用压缩页面的记录

    使用 InnoDB 压缩 功能时, 在对压缩数据进行更改时,会将 重新压缩 页面的 图像 写入 重做日志 此行为由 innodb_log_compressed_pages ,默认情况下启用,以防止 zlib 在恢复期间使用 不同版本的 压缩算法时 可能发生的损坏 如果您确定 zlib 版本不会更改,请禁用 innodb_log_compressed_pages 以减少修改压缩数据的工作负载的重做日志生成。

8.5.9优化InnoDB配置变量

对于具有轻量,可预测负载的服务器而言,不同的设置最适用于始终满负荷运行的服务器,或者遇到高活动高峰的服务器。

由于 InnoDB 存储引擎会自动执行许多优化,因此许多性能调整任务涉及监视以确保数据库运行良好,并在性能下降时更改配置选项。 有关详细 性能监视的 信息 请参见 第15.15节“InnoDB与MySQL性能模式的集成” InnoDB

您可以执行的主要配置步骤包括:

  • 控制 InnoDB 缓冲更改数据 的数据更改操作类型 ,以避免频繁的小磁盘写入。 请参阅 配置更改缓冲 因为默认是缓冲所有类型的数据更改操作,所以只有在需要减少缓冲量时才更改此设置。

  • 使用该 innodb_adaptive_hash_index 选项 打开和关闭自适应哈希索引功能 有关 更多信息 请参见 第15.5.3节“自适应哈希索引” 您可以在异常活动期间更改此设置,然后将其恢复为原始设置。

  • InnoDB 如果上下文切换是瓶颈,则 处理 的并发线程数设置限制 请参见 第15.8.4节“为InnoDB配置线程并发”

  • 控制预 InnoDB 读操作 所需的预取量 当系统具有未使用的I / O容量时,更多预读可以提高查询性能。 过多的预读会导致负载很重的系统性能周期性下降。 请参见 第15.8.3.4节“配置InnoDB缓冲池预取(预读)”

  • 如果您有一个未被默认值充分利用的高端I / O子系统,则增加读取或写入操作的后台线程数。 请参见 第15.8.5节“配置后台InnoDB I / O线程的数量”

  • 控制I / O InnoDB 在后台执行的程度。 请参见 第15.8.7节“配置InnoDB主线程I / O速率” 如果您观察到性能的周期性下降,则可以缩减此设置。

  • 控制确定何时 InnoDB 执行某些类型的后台写入 的算法 请参见 第15.8.3.5节“配置InnoDB缓冲池刷新” 该算法适用于某些类型的工作负载,但不适用于其他工作负载,因此如果您观察到性能周期性下降,可能会关闭此设置。

  • 利用多核处理器及其高速缓存配置,最大限度地减少上下文切换的延迟。 请参见 第15.8.8节“配置自旋锁轮询”

  • 防止诸如表扫描之类的一次性操作干扰存储在 InnoDB 缓冲区高速缓存中 的频繁访问的数据 请参见 第15.8.3.3节“使缓冲池抗扫描”

  • 将日志文件调整为对可靠性和崩溃恢复有意义的大小。 InnoDB 日志文件通常保持较小,以避免崩溃后的长启动时间。 MySQL 5.5中引入的优化加速了崩溃 恢复 过程的 某些步骤 特别是, 由于改进了内存管理算法 ,扫描 重做日志 和应用重做日志的速度更快。 如果您将日志文件人为地缩小以避免长启动时间,则现在可以考虑增加日志文件大小以减少由于重做日志记录的回收而发生的I / O.

  • 配置 InnoDB 缓冲池 的实例大小和数量, 对于具有多千兆字节缓冲池的系统尤其重要。 请参见 第15.8.3.2节“配置多个缓冲池实例”

  • 增加最大并发事务数,从而显着提高最繁忙数据库的可伸缩性。 请参见 第15.6.6节“撤消日志”

  • 将清除操作(一种垃圾收集)移动到后台线程中。 请参见 第15.8.9节“配置InnoDB清除调度” 要有效地测量此设置的结果,请首先调整其他与I / O相关的配置和与线程相关的配置设置。

  • 减少 InnoDB 并发线程之间 的切换量 ,以便繁忙服务器上的SQL操作不会排队并形成 交通堵塞 innodb_thread_concurrency 选项 设置一个值, 对于高功率的现代系统,最多约为32。 增加 innodb_concurrency_tickets 选项 的值 ,通常为5000左右。 这种选项组合设置了线程数量的上限 InnoDB 任何时候进程,并允许每个线程在被换出之前做大量的工作,这样等待线程的数量保持很低,操作可以在没有过多上下文切换的情况下完成。

8.5.10为具有多个表的系统优化InnoDB

  • 如果您已配置 的非持久性优化统计 (非默认配置), InnoDB 计算指标 基数 值表中的第一次表,启动后访问,而不是在表中存储这些值。 对于将数据分区为多个表的系统,此步骤可能会花费大量时间。 由于此开销仅适用于初始表打开操作,要 预热 表以供以后使用,请在启动后立即通过发出诸如的语句来访问它 SELECT 1 FROM tbl_name LIMIT 1

    默认情况下,优化程序统计信息将持久保存到磁盘,并由 innodb_stats_persistent 配置选项 启用 有关持久优化器统计信息的信息,请参见 第15.8.10.1节“配置持久优化器统计信息参数”

8.6优化MyISAM表

MyISAM 存储引擎与读数据或低并发操作的性能最佳,因为表锁限制进行同步更新的能力。 在MySQL中, InnoDB 是默认的存储引擎而不是 MyISAM

8.6.1优化MyISAM查询

加速查询 MyISAM 表的 一些常规技巧

  • 为了帮助MySQL更好地优化查询, 在加载数据后在表上 使用 ANALYZE TABLE 或运行 myisamchk --analyze 这会更新每个索引部分的值,以指示具有相同值的平均行数。 (对于唯一索引,这始终是1.)MySQL使用它来决定在基于非常量表达式连接两个表时选择哪个索引。 您可以通过使用 和检查 检查 表分析的结果 myisamchk --description --verbose 显示索引分发信息。 SHOW INDEX FROM tbl_name Cardinality

  • 要根据索引对索引和数据进行排序,请使用 myisamchk --sort-index --sort-records = 1 (假设您要对索引1进行排序)。 如果您有一个唯一索引,您希望根据索引按顺序读取所有行,这是一种提高查询速度的好方法。 第一次以这种方式对大表进行排序时,可能需要很长时间。

  • 尽量避免 SELECT MyISAM 经常更新的表 进行复杂 查询 ,以避免由于读者和编写者之间的争用而导致的表锁定问题。

  • MyISAM 支持并发插入:如果表在数据文件的中间没有空闲块,则可以 INSERT 在其他线程从表中读取的同时 新行放入其中。 如果能够执行此操作很重要,请考虑以避免删除行的方式使用该表。 另一种可能性是在 OPTIMIZE TABLE 从表中删除大量行后 运行 对表进行碎片整理。 通过设置 concurrent_insert 变量 可以更改此行为 您可以强制追加新行(因此允许并发插入),即使在已删除行的表中也是如此。 请参见 第8.11.3节“并发插入”

  • 对于 MyISAM 频繁更改的表,尽量避免所有变长列( VARCHAR BLOB ,和 TEXT )。 如果表包含单个可变长度列,则表使用动态行格式。 请参见 第16章, 备用存储引擎

  • 将表拆分成不同的表通常没有用,因为行变大。 在访问行时,最大的性能影响是找到行的第一个字节所需的磁盘搜索。 查找数据后,大多数现代磁盘可以足够快地读取整行,以满足大多数应用程序的需要。 拆分表的唯一情况是,如果它是 MyISAM 使用动态行格式 表,您可以更改为固定的行大小,或者如果您经常需要扫描表但不需要大多数列。 请参见 第16章, 备用存储引擎

  • 如果您通常按 顺序 检索行, 请使用 通过对表进行大量更改后使用此选项,您可以获得更高的性能。 ALTER TABLE ... ORDER BY expr1, expr2, ... expr1, expr2, ...

  • 如果您经常需要根据来自大量行的信息计算结果,例如计数,则最好引入新表并实时更新计数器。 以下表单的更新非常快:

    UPDATE tbl_nameSET count_col= count_col+1 WHERE key_col= constant;
    

    当您使用MySQL存储引擎时,这非常重要,例如 MyISAM 只有表级锁定(多个读取器与单个编写器)。 这也为大多数数据库系统提供了更好的性能,因为在这种情况下行锁定管理器做的事情较少。

  • OPTIMIZE TABLE 定期 使用 以避免使用动态格式 MyISAM 表进行 碎片 请参见 第16.2.3节“MyISAM表存储格式”

  • MyISAM 使用 DELAY_KEY_WRITE=1 table选项 声明 会使索引更新更快,因为在关闭表之前它们不会刷新到磁盘。 缺点是,如果在打开这样的表时某些东西会杀死服务器,则必须通过运行带有该 --myisam-recover-options 选项 的服务器 在重新启动服务器之前 运行 myisamchk 来确保表可以正常运行 (但是,即使在这种情况下,您也不应该因使用而丢失任何内容 DELAY_KEY_WRITE ,因为始终可以从数据行生成密钥信息。)

  • 字符串在 MyISAM 索引 中自动压缩前缀和结束空间 请参见 第13.1.15节“CREATE INDEX语法”

  • 您可以通过在应用程序中缓存查询或答案,然后一起执行许多插入或更新来提高性能。 在此操作期间锁定表可确保仅在所有更新后刷新索引缓存一次。

8.6.2 MyISAM表的批量数据加载

这些性能提示补充了 第8.2.5.1节“优化INSERT语句”中 快速插入的一般准则

  • 对于 MyISAM 表, SELECT 如果数据文件中间没有已删除的行,则 可以使用并发插入来在 语句运行 的同时添加行 请参见 第8.11.3节“并发插入”

  • 通过一些额外的工作, 当表具有许多索引时,可以使表 LOAD DATA 更快 运行 MyISAM 使用以下过程:

    1. 执行 FLUSH TABLES 语句或 mysqladmin flush-tables 命令。

    2. 使用 myisamchk --keys-used = 0 -rq /path/to/db/tbl_name 删除表的所有索引使用。

    3. 用数据将数据插入表中 LOAD DATA 这不会更新任何索引,因此非常快。

    4. 如果您打算将来只读取该表,请使用 myisampack 进行压缩。 请参见 第16.2.3.3节“压缩表特性”

    5. 使用 myisamchk -rq /path/to/db/tbl_name 重新创建索引 这会在将内容写入磁盘之前在内存中创建索引树,这比更新索引要快得多, LOAD DATA 因为它避免了大量的磁盘搜索。 生成的索引树也完美平衡。

    6. 执行 FLUSH TABLES 语句或 mysqladmin flush-tables 命令。

    LOAD DATA 如果 MyISAM 插入数据 表为空, 则自动执行上述优化 自动优化和显式使用过程之间的主要区别在于,您可以让 myisamchk 为索引创建分配更多的临时内存,而不是您希望服务器在执行 LOAD DATA 语句 时为索引重新创建分配的内存

    您还可以 MyISAM 使用以下语句而不是 myisamchk 禁用或启用 的非唯一索引 如果您使用这些语句,则可以跳过 FLUSH TABLES 操作:

    ALTER TABLE tbl_nameDISABLE KEYS;
    ALTER TABLE tbl_nameENABLE KEYS;
    
  • 要加快 INSERT 使用非事务表的多个语句执行的操作,请锁定表:

    LOCK TABLES a WRITE;
    插入值(1,23),(2,34),(4,33);
    插入价值观(8,26),(6,29);
    ...
    解锁表;
    

    这有利于提高性能,因为在 INSERT 完成 所有 语句 之后,索引缓冲区仅刷新到磁盘一次 通常,存在与 INSERT 语句 一样多的索引缓冲区刷新 如果可以使用单个行插入所有行,则不需要显式锁定语句 INSERT

    锁定还可以降低多连接测试的总时间,尽管单个连接的最长等待时间可能会因为等待锁定而上升。 假设五个客户端尝试同时执行插入,如下所示:

    • 连接1执行1000次插入

    • 连接2,3和4进行1次插入

    • 连接5执行1000次插入

    如果不使用锁定,则连接2,3和4在1和5之前完成。如果使用锁定,则连接2,3和4可能在1或5之前未完成,但总时间应为约40%快点。

    INSERT UPDATE DELETE 操作都非常快,MySQL,但是你可以通过添加周围的一切,做超过约五个连续的插入或更新锁获得更好的整体性能。 如果你做了很多连续的插入,你可以做一次, LOCK TABLES 然后 UNLOCK TABLES 偶尔(每1000行左右)允许其他线程访问表。 这仍然会带来不错的性能提升。

    INSERT LOAD DATA 即使使用刚刚概述的策略 ,加载数据的速度仍然要慢得多

  • 为了提高性能 MyISAM 表,对于 LOAD DATA INSERT ,通过增加放大键缓存 key_buffer_size 系统变量。 请参见 第5.1.1节“配置服务器”

8.6.3优化REPAIR TABLE语句

REPAIR TABLE 对于 MyISAM 表类似于使用 myisamchk 进行修复操作,并且一些相同的性能优化适用:

  • myisamchk 具有控制内存分配的变量。 您可以通过设置这些变量来提高性能,如 第4.6.4.6节“myisamchk内存使用”中所述

  • 因为 REPAIR TABLE ,同样的原则适用,但由于修复是由服务器完成的,因此您需要设置服务器系统变量而不是 myisamchk 变量。 此外,除了设置内存分配变量之外,增加 myisam_max_sort_file_size 系统变量还增加了修复将使用更快的filesort方法并避免密钥缓存方法更慢修复的可能性。 在检查以确保有足够的可用空间来容纳表文件的副本之后,将变量设置为系统的最大文件大小。 必须在包含原始表文件的文件系统中提供可用空间。

假设 使用以下选项完成 myisamchk 表修复操作以设置其内存分配变量:

--key_buffer_size = 128M --myisam_sort_buffer_size = 256M
--read_buffer_size = 64M --write_buffer_size = 64M

其中一些 myisamchk 变量对应于服务器系统变量:

myisamchk 变量 系统变量
key_buffer_size key_buffer_size
myisam_sort_buffer_size myisam_sort_buffer_size
read_buffer_size read_buffer_size
write_buffer_size 没有

每个服务器系统变量都可以在运行时设置,其中一些( myisam_sort_buffer_size read_buffer_size )除了全局值之外还有会话值。 设置会话值会限制更改对当前会话的影响,并且不会影响其他用户。 更改仅全局变量( key_buffer_size myisam_max_sort_file_size )也会影响其他用户。 因为 key_buffer_size ,您必须考虑缓冲区与这些用户共享。 例如,如果将 myisamchk key_buffer_size 变量 设置 为128MB,则可以设置相应的变量 key_buffer_size 系统变量大于该值(如果尚未设置更大),以允许其他会话中的活动使用密钥缓冲区。 但是,更改全局键缓冲区大小会使缓冲区无效,从而导致磁盘I / O增加并导致其他会话减速。 避免此问题的另一种方法是使用单独的密钥缓存,为其分配要修复的表中的索引,并在修复完成时取消分配。 请参见 第8.10.2.2节“多个密钥缓存”

根据前面的说明, REPAIR TABLE 可以按如下方式进行操作,以使用类似于 myisamchk 命令的 设置 这里分配了一个单独的128MB密钥缓冲区,假定文件系统允许文件大小至少为100GB。

SET SESSION myisam_sort_buffer_size = 256 * 1024 * 1024;
SET SESSION read_buffer_size = 64 * 1024 * 1024;
SET GLOBAL myisam_max_sort_file_size = 100 * 1024 * 1024 * 1024;
SET GLOBAL repair_cache.key_buffer_size = 128 * 1024 * 1024;
CACHE INDEX tbl_nameIN repair_cache;
负荷指数进入CACHE tbl_name;
修理表tbl_name;
SET GLOBAL repair_cache.key_buffer_size = 0;

如果您打算更改全局变量但希望仅在 REPAIR TABLE 操作 期间执行此 操作以最小程度地影响其他用户,请将其值保存在用户变量中,然后将其还原。 例如:

SET @old_myisam_sort_buffer_size = @@ GLOBAL.myisam_max_sort_file_size;
SET GLOBAL myisam_max_sort_file_size = 100 * 1024 * 1024 * 1024;
REPAIR TABLE tbl_name;
SET GLOBAL myisam_max_sort_file_size = @old_myisam_max_sort_file_size;

REPAIR TABLE 如果您希望默认情况下值有效,则可以在服务器启动时全局设置 影响的系统变量 例如,将这些行添加到服务器 my.cnf 文件中:

的[mysqld]
myisam_sort_buffer_size = 256M
的key_buffer_size = 1G
myisam_max_sort_file_size = 100G

这些设置不包括 read_buffer_size read_buffer_size 全局 设置 为较大的值会对所有会话执行此操作,并且可能会因为具有多个同时会话的服务器的过多内存分配而导致性能受损。

8.7优化MEMORY表

考虑将 MEMORY 表用于经常访问的非关键数据,并且是只读的或很少更新。 在实际工作负载下 针对等效 InnoDB MyISAM 表格 对应用程序进行基准测试 ,以确认任何其他性能值得丢失数据的风险,或者在应用程序启动时从基于磁盘的表复制数据的开销。

为了获得 MEMORY 表的 最佳性能 ,请检查针对每个表的查询类型,并指定要用于每个关联索引的类型,B树索引或哈希索引。 CREATE INDEX 声明中,使用 USING BTREE USING HASH 对于通过诸如 > 等运算符进行大于或小于比较的查询,B树索引很快 BETWEEN 散列索引仅对通过 = 运算符 查找单个值的查询 通过 运算符的一组受限 值的查询很快 IN 为什么 USING BTREE 通常是比默认值更好的选择 USING HASH ,请参见 第8.2.1.22节“避免全表扫描” 有关不同类型 MEMORY 索引的 实现细节 ,请参见 第8.3.9节“B树和散列索引的比较”

8.8了解查询执行计划

根据表,列,索引和 WHERE 子句中 的条件的详细信息 ,MySQL优化器会考虑许多技术来有效地执行SQ​​L查询中涉及的查找。 可以在不读取所有行的情况下执行对大表的查询; 可以在不比较每个行组合的情况下执行涉及多个表的连接。 优化程序选择执行最有效查询的操作集称为 查询执行计划 ,也称为 EXPLAIN 计划。 你的目标是认识到的方方面面 EXPLAIN 计划表明查询已经过优化,并且如果您看到一些低效的操作,请学习SQL语法和索引技术以改进计划。

8.8.1使用EXPLAIN优化查询

EXPLAIN 语句提供有关MySQL如何执行语句的信息:

借助于 EXPLAIN ,您可以看到应该向表添加索引的位置,以便通过使用索引查找行来更快地执行语句。 您还可以使用 EXPLAIN 检查优化程序是否以最佳顺序连接表。 要给优化器提供一个提示,以使用与 SELECT 语句 中命名表的顺序相对应的连接顺序 ,请使用 SELECT STRAIGHT_JOIN 而不是仅仅 开始语句 SELECT (请参见 第13.2.10节“SELECT语法” 。)但是, STRAIGHT_JOIN 可能会阻止使用索引,因为它会禁用半连接转换。 看到 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”

优化器跟踪有时可以提供与其相辅相成的信息 EXPLAIN 但是,优化程序跟踪格式和内容可能会在不同版本之间发生变化。 有关详细信息,请参阅 MySQL内部:跟踪优化程序

如果您在认为应该使用索引时遇到问题,请运行 ANALYZE TABLE 以更新表统计信息,例如密钥的基数,这可能会影响优化程序所做的选择。 请参见 第13.7.3.1节“ANALYZE TABLE语法”

注意

EXPLAIN 也可用于获取有关表中列的信息。 和的 同义词 有关更多信息,请参见 第13.8.1节“DESCRIBE语法” 第13.7.6.5节“显示列语法” EXPLAIN tbl_name DESCRIBE tbl_name SHOW COLUMNS FROM tbl_name

8.8.2 EXPLAIN输出格式

EXPLAIN 语句提供有关MySQL如何执行语句的信息。 EXPLAIN 作品有 SELECT DELETE INSERT REPLACE ,和 UPDATE 语句。

EXPLAIN 返回 SELECT 语句 中使用的每个表的一行信息 它按照MySQL在处理语句时读取它们的顺序列出输出中的表。 MySQL使用嵌套循环连接方法解析所有连接。 这意味着MySQL从第一个表中读取一行,然后在第二个表,第三个表中找到匹配的行,依此类推。 处理完所有表后,MySQL将通过表列表输出所选列和回溯,直到找到有更多匹配行的表。 从该表中读取下一行,并继续下一个表。

注意

MySQL Workbench具有Visual Explain功能,可以直观地显示 EXPLAIN 输出。 请参阅 教程:使用说明来提高查询性能

EXPLAIN输出列

本节介绍生成的输出列 EXPLAIN 后面的部分提供有关 type Extra 列的 其他信息

每个输出行 EXPLAIN 提供有关一个表的信息。 每行包含 表8.1“EXPLAIN输出列”中 汇总的值 ,并在表后面进行了更详细的描述。 列名显示在表的第一列中; 第二列提供了 FORMAT=JSON 使用 时输出中显示的等效属性名称

表8.1 EXPLAIN输出列

JSON名称 含义
id select_id SELECT 标识符
select_type 没有 SELECT 类型
table table_name 输出行的表
partitions partitions 匹配的分区
type access_type 连接类型
possible_keys possible_keys 可供选择的索引
key key 实际选择的指数
key_len key_length 所选键的长度
ref ref 列与索引进行比较
rows rows 估计要检查的行
filtered filtered 按表条件过滤的行的百分比
Extra 没有 附加信息

注意

JSON属性 NULL 未显示在JSON格式的 EXPLAIN 输出中。

  • id (JSON名: select_id

    SELECT 标识符。 这是 SELECT 查询中的 序号 NULL 如果行引用其他行的联合结果,则 该值可以是 在这种情况下,该 table 列显示一个值 ,表示该行引用 值为 的行 并集 <unionM,N> id M N

  • select_type (JSON名称:无)

    类型 SELECT ,可以是下表中显示的任何 类型 JSON格式化 EXPLAIN SELECT 类型 公开 为a的属性 query_block ,除非它是 SIMPLE PRIMARY JSON名称(如果适用)也显示在表中。

    select_type JSON名称 含义
    SIMPLE 没有 简单 SELECT (不使用 UNION 或子查询)
    PRIMARY 没有 SELECT
    UNION 没有 第二次或以后的 SELECT 陈述 UNION
    DEPENDENT UNION dependent true a中的第二个或更晚的 SELECT 语句 UNION ,取决于外部查询
    UNION RESULT union_result 的结果 UNION
    SUBQUERY 没有 首先 SELECT 是子查询
    DEPENDENT SUBQUERY dependent true 首先 SELECT 在子查询中,依赖于外部查询
    DERIVED 没有 派生表
    DEPENDENT DERIVED dependent true 派生表依赖于另一个表
    MATERIALIZED materialized_from_subquery 物化子查询
    UNCACHEABLE SUBQUERY cacheable false 无法缓存结果的子查询,必须为外部查询的每一行重新计算
    UNCACHEABLE UNION cacheable false UNION 属于不可缓存的子查询 的第二个或后一个选择 (请参阅参考资料 UNCACHEABLE SUBQUERY

    DEPENDENT 通常表示使用相关子查询。 请参见 第13.2.11.7节“相关子查询”

    DEPENDENT SUBQUERY 评估与评估不同 UNCACHEABLE SUBQUERY 因为 DEPENDENT SUBQUERY ,子查询仅针对来自其外部上下文的变量的每组不同值重新评估一次。 对于 UNCACHEABLE SUBQUERY ,子查询将针对外部上下文的每一行重新进行评估。

    当您指定 FORMAT=JSON EXPLAIN ,输出没有直接等效的单个属性 select_type ; query_block 属性对应于给定的 SELECT 可以使用与 SELECT 刚显示的 大多数 子查询类型 等效的属性 (示例 materialized_from_subquery MATERIALIZED ),并在适当时显示。 没有JSON等价物 SIMPLE PRIMARY

    select_type SELECT 语句 显示受影响表的语句类型。 例如, select_type DELETE 用于 DELETE 陈述。

  • table (JSON名: table_name

    输出行引用的表的名称。 这也可以是以下值之一:

    • <unionM,N> :行指的是 id 值为 M 的行 并集 N

    • <derivedN> :该行是指用于与该行的派生表结果 id 的值 N 例如,派生表可能来自 FROM 子句中 的子查询

    • <subqueryN> :行引用 id 值为 的行的具体化子查询的结果 N 请参见 第8.2.2.2节“使用实现优化子查询”

  • partitions (JSON名: partitions

    查询将匹配记录的分区。 该值适用 NULL 于非分区表。 请参见 第23.3.5节“获取有关分区的信息”

  • type (JSON名: access_type

    连接类型。 有关不同类型的说明,请参阅 EXPLAIN 连接类型

  • possible_keys (JSON名: possible_keys

    possible_keys 列指示MySQL可以从中选择查找此表中的行的索引。 请注意,此列完全独立于输出中显示的表的顺序 EXPLAIN 这意味着某些键 possible_keys 可能无法在生成中使用生成的表顺序。

    如果此列是 NULL (或在JSON格式的输出中未定义),则没有相关索引。 在这种情况下,您可以通过检查 WHERE 子句以检查它是否引用适合索引的某些列或列 来提高查询性能 如果是,请创建适当的索引并 EXPLAIN 再次 检查查询 请参见 第13.1.9节“ALTER TABLE语法”

    要查看表有哪些索引,请使用 SHOW INDEX FROM tbl_name

  • key (JSON名: key

    key 列指示MySQL实际决定使用的密钥(索引)。 如果MySQL决定使用其中一个 possible_keys 索引来查找行,那么该索引将被列为键值。

    可能 key 会命名值中不存在的索引 possible_keys 如果没有 possible_keys 索引适合查找行, 则会发生这种情况 ,但查询选择的所有列都是其他索引的列。 也就是说,命名索引覆盖了所选列,因此虽然它不用于确定要检索的行,但索引扫描比数据行扫描更有效。

    因为 InnoDB ,即使查询还选择主键,辅助索引也可能覆盖所选列,因为 InnoDB 主键值与每个辅助索引一起存储。 如果 key NULL ,MySQL没有找到用于更有效地执行查询的索引。

    要强制MySQL使用或忽略列出的索引 possible_keys 列,使用 FORCE INDEX USE INDEX IGNORE INDEX 在您的查询。 请参见 第8.9.4节“索引提示”

    对于 MyISAM 表,运行 ANALYZE TABLE 有助于优化器选择更好的索引。 对于 MyISAM 表格, myisamchk --analyze也是 如此。 请参见 第13.7.3.1节“ANALYZE TABLE语法” 第7.6节“MyISAM表维护和崩溃恢复”

  • key_len (JSON名: key_length

    key_len 列指示MySQL决定使用的密钥的长度。 该值 key_len 使您可以确定MySQL实际使用的多部分密钥的多少部分。 如果 key 列说 NULL ,该 len_len 列也说 NULL

    由于密钥存储格式,对于可能 NULL 列的列,密钥长度更大 NOT NULL

  • ref (JSON名: ref

    ref 列显示将哪些列或常量与列中指定的索引进行比较,以 key 从表中选择行。

    如果值为 func ,则使用的值是某个函数的结果。 要查看哪个函数,请使用 SHOW WARNINGS 以下内容 EXPLAIN 查看扩展 EXPLAIN 输出。 该函数实际上可能是算术运算符等运算符。

  • rows (JSON名: rows

    rows 列指示MySQL认为必须检查以执行查询的行数。

    对于 InnoDB 表格,此数字是估算值,可能并不总是准确的。

  • filtered (JSON名: filtered

    filtered 列指示将按表条件过滤的表行的估计百分比。 最大值为100,这意味着不会对行进行过滤。 值从100开始减少表示过滤量增加。 rows 显示检查的估计行数, rows × filtered 表示将与下表连接的行数。 例如,如果 rows 是1000并且 filtered 是50.00(50%),则使用下表连接的行数是1000×50%= 500。

  • Extra (JSON名称:无)

    此列包含有关MySQL如何解析查询的其他信息。 有关不同值的说明,请参阅 EXPLAIN 附加信息

    没有与该 Extra 对应的单个JSON属性 ; 但是,此列中可能出现的值将作为JSON属性公开,或作为属性的文本公开 message

EXPLAIN加入类型

type EXPLAIN 输出介绍如何联接表。 在JSON格式的输出中,这些是作为 access_type 属性的 值找到的 以下列表描述了从最佳类型到最差类型的连接类型:

  • system

    该表只有一行(=系统表)。 这是 const 连接类型 的特例

  • const

    该表最多只有一个匹配行,在查询开头读取。 因为只有一行,所以优化器的其余部分可以将此行中列的值视为常量。 const 表非常快,因为它们只读一次。

    const 将a PRIMARY KEY UNIQUE 索引的 所有部分 与常量值 进行比较时使用 在以下查询中, tbl_name 可以用作 const 表:

    SELECT * FROM tbl_nameWHERE primary_key= 1;
    
    SELECT * FROM tbl_name
      WHERE primary_key_part1= 1 AND primary_key_part2= 2;
    
  • eq_ref

    对于前面表格中的每个行组合,从该表中读取一行。 除了 system const 类型之外,这是最好的连接类型。 当连接使用索引的所有部分且索引是 索引 PRIMARY KEY UNIQUE NOT NULL 索引时使用它。

    eq_ref 可用于使用 = 运算符 进行比较的索引列 比较值可以是常量,也可以是使用在此表之前读取的表中的列的表达式。 在以下示例中,MySQL可以使用 eq_ref 联接来处理 ref_table

    SELECT * FROM ref_tableother_table
      WHERE ref_tablekey_column= other_tablecolumn;
    
    SELECT * FROM ref_tableother_table
      WHERE ref_tablekey_column_part1= other_tablecolumn
      AND ref_tablekey_column_part2= 1;
    
  • ref

    对于前面表中的每个行组合,将从此表中读取具有匹配索引值的所有行。 ref 如果连接仅使用键的最左前缀或者键不是a PRIMARY KEY UNIQUE 索引(换句话说,如果连接不能基于键值选择单行),则使用此方法。 如果使用的密钥只匹配几行,这是一个很好的连接类型。

    ref 可以用于使用 = or <=> 运算符 进行比较的索引列 在以下示例中,MySQL可以使用 ref 联接来处理 ref_table

    SELECT * FROM ref_tableWHERE key_column= expr;
    
    SELECT * FROM ref_tableother_table
      WHERE ref_tablekey_column= other_tablecolumn;
    
    SELECT * FROM ref_tableother_table
      WHERE ref_tablekey_column_part1= other_tablecolumn
      AND ref_tablekey_column_part2= 1;
    
  • fulltext

    使用 FULLTEXT 索引 执行连接

  • ref_or_null

    这种连接类型是这样的 ref ,但除此之外,MySQL还会对包含 NULL 值的 行进行额外搜索 此连接类型优化最常用于解析子查询。 在以下示例中,MySQL可以使用 ref_or_null 联接来处理 ref_table

    SELECT * FROM ref_table
      WHERE key_column= exprOR为key_columnNULL;
    

    请参见 第8.2.1.14节“IS NULL优化”

  • index_merge

    此连接类型表示使用了索引合并优化。 在这种情况下, key 输出行中的列包含使用的索引列表,并 key_len 包含所用 索引 的最长关键部分的列表。 有关更多信息,请参见 第8.2.1.3节“索引合并优化”

  • unique_subquery

    此类型替换 以下形式的 eq_ref 某些 IN 子查询:

    valueIN(primary_keysingle_table哪里选择some_expr

    unique_subquery 只是一个索引查找功能,它可以完全替换子查询以提高效率。

  • index_subquery

    此连接类型类似于 unique_subquery 它替换 IN 子查询,但它适用于以下形式的子查询中的非唯一索引:

    valueIN(key_columnsingle_table哪里选择some_expr
  • range

    仅检索给定范围内的行,使用索引选择行。 key 输出行中的列指示使用哪个索引。 key_len 包含已使用的时间最长的关键部分。 ref NULL 适用于此类型。

    range 当一个键柱使用任何的相比于恒定可使用 = <> > >= < <= IS NULL <=> BETWEEN LIKE ,或 IN() 运营商:

    SELECT * FROM tbl_name
      WHERE key_column= 10;
    
    SELECT * FROM tbl_name
      WHERE key_column10和20之间;
    
    SELECT * FROM tbl_name
      WHERE key_columnIN(10,20,30);
    
    SELECT * FROM tbl_name
      WHERE key_part1= 10 AND key_part2IN(10,20,30);
    
  • index

    index 联接类型是一样的 ALL ,只是索引树被扫描。 这有两种方式:

    • 如果索引是查询的覆盖索引,并且可用于满足表中所需的所有数据,则仅扫描索引树。 在这种情况下, Extra 专栏说 Using index 仅索引扫描通常比 ALL 索引的大小通常小于表数据 更快

    • 使用索引中的读取执行全表扫描,以按索引顺序查找数据行。 Uses index 没有出现在 Extra 列中。

    当查询仅使用属于单个索引的列时,MySQL可以使用此连接类型。

  • ALL

    对前面表格中的每个行组合进行全表扫描。 如果表是第一个未标记的表 const ,这通常 不好的 ,并且 在所有其他情况下 通常 非常 糟糕。 通常,您可以 ALL 通过添加索引 来避免 ,这些索引根据以前表中的常量值或列值从表中启用行检索。

解释额外信息

Extra EXPLAIN 输出包含MySQL解决查询的额外信息。 以下列表说明了此列中可能出现的值。 每个项目还指示JSON格式的输出哪个属性显示 Extra 值。 对于其中一些,有一个特定的属性。 其他显示为 message 属性 的文本

如果你想使你的查询尽可能快,看出来 Extra 的列值 Using filesort Using temporary ,或在JSON格式的 EXPLAIN 输出,用于 using_filesort using_temporary_table 等于性能 true

  • Child of 'table' pushed join@1 (JSON: message 文字)

    此表被引用为 table 可以下推到NDB内核的联接中 的子代 仅当启用了下推式联接时,才适用于NDB群集。 有关 ndb_join_pushdown 更多信息和示例, 请参阅 服务器系统变量的说明。

  • const row not found (JSON属性: const_row_not_found

    对于诸如此类的查询 ,该表为空。 SELECT ... FROM tbl_name

  • Deleting all rows (JSON属性: message

    因为 DELETE ,某些存储引擎(例如 MyISAM )支持一种处理程序方法,该方法以简单快捷的方式删除所有表行。 Extra 如果引擎使用此优化,则会显示 值。

  • Distinct (JSON属性: distinct

    MySQL正在寻找不同的值,因此它在找到第一个匹配行后停止为当前行组合搜索更多行。

  • FirstMatch(tbl_name) (JSON属性: first_match

    半连接FirstMatch连接快捷方式策略用于 tbl_name

  • Full scan on NULL key (JSON属性: message

    当优化程序无法使用索引查找访问方法时,子查询优化会作为回退策略发生这种情况。

  • Impossible HAVING (JSON属性: message

    HAVING 子句始终为false,不能选择任何行。

  • Impossible WHERE (JSON属性: message

    WHERE 子句始终为false,不能选择任何行。

  • Impossible WHERE noticed after reading const tables (JSON属性: message

    MySQL已经读取了所有 const (和 system )表,并注意到该 WHERE 子句始终为false。

  • LooseScan(m..n) (JSON属性: message

    使用半连接LooseScan策略。 m 并且 n 是关键部件号。

  • No matching min/max row (JSON属性: message

    没有行满足查询的条件,例如 SELECT MIN(...) FROM ... WHERE condition

  • no matching row in const table (JSON属性: message

    对于具有连接的查询,有一个空表或没有满足唯一索引条件的行的表。

  • No matching rows after partition pruning (JSON属性: message

    对于 DELETE UPDATE ,优化器在分区修剪后没有发现任何删除或更新的内容。 这是在意义上类似 Impossible WHERE SELECT 声明。

  • No tables used (JSON属性: message

    查询没有 FROM 子句,或者有 FROM DUAL 子句。

    对于 INSERT REPLACE 语句, EXPLAIN 在没有 SELECT 部分 时显示该值 例如,它似乎 EXPLAIN INSERT INTO t VALUES(10) 因为相当于 EXPLAIN INSERT INTO t SELECT 10 FROM DUAL

  • Not exists (JSON属性: message

    MySQL能够对 LEFT JOIN 查询 进行 优化,并且在找到与 LEFT JOIN 标准 匹配的行之后,不会检查此表中更多行以用于上一行组合 以下是可以通过以下方式优化的查询类型的示例:

    SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id
      WHERE22.id IS NULL;
    

    假设 t2.id 定义为 NOT NULL 在这种情况下,MySQL 使用的值 扫描 t1 并查找行 如果MySQL找到匹配的行 ,则它知道 永远不会 ,并且不会扫描 具有相同 的其余行 换句话说,对于每一行 ,MySQL只需要进行一次查找 ,而不管实际匹配的行数 t2 t1.id t2 t2.id NULL t2 id t1 t2 t2

  • Plan isn't ready yet (JSON属性:无)

    EXPLAIN FOR CONNECTION 当优化程序尚未完成为在命名连接中执行的语句创建执行计划时, 会出现此值 如果执行计划输出包含多行,则它们中的任何一个或全部都可以具有此 Extra 值,具体取决于优化程序在确定完整执行计划时的进度。

  • Range checked for each record (index map: N) (JSON属性: message

    MySQL发现没有好的索引可以使用,但发现在前面的表的列值已知之后可能会使用某些索引。 对于上表中的每个行组合,MySQL检查是否可以使用 range index_merge 访问方法来检索行。 这不是很快,但比执行没有索引的连接更快。 适用性标准如 第8.2.1.2节“范围优化” 第8.2.1.3节“索引合并优化”中所述 ,除了前面的表的所有列值都是已知的并被认为是常量。

    索引从1开始编号,顺序 SHOW INDEX 与表中 显示的顺序相同 索引映射值 N 是一个位掩码值,指示哪些索引是候选。 例如,值 0x19 (二进制11001)表示将考虑索引1,4和5。

  • Recursive (JSON属性: recursive

    这表明该行适用于 SELECT 递归公用表表达式 的递归 部分。 请参见 第13.2.13节“WITH语法(公用表表达式)”

  • Rematerialize (JSON属性: rematerialize

    Rematerialize (X,...) 显示在 EXPLAIN 行中 T ,其中 X 是任何横向派生表,当 T 读取 新行时触发重新实现 例如:

    选择
      ...
      T,
      LATERAL(derived table that refers to t)AS dt
    ...
    

    重新实现派生表的内容,以便每次 t 顶部查询处理 新行时使其更新

  • Scanned N databases (JSON属性: message

    这表示服务器在处理 INFORMATION_SCHEMA 查询时执行的目录扫描次数 ,如 第8.2.3节“优化INFORMATION_SCHEMA查询”中所述 N 可以是0,1或 all

  • Select tables optimized away (JSON属性: message

    优化器确定1)应该返回最多一行,以及2)为了产生该行,必须读取确定的行集。 当在优化阶段(例如,通过读取索引行)读取要读取的行时,在查询执行期间不需要读取任何表。

    当查询被隐式分组时(包含聚合函数但没有 GROUP BY 子句), 满足第一个条件 当每个使用的索引执行一行查找时,满足第二个条件。 读取的索引数决定了要读取的行数。

    请考虑以下隐式分组查询:

    SELECT MIN(c1),MIN(c2)FROM t1;
    

    假设 MIN(c1) 可以通过读取一个索引行 MIN(c2) 来检索, 并且 可以通过从不同的索引读取一行来检索。 即,对于每一列 c1 c2 ,存在其中列是索引的第一列的索引。 在这种情况下,返回一行,通过读取两个确定行来生成。

    Extra 如果要读取的行不是确定性的,则不会出现 值。 考虑这个查询:

    SELECT MIN(c2)FROM t1 WHERE c1 <= 10;
    

    假设这 (c1, c2) 是一个覆盖索引。 使用此索引, c1 <= 10 必须扫描 所有行 以查找最小值 c2 相比之下,请考虑以下查询:

    SELECT MIN(c2)FROM t1 WHERE c1 = 10;
    

    在这种情况下,第一个索引行 c1 = 10 包含最小值 c2 必须只读取一行才能生成返回的行。

    对于保持每个表的精确行数(例如 MyISAM 但不是 InnoDB )的 存储引擎,对于 缺少 子句或始终为true且没有 子句的 查询, Extra 可能会出现 (这是隐式分组查询的实例,其中存储引擎会影响是否可以读取确定数量的行。) COUNT(*) WHERE GROUP BY

  • Skip_open_table Open_frm_only Open_full_table (JSON属性: message

    这些值表示适用于 INFORMATION_SCHEMA 查询的文件打开优化

    • Skip_open_table :表文件不需要打开。 该信息已从数据字典中获得。

    • Open_frm_only :只需要读取表信息的数据字典。

    • Open_full_table :未优化的信息查找。 必须从数据字典中读取表信息并读取表文件。

  • Start temporary End temporary (JSON属性: message

    这表示临时表用于半连接Duplicate Weedout策略。

  • unique row not found (JSON属性: message

    对于诸如的查询 ,没有行满足 索引或 表的条件。 SELECT ... FROM tbl_name UNIQUE PRIMARY KEY

  • Using filesort (JSON属性: using_filesort

    MySQL必须执行额外的传递以找出如何按排序顺序检索行。 排序是通过根据连接类型遍历所有行并将排序键和指针存储到与该 WHERE 子句 匹配的所有行的行来完成的 然后对键进行排序,并按排序顺序检索行。 请参见 第8.2.1.15节“ORDER BY Optimization”

  • Using index (JSON属性: using_index

    仅使用索引树中的信息从表中检索列信息,而不必执行额外的搜索以读取实际行。 当查询仅使用属于单个索引的列时,可以使用此策略。

    对于 InnoDB 具有用户定义的聚簇索引的表,即使 列中 Using index 不存在 该索引,也可以使用该索引 Extra 这样的话,如果 type index key PRIMARY

  • Using index condition (JSON属性: using_index_condition

    通过访问索引元组并首先测试它们以确定是否读取完整的表行来读取表。 以这种方式,索引信息用于推迟( 下推 )读取全表行,除非有必要。 请参见 第8.2.1.5节“索引条件下推优化”

  • Using index for group-by (JSON属性: using_index_for_group_by

    Using index 表访问方法 类似 Using index for group-by 表示MySQL找到了一个索引,可用于检索 GROUP BY DISTINCT 查询的 所有列, 而无需对实际表进行任何额外的磁盘访问。 此外,索引以最有效的方式使用,因此对于每个组,只读取少数索引条目。 有关详细信息,请参见 第8.2.1.16节“GROUP BY优化”

  • Using index for skip scan (JSON属性: using_index_for_skip_scan

    表示使用了Skip Scan访问方法。 请参见 跳过扫描范围访问方法

  • Using join buffer (Block Nested Loop) Using join buffer (Batched Key Access) (JSON属性: using_join_buffer

    将早期联接中的表分成几部分读入连接缓冲区,然后从缓冲区中使用它们的行来执行与当前表的连接。 (Block Nested Loop) 表示使用块嵌套循环算法并 (Batched Key Access) 指示使用批量密钥访问算法。 也就是说,来自 EXPLAIN 输出 前一行的表中的键 将被缓冲,匹配的行将从 Using join buffer 出现 的行所代表的表中批量获取

    在JSON格式的输出,值 using_join_buffer 始终是中的任一个 Block Nested Loop Batched Key Access

  • Using MRR (JSON属性: message

    使用多范围读取优化策略读取表。 请参见 第8.2.1.10节“多范围读取优化”

  • Using sort_union(...) Using union(...) Using intersect(...) (JSON属性: message

    这些指示特定算法显示如何为 index_merge 连接类型 合并索引扫描 请参见 第8.2.1.3节“索引合并优化”

  • Using temporary (JSON属性: using_temporary_table

    要解析查询,MySQL需要创建一个临时表来保存结果。 如果查询包含以 不同方式列出列的 GROUP BY ORDER BY 子句, 则通常会发生这种情况

  • Using where (JSON属性: attached_condition

    WHERE 子句用于限制匹配哪些行针对下一个表或发送到客户端。 除非您特意打算从表中获取或检查所有行,否则如果 Extra 值不是 Using where 并且表连接类型为 ALL 或者 ,则 查询中可能出现错误 index

    Using where 在JSON格式的输出中没有直接的对应物; attached_condition 属性包含任何 WHERE 使用的条件。

  • Using where with pushed condition (JSON属性: message

    此产品适用于 NDB 这意味着NDB Cluster正在使用条件下推优化来提高非索引列和常量之间直接比较的效率。 在这种情况下,条件被 下推 到集群的数据节点,并在所有数据节点上同时进行评估。 这消除了通过网络发送不匹配行的需要,并且可以在可以但不使用条件下推的情况下将这种查询加速5到10倍。 有关更多信息,请参阅 第8.2.1.4节“发动机状态下推优化”

  • Zero limit (JSON属性: message

    查询有一个 LIMIT 0 子句,不能选择任何行。

EXPLAIN输出解释

通过获取 输出 rows 列中 值的乘积,可以很好地指示连接的好坏程度 EXPLAIN 这应该大致告诉你MySQL必须检查多少行才能执行查询。 如果使用 max_join_size 系统变量 限制查询,则 此行产品还用于确定 SELECT 要执行的 多表 语句和要中止的 多个表 语句。 请参见 第5.1.1节“配置服务器”

以下示例显示如何根据提供的信息逐步优化多表连接 EXPLAIN

假设您有 SELECT 此处显示 语句,并且您计划使用 EXPLAIN 以下方法 检查它

EXPLAIN SELECT tt.TicketNumber,tt.TimeIn,
               tt.ProjectReference,tt.EstimatedShipDate,
               tt.ActualShipDate,tt.ClientID,
               tt.ServiceCodes,tt.RepetitiveID,
               tt.CurrentProcess,tt.CurrentDPPerson,
               tt.RecordVolume,tt.DPPrinted,et.COUNTRY,
               et_1.COUNTRY,do.CUSTNAME
        FROM tt,et,et AS et_1,do
        WHt tt.SubmitTime为NULL
          AND tt.ActualPC = et.EMPLOYID
          AND tt.AssignedPC = et_1.EMPLOYID
          AND tt.ClientID = do.CUSTNMBR;

对于此示例,请进行以下假设:

  • 被比较的列已声明如下。

    数据类型
    tt ActualPC CHAR(10)
    tt AssignedPC CHAR(10)
    tt ClientID CHAR(10)
    et EMPLOYID CHAR(15)
    do CUSTNMBR CHAR(15)
  • 表格具有以下索引。

    指数
    tt ActualPC
    tt AssignedPC
    tt ClientID
    et EMPLOYID (首要的关键)
    do CUSTNMBR (首要的关键)
  • tt.ActualPC 值不是均匀分布的。

最初,在执行任何优化之前,该 EXPLAIN 语句将生成以下信息:

表类型possible_keys键key_len ref rows Extra
et ALL PRIMARY NULL NULL NULL 74
do ALL PRIMARY NULL NULL NULL 2135
et_1 ALL PRIMARY NULL NULL NULL 74
tt ALL AssignedPC,NULL NULL NULL 3872
           客户端ID,
           ActualPC的
      检查每条记录的范围(索引图:0x23)

因为 type ALL 针对每个表,所以此输出表明MySQL正在生成所有表的笛卡尔积; 也就是说,每一行的组合。 这需要相当长的时间,因为必须检查每个表中行数的乘积。 对于手头的情况,该产品为74×2135×74×3872 = 45,268,558,720行。 如果表格更大,你只能想象需要多长时间。

这里的一个问题是MySQL可以更有效地使用列上的索引,如果它们被声明为相同的类型和大小。 在这种情况下, VARCHAR CHAR 被认为是相同的,如果它们被声明为相同的大小。 tt.ActualPC 被声明为 CHAR(10) et.EMPLOYID CHAR(15) ,所以有一个长度不匹配。

要修复列长度之间的这种差异,请使用 从10个字符 ALTER TABLE 延长 ActualPC 到15个字符:

MySQL的> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);

现在 tt.ActualPC et.EMPLOYID 都是 VARCHAR(15) EXPLAIN 再次 执行该 语句会产生以下结果:

表类型possible_keys键key_len ref rows Extra
tt ALL AssignedPC,NULL NULL NULL 3872使用
             ClientID,在哪里
             ActualPC的
do ALL PRIMARY NULL NULL NULL 2135
      检查每条记录的范围(索引图:0x1)
et_1 ALL PRIMARY NULL NULL NULL 74
      检查每条记录的范围(索引图:0x1)
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1

这并不完美,但要好得多: rows 的乘积 减少了74倍。这个版本在几秒钟内执行。

可以进行第二次更改以消除 tt.AssignedPC = et_1.EMPLOYID tt.ClientID = do.CUSTNMBR 比较 的列长度不匹配

MySQL的> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),
                      MODIFY ClientID   VARCHAR(15);

在修改之后, EXPLAIN 生成此处显示的输出:

表类型possible_keys键key_len ref rows Extra
et ALL PRIMARY NULL NULL NULL 74
tt ref AssignedPC,ActualPC 15 et.EMPLOYID 52使用
             ClientID,在哪里
             ActualPC的
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1

此时,查询几乎尽可能地优化。 剩下的问题是,默认情况下,MySQL假定 tt.ActualPC 列中的 均匀分布,而 tt 不是这种情况 幸运的是,很容易告诉MySQL分析密钥分发:

MySQL的> ANALYZE TABLE tt;

使用附加索引信息,连接是完美的并 EXPLAIN 产生以下结果:

表类型possible_keys键key_len ref rows Extra
tt ALL AssignedPC NULL NULL NULL 3872使用
             ClientID,在哪里
             ActualPC的
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1

rows 输出中 EXPLAIN 是来自MySQL连接优化器的有根据的猜测。 通过将 rows 产品与查询返回的实际行数 进行比较,检查数字是否与事实 相符。 如果数字完全不同,则可以通过 STRAIGHT_JOIN SELECT 语句中使用并尝试在 FROM 子句 中以不同顺序列出表来 获得更好的性能 (但是, STRAIGHT_JOIN 可能会阻止使用索引,因为它会禁用半连接转换。请参阅 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式” 。)

在某些情况下,可以执行在 EXPLAIN SELECT 与子查询一起使用 时修改数据的语句 ; 有关更多信息,请参见 第13.2.11.8节“派生表”

8.8.3扩展EXPLAIN输出格式

EXPLAIN 语句产生额外( 扩展 )信息,该信息不是 EXPLAIN 输出的 一部分, 但可以通过发出 SHOW WARNINGS 以下语句 来查看 EXPLAIN 在MySQL 8.0.12的,扩展的信息可用于 SELECT DELETE INSERT REPLACE ,和 UPDATE 语句。 在8.0.12之前,扩展信息仅适用于 SELECT 语句。

输出中 Message SHOW WARNINGS 显示优化程序如何限定 SELECT 语句 中的表名和列名, SELECT 应用重写和优化规则后的外观,以及可能有关优化过程的其他说明。

可通过 SHOW WARNINGS 以下语句 显示的扩展信息 EXPLAIN 仅针对 SELECT 语句生成。 SHOW WARNINGS 显示其他可解释语句(一个空的结果 DELETE INSERT REPLACE ,和 UPDATE )。

以下是扩展 EXPLAIN 输出 的示例

MySQL的> EXPLAIN
       SELECT t1.a, t1.a IN (SELECT t2.a FROM t2) FROM t1\G
*************************** 1。排******************** *******
           id:1
  select_type:PRIMARY
        表:t1
         类型:索引
possible_keys:NULL
          关键:主要
      key_len:4
          ref:NULL
         行:4
     过滤:100.00
        额外:使用索引
*************************** 2.排******************** *******
           id:2
  select_type:SUBQUERY
        表:t2
         类型:索引
possible_keys:a
          关键:a
      key_len:5
          ref:NULL
         行:3
     过滤:100.00
        额外:使用索引
2行,1个警告(0.00秒)

MySQL的> SHOW WARNINGS\G
*************************** 1。排******************** *******
  等级:注意
   代码:1003
消息:/ * select#1 * / select`test` .t1``a` AS`a`,
         <in_optimizer>(`test` .t1` .a`,`test` .t1` .a` in
         (<materialize>(/ * select#2 * / select`test` .t2` .a`
         来自`test``t2`其中1有1),
         <primary_index_lookup>(`test` .t1` .a` in
         <auto_key>上的<临时表>
         where((`test` .t1``a` =`materialized-subquery``a`)))))AS`t1.a
         IN(SELECT t2.a FROM t2)`from`test``t1`
1排(0.00秒)

由于显示的语句 SHOW WARNINGS 可能包含特殊标记以提供有关查询重写或优化程序操作的信息,因此该语句不一定是有效的SQL,也不打算执行。 输出还可以包含具有 Message 值的 行,这些 值提供有关优化程序采取的操作的其他非SQL解释性说明。

以下列表描述了可显示在扩展输出中的特殊标记 SHOW WARNINGS

  • <auto_key>

    自动生成的临时表密钥。

  • <cache>(expr)

    表达式(例如标量子查询)执行一次,结果值保存在内存中供以后使用。 对于由多个值组成的结果,可能会创建一个临时表,您将看到 <temporary table>

  • <exists>(query fragment)

    子查询谓词被转换为 EXISTS 谓词,子查询被转换,以便它可以与 EXISTS 谓词 一起使用

  • <in_optimizer>(query fragment)

    这是一个没有用户意义的内部优化器对象。

  • <index_lookup>(query fragment)

    使用索引查找处理查询片段以查找符合条件的行。

  • <if>(condition, expr1, expr2)

    如果条件为真,则评估为 expr1 ,否则 expr2

  • <is_not_null_test>(expr)

    用于验证表达式未评估的测试 NULL

  • <materialize>(query fragment)

    使用子查询实现。

  • `materialized-subquery`.col_name

    col_name 内部临时表中 的列的引用,该 表用于保存评估子查询的结果。

  • <primary_index_lookup>(query fragment)

    使用主键查找处理查询片段以查找符合条件的行。

  • <ref_null_helper>(expr)

    这是一个没有用户意义的内部优化器对象。

  • /* select#N */ select_stmt

    SELECT 与非扩展 EXPLAIN 输出中 的行相关联,其 id 值为 N

  • outer_tables semi join (inner_tables)

    半连接操作。 inner_tables 显示未拉出的表格。 请参见 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”

  • <temporary table>

    这表示为缓存中间结果而创建的内部临时表。

当某些表是 const 或者 是某些表时, system 优化程序会尽早评估涉及这些表中列的表达式,而这些表达式不是显示语句的一部分。 但是, FORMAT=JSON 有些 const 表访问显示为 ref 使用const值 访问。

8.8.4获取命名连接的执行计划信息

要获取在命名连接中执行的可解释语句的执行计划,请使用以下语句:

解释[ options]连接connection_id;

EXPLAIN FOR CONNECTION 返回 EXPLAIN 当前用于在给定连接中执行查询 信息。 由于数据(和支持统计信息)的更改,它可能会产生与运行 EXPLAIN 等效查询文本 不同的结果 这种行为差异可用于诊断更多瞬态性能问题。 例如,如果您在一个会话中运行语句需要很长时间才能完成,则 EXPLAIN FOR CONNECTION 在另一个会话中使用可能会产生有关延迟原因的有用信息。

connection_id 是从 INFORMATION_SCHEMA PROCESSLIST 表或 SHOW PROCESSLIST 语句中 获取的连接标识符 如果您具有该 PROCESS 权限,则可以指定任何连接的标识符。 否则,您只能为自己的连接指定标识符。

如果指定的连接未执行语句,则结果为空。 否则, EXPLAIN FOR CONNECTION 仅在可以解释在命名连接中执行的语句时才适用。 这包括 SELECT DELETE INSERT REPLACE ,和 UPDATE (但是, EXPLAIN FOR CONNECTION 对于准备好的语句,甚至是那些类型的预备语句都不起作用。)

如果命名连接正在执行可解释语句,那么输出就是您 EXPLAIN 在语句本身上 使用的输出

如果命名连接正在执行不可解释的语句,则会发生错误。 例如,您无法为当前会话命名连接标识符,因为 EXPLAIN 无法解释:

MySQL的> SELECT CONNECTION_ID();
+ ----------------- +
| CONNECTION_ID()|
+ ----------------- +
| 373 |
+ ----------------- +
1排(0.00秒)

MySQL的> EXPLAIN FOR CONNECTION 373;
ERROR 1889(HY000):支持EXPLAIN FOR CONNECTION命令
仅适用于SELECT / UPDATE / INSERT / DELETE / REPLACE

Com_explain_other 状态变量表示的数 EXPLAIN FOR CONNECTION 执行的语句。

8.8.5估计查询性能

在大多数情况下,您可以通过计算磁盘搜索来估计查询性能。 对于小型表,通常可以在一个磁盘查找中找到一行(因为索引可能已缓存)。 对于更大的表,你可以估计,使用B树索引,你需要这么多寻找一行: log(row_count) / log(index_block_length / 3 * 2 / (index_length + data_pointer_length)) + 1

在MySQL中,索引块通常为1,024字节,数据指针通常为4字节。 对于密钥值长度为3个字节(大小 MEDIUMINT 的500,000行表 ,公式表示 log(500,000)/log(1024/3*2/(3+4)) + 1 = 4 seek。

该索引需要存储大约500,000 * 7 * 3/2 = 5.2MB(假设典型的索引缓冲区填充率为2/3),因此您可能在内存中有很多索引,因此只需要一次或两次调用读取数据以查找行。

但是,对于写入,您需要四个搜索请求来查找放置新索引值的位置,通常两个寻求更新索引并写入行。

前面的讨论并不意味着您的应用程序性能会慢慢退化 N 只要操作系统或MySQL服务器缓存了所有内容,随着表变大,事情就会变得稍微慢一点。 在数据太大而无法缓存之后,事情开始变得慢得多,直到您的应用程序仅受磁盘搜索限制(按日志增加 N )。 为避免这种情况,请在数据增长时增加密钥缓存大小。 对于 MyISAM 表,密钥缓存大小由 key_buffer_size 系统变量 控制 请参见 第5.1.1节“配置服务器”

8.9控制查询优化器

MySQL通过影响查询计划评估方式的系统变量,可切换优化,优化器和索引提示以及优化器成本模型来提供优化器控制。

尽管优化器尚未使用此信息,但服务器还会维护有关列值的统计信息。

8.9.1控制查询计划评估

查询优化器的任务是找到执行SQL查询的最佳计划。 因为 之间的表现差异 计划可以是数量级(即秒与小时甚至数天),大多数查询优化器(包括MySQL的查询优化器)在所有可能的查询评估计划中执行或多或少的详尽搜索以获得最佳计划。 对于连接查询,MySQL优化器调查的可能计划数量随着查询中引用的表的数量呈指数增长。 对于少量表(通常小于7到10),这不是问题。 但是,当提交较大的查询时,在查询优化中花费的时间可能很容易成为服务器性能的主要瓶颈。

更灵活的查询优化方法使用户能够控制优化器在搜索最佳查询评估计划时的详尽程度。 一般的想法是,优化器调查的计划越少,编译查询所花费的时间就越少。 另一方面,由于优化器会跳过某些计划,因此可能无法找到最佳计划。

可以使用两个系统变量来控制优化程序相对于其计算的计划数量的行为:

  • optimizer_prune_level 变量告诉优化器根据对每个表访问的行数的估计跳过某些计划。 我们的经验表明,这种 “有 根据的猜测 很少会错过最佳计划,并且可能会大大缩短查询编译时间。 这就是 optimizer_prune_level=1 默认情况下 此选项为on( )的原因。 但是,如果您认为优化程序错过了更好的查询计划,则可以关闭此选项( optimizer_prune_level=0 )查询编译可能需要更长时间的风险。 请注意,即使使用此启发式算法,优化程序仍会探索大致指数的计划。

  • optimizer_search_depth 变量告诉 优化器应该查看每个不完整计划 未来 的距离,以评估是否应该进一步扩展。 较小的值 optimizer_search_depth 可能导致查询编译时间的数量级减少。 例如,如果 optimizer_search_depth 接近查询中的表数, 则使用12,13或更多表的查询可能很容易需要数小时甚至数天才能编译 同时,如果编译 optimizer_search_depth 等于3或4,优化器可以在不到一分钟的时间内为同一查询编译。 如果您不确定合理值是什么 optimizer_search_depth ,可以将此变量设置为0以告知优化器自动确定该值。

8.9.2可切换的优化

optimizer_switch 系统变量能够在优化行为的控制。 它的值是一组标志,每个标志的值为 on off 表示是否启用或禁用了相应的优化器行为。 此变量具有全局值和会话值,可以在运行时更改。 可以在服务器启动时设置全局默认值。

要查看当前的优化程序标志集,请选择变量值:

MySQL的> SELECT @@optimizer_switch\G
*************************** 1。排******************** *******
@@ optimizer_switch:index_merge = on,index_merge_union = on,
                    index_merge_sort_union =开,
                    index_merge_intersection =开,
                    engine_condition_pushdown =开,
                    index_condition_pushdown =开,
                    MRR =开,上mrr_cost_based =,
                    block_nested_loop =开,batched_key_access =关,
                    物化=开,半连接=上,loosescan =开,
                    firstmatch =开,duplicateweedout =开,
                    subquery_materialization_cost_based =开,
                    use_index_extensions =开,
                    condition_fanout_filter =开,derived_merge =开,
                    use_invisible_indexes =关,skip_scan =上

要更改值 optimizer_switch ,请指定一个由逗号分隔的一个或多个命令列表组成的值:

SET [GLOBAL | SESSION] optimizer_switch =' command[,command] ......';

每个 command 值都应具有下表中显示的其中一种形式。

命令语法 含义
default 将每个优化重置为其默认值
opt_name=default 将命名优化设置为其默认值
opt_name=off 禁用命名优化
opt_name=on 启用命名优化

值中的命令顺序无关紧要,尽管 default 如果存在则首先执行命令。 一个设置 opt_name 标志 default 设置它取的 on 或者 off 是它的默认值。 opt_name 不允许在值中 指定任何给定的 多次,并导致错误。 值中的任何错误都会导致赋值失败并显示错误,并保持值 optimizer_switch 不变。

以下列表描述了 opt_name 按优化策略分组 的允许 标志名称:

  • 批量密钥访问标志

    • batched_key_access (默认 off

      控制BKA连接算法的使用。

    为了 batched_key_access 在设置时有任何效果 on mrr 标志也必须是 on 目前,MRR的成本估算过于悲观。 因此,也有必要对 mrr_cost_based off 用于要使用的BKA。

    有关更多信息,请参见 第8.2.1.11节“阻止嵌套循环和批量密钥访问连接”

  • 阻止嵌套循环标志

    • block_nested_loop (默认 on

      控制BNL连接算法的使用。

    有关更多信息,请参见 第8.2.1.11节“阻止嵌套循环和批量密钥访问连接”

  • 条件过滤标志

    • condition_fanout_filter (默认 on

      控制条件过滤的使用。

    有关更多信息,请参见 第8.2.1.12节“条件过滤”

  • 派生表合并标志

    • derived_merge (默认 on

      控制将派生表和视图合并到外部查询块中。

    derived_merge 标志控制优化器是否尝试将派生表,视图引用和公用表表达式合并到外部查询块中,假设没有其他规则阻止合并; 例如, ALGORITHM 视图 指令优先于 derived_merge 设置。 默认情况下,该标志 on 用于启用合并。

    有关更多信息,请参见 第8.2.2.4节“使用合并或实现优化派生表,视图引用和公用表表达式”

  • 发动机状态下推标志

    • engine_condition_pushdown (默认 on

      控制发动机状态下推。

    有关更多信息,请参见 第8.2.1.4节“发动机状态下推优化”

  • 指数条件下推标志

    • index_condition_pushdown (默认 on

      控制索引条件下推。

    有关更多信息,请参见 第8.2.1.5节“索引条件下推优化”

  • 索引扩展标志

    • use_index_extensions (默认 on

      控制索引扩展的使用。

    有关更多信息,请参见 第8.3.10节“索引扩展的使用”

  • 索引合并标志

    • index_merge (默认 on

      控制所有索引合并优化。

    • index_merge_intersection (默认 on

      控制索引合并交叉点访问优化。

    • index_merge_sort_union (默认 on

      控制索引合并排序 - 联合访问优化。

    • index_merge_union (默认 on

      控制索引合并联盟访问优化。

    有关更多信息,请参见 第8.2.1.3节“索引合并优化”

  • 索引可见性标志

    • use_invisible_indexes (默认 off

      控制不可见索引的使用。

    有关更多信息,请参见 第8.3.12节“不可见索引”

  • 多范围读取标志

    • mrr (默认 on

      控制多范围读取策略。

    • mrr_cost_based (默认 on

      如果控制使用基于成本的MRR mrr=on

    有关更多信息,请参见 第8.2.1.10节“多范围读取优化”

  • 跳过扫描标志

    • skip_scan (默认 on

      控制使用Skip Scan访问方法。

    有关更多信息,请参阅 跳过扫描范围访问方法

  • 半连接标志

    • semijoin (默认 on

      控制所有半连接策略。

    • duplicateweedout (默认 on

      控制半连接重复Weedout策略。

    • firstmatch (默认 on

      控制半连接FirstMatch策略。

    • loosescan (默认 on

      控制半连接LooseScan策略(不要与松散索引扫描混淆 GROUP BY )。

    semijoin firstmatch loosescan ,和 duplicateweedout 超过半连接策略的标志使能控制。 semijoin 标志控制是否使用半连接。 如果设置为 on ,则 firstmatch loosescan 标志可以更好地控制允许的半连接策略。

    如果 duplicateweedout 禁用半连接策略,则除非还禁用所有其他适用策略,否则不会使用该策略。

    如果 semijoin materialization 是两个 on ,半连接也适用使用物化。 这些标志是 on 默认的。

    有关更多信息,请参见 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”

  • 子查询物化标志

    • materialization (默认 on

      控制物化(包括半连接物化)。

    • subquery_materialization_cost_based (默认 on

      使用基于成本的物化选择。

    materialization 标志控制是否使用子查询实现。 如果 semijoin materialization 是两个 on ,半连接也适用使用物化。 这些标志是 on 默认的。

    subquery_materialization_cost_based 标志使得能够在子查询物化和之间的选择控制 IN -到- EXISTS 子查询变换。 如果该标志为 on (默认值),优化器进行子查询物化和之间的基于成本的选择 IN -到- EXISTS 子查询变换如果可以使用任一方法。 如果标志是 off ,优化器选择了子查询物化 IN 至- EXISTS 子查询的转变。

    有关更多信息,请参见 第8.2.2节“优化子查询,派生表,视图引用和公用表表达式”

为其分配值时 optimizer_switch ,未提及的标志会保留其当前值。 这使得可以在单个语句中启用或禁用特定的优化器行为,而不会影响其他行为。 该语句不依赖于存在的其他优化器标志以及它们的值。 假设已启用所有索引合并优化:

MySQL的> SELECT @@optimizer_switch\G
*************************** 1。排******************** *******
@@ optimizer_switch:index_merge = on,index_merge_union = on,
                    index_merge_sort_union =开,
                    index_merge_intersection =开,
                    engine_condition_pushdown =开,
                    index_condition_pushdown =开,
                    MRR =开,上mrr_cost_based =,
                    block_nested_loop =开,batched_key_access =关,
                    物化=开,半连接=上,loosescan =开,
                    firstmatch =开,
                    subquery_materialization_cost_based =开,
                    use_index_extensions =开,
                    condition_fanout_filter =上

如果服务器对某些查询使用索引合并联合或索引合并排序联合访问方法,并且您想要检查优化程序在没有它们的情况下是否会表现更好,请设置变量值,如下所示:

MySQL的> SET optimizer_switch='index_merge_union=off,index_merge_sort_union=off';

MySQL的> SELECT @@optimizer_switch\G
*************************** 1。排******************** *******
@@ optimizer_switch:index_merge = on,index_merge_union = off,
                    index_merge_sort_union =关,
                    index_merge_intersection =开,
                    engine_condition_pushdown =开,
                    index_condition_pushdown =开,
                    MRR =开,上mrr_cost_based =,
                    block_nested_loop =开,batched_key_access =关,
                    物化=开,半连接=上,loosescan =开,
                    firstmatch =开,
                    subquery_materialization_cost_based =开,
                    use_index_extensions =开,
                    condition_fanout_filter =上

8.9.3优化器提示

控制优化器策略的一种方法是设置 optimizer_switch 系统变量(参见 第8.9.2节“可切换的优化” )。 对此变量的更改会影响所有后续查询的执行; 为了使一个查询与另一个查询不同,需要 optimizer_switch 在每个 查询 之前 进行更改

控制优化器的另一种方法是使用优化器提示,可以在各个语句中指定。 由于优化程序提示适用于每个语句,因此它们可以更好地控制语句执行计划,而不是使用 optimizer_switch 例如,您可以为语句中的一个表启用优化,并禁用其他表的优化。 语句中的提示优先于 optimizer_switch 标志。

例子:

SELECT / * + NO_RANGE_OPTIMIZATION(t3 PRIMARY,f2_idx)* / f1
  从t3,其中f1> 30且f1 <33;
SELECT / * + BKA(t1)NO_BKA(t2)* / * FROM t1 INNER JOIN t2 WHERE ...;
SELECT / * + NO_ICP(t1,t2)* / * FROM t1 INNER JOIN t2 WHERE ...;
SELECT / * + SEMIJOIN(FIRSTMATCH,LOOSESCAN)* / * FROM t1 ...;
EXPLAIN SELECT / * + NO_ICP(t1)* / * FROM t1 WHERE ...;
SELECT / * + MERGE(dt)* / * FROM(SELECT * FROM t1)AS dt;
INSERT / * + SET_VAR(foreign_key_checks = OFF)* / INTO t2 VALUES(2);

此处描述的优化程序提示与索引提示不同,如 第8.9.4节“索引提示”中所述 优化程序和索引提示可以单独使用或一起使用。

优化程序提示概述

优化程序提示适用于不同的范围级别:

  • 全局:提示会影响整个语句

  • 查询块:提示会影响语句中的特定查询块

  • 表级:提示会影响查询块中的特定表

  • 索引级别:提示会影响表中的特定索引

下表总结了可用的优化程序提示,它们影响的优化程序策略以及它们应用的范围或范围。 稍后将给出更多细节。

表8.2可用的优化程序提示

提示名称 描述 适用范围
BKA NO_BKA 影响批量密钥访问联接处理 查询块,表
BNL NO_BNL 影响块嵌套循环连接处理 查询块,表
INDEX_MERGE NO_INDEX_MERGE 影响索引合并优化 表,索引
JOIN_FIXED_ORDER 使用 FROM 子句中 指定的表顺序 进行连接顺序 查询块
JOIN_ORDER 使用提示中指定的表顺序进行连接顺序 查询块
JOIN_PREFIX 使用提示中指定的表顺序作为连接顺序的第一个表 查询块
JOIN_SUFFIX 使用提示中指定的表顺序来获取连接顺序的最后一个表 查询块
MAX_EXECUTION_TIME 限制语句执行时间 全球
MERGE NO_MERGE 影响派生表/视图合并到外部查询块中
MRR NO_MRR 影响多范围读取优化 表,索引
NO_ICP 影响指数条件下推优化 表,索引
NO_RANGE_OPTIMIZATION 影响范围优化 表,索引
QB_NAME 为查询块分配名称 查询块
RESOURCE_GROUP 在语句执行期间设置资源组 全球
SEMIJOIN NO_SEMIJOIN 影响半连接策略 查询块
SKIP_SCAN NO_SKIP_SCAN 影响Skip Scan优化 表,索引
SET_VAR 在语句执行期间设置变量 全球
SUBQUERY 影响物化, IN EXISTS 子查询的策略 查询块

禁用优化会阻止优化程序使用它。 启用优化意味着优化器可以自由地使用策略(如果它适用于语句执行,而不是优化器必然会使用它)。

优化程序提示语法

MySQL支持SQL语句中的注释,如 第9.6节“注释语法”中所述 必须在 /*+ ... */ 注释中 指定优化程序提示 也就是说,优化器提示使用 /* ... */ C样式注释语法 的变体, + /* 注释开始序列 后面 有一个 字符 例子:

/ * + BKA(t1)* /
/ * + BNL(t1,t2)* /
/ * + NO_RANGE_OPTIMIZATION(t4 PRIMARY)* /
/ * + QB_NAME(qb2)* /

+ 字符 后允许空格

分析器的初始关键字后承认优化提示意见 SELECT UPDATE INSERT REPLACE ,和 DELETE 语句。 在这些情况下允许提示:

  • 在查询和数据更改语句的开头:

    SELECT / * + ... * / ...
    INSERT / * + ... * / ...
    REPLACE / * + ... * / ...
    UPDATE / * + ... * / ...
    DELETE / * + ... * / ...
    
  • 在查询块的开头:

    (SELECT / * + ... * / ...)
    (SELECT ...)UNION(SELECT / * + ... * / ...)
    (SELECT / * + ... * / ...)UNION(SELECT / * + ... * / ...)
    UPDATE ... WHERE x IN(SELECT / * + ... * / ...)
    INSERT ... SELECT / * + ... * / ...
    
  • 在前面提到的可疑陈述中 EXPLAIN 例如:

    EXPLAIN SELECT / * + ... * / ...
    EXPLAIN UPDATE ... WHERE x IN(SELECT / * + ... * / ...)
    

    这意味着您可以使用 EXPLAIN 查看优化程序提示如何影响执行计划。 SHOW WARNINGS 之后立即 使用 EXPLAIN 以查看提示的使用方式。 EXPLAIN 以下 SHOW WARNINGS 显示 的扩展 输出 表示使用了哪些提示。 忽略的提示不会显示。

提示注释可能包含多个提示,但查询块不能包含多个提示注释。 这是有效的:

SELECT / * + BNL(t1)BKA(t2)* / ...

但这是无效的:

SELECT / * + BNL(t1)* / / * BKA(t2)* / ...

当提示注释包含多个提示时,存在重复和冲突的可能性。 以下一般准则适用。 对于特定提示类型,可能会应用其他规则,如提示说明中所示。

  • 重复提示:对于提示,例如 /*+ MRR(idx1) MRR(idx1) */ ,MySQL使用第一个提示并发出有关重复提示的警告。

  • 冲突提示:对于提示,例如 /*+ MRR(idx1) NO_MRR(idx1) */ ,MySQL使用第一个提示并发出有关第二个冲突提示的警告。

查询块名称是标识符,并遵循有关哪些名称有效以及如何引用它们的常规规则(请参见 第9.2节“模式对象名称” )。

提示名称,查询块名称和策略名称不区分大小写。 对表和索引名称的引用遵循通常的标识符区分大小写规则(请参见 第9.2.2节“标识符区分大小写” )。

加入订单优化器提示

连接顺序提示会影响优化程序连接表的顺序。

JOIN_FIXED_ORDER 提示 语法

hint_name([@ query_block_name])

其他连接顺序提示的语法:

hint_name([@ query_block_name] tbl_name[,tbl_name] ......)
 hint_nametbl_name[@ query_block_name] [,tbl_name [@ query_block_name]] ......)

语法指的是这些术语:

  • hint_name :允许使用以下提示名称:

    • JOIN_FIXED_ORDER :强制优化器使用它们在 FROM 子句中 出现的顺序来连接表 这与指定相同 SELECT STRAIGHT_JOIN

    • JOIN_ORDER :指示优化器使用指定的表顺序连接表。 提示适用于命名表。 优化程序可能会在连接顺序中的任何位置放置未命名的表,包括指定表之间的表。

    • JOIN_PREFIX :指示优化器使用指定的表顺序连接表,以用于连接执行计划的第一个表。 提示适用于命名表。 优化器将所有其他表放在命名表之后。

    • JOIN_SUFFIX :指示优化器使用指定的表顺序连接表,以用于连接执行计划的最后一个表。 提示适用于命名表。 优化器将所有其他表放在命名表之前。

  • tbl_name :语句中使用的表的名称。 名称表的提示适用于它命名的所有表。 JOIN_FIXED_ORDER 提示名称没有表,并适用于所有的表 FROM 在其发生的查询块的条款。

    如果表具有别名,则提示必须引用别名,而不是表名。

    提示中的表名不能使用模式名限定。

  • query_block_name :提示适用的查询块。 如果提示不包含前导 ,则提示将应用于发生它的查询块。 对于 语法,提示适用于命名查询块中的命名表。 要为查询块指定名称,请参阅 命名查询块的优化程序提示 @query_block_name tbl_name@query_block_name

例:

选择
/ * + JOIN_PREFIX(t2,t5 @ subq2,t4 @ subq1)
    JOIN_ORDER(t4 @ subq1,t3)
    JOIN_SUFFIX(t1)* /
COUNT(*)FROM t1 JOIN t2 JOIN t3
           在哪里t1.f1 IN(SELECT / * + QB_NAME(subq1)* / f1 FROM t4)
             AND t2.f1 IN(SELECT / * + QB_NAME(subq2)* / f1 FROM t5);

提示控制合并到外部查询块的半连接表的行为。 如果子查询 subq1 subq2 转换为半连接,则表 t4@subq1 t5@subq2 合并到外部查询块。 在这种情况下,外部查询块中指定的提示控制行为 t4@subq1 t5@subq2 表。

优化器根据以下原则解析连接顺序提示:

  • 多个提示实例

    每种类型只应用 一种 JOIN_PREFIX JOIN_SUFFIX 提示。 任何后来的相同类型的提示都会被警告忽略。 JOIN_ORDER 可以多次指定。

    例子:

    / * + JOIN_PREFIX(t1)JOIN_PREFIX(t2)* /
    

    JOIN_PREFIX 警告会忽略 第二个 提示。

    / * + JOIN_PREFIX(t1)JOIN_SUFFIX(t2)* /
    

    这两个提示都适用。 没有警告发生。

    / * + JOIN_ORDER(t1,t2)JOIN_ORDER(t2,t3)* /
    

    这两个提示都适用。 没有警告发生。

  • 矛盾的提示

    在某些情况下,提示可能会发生冲突,例如何时 JOIN_ORDER JOIN_PREFIX 表订单无法同时应用:

    SELECT / * + JOIN_ORDER(t1,t2)JOIN_PREFIX(t2,t1)* / ... FROM t1,t2;
    

    在这种情况下,将应用第一个指定的提示,并忽略后续冲突提示而不显示警告。 无法应用的有效提示会在没有警告的情况下默默忽略。

  • 忽略了提示

    如果提示中指定的表具有循环依赖关系,则忽略提示。

    例:

    / * + JOIN_ORDER(t1,t2)JOIN_PREFIX(t2,t1)* /
    

    JOIN_ORDER 提示套表 t2 依赖 t1 JOIN_PREFIX 提示将被忽略,因为表 t1 不能依赖 t2 忽略的提示不会显示在扩展 EXPLAIN 输出中。

  • const 表的 交互

    MySQL优化器 const 首先 表放在连接顺序中,并且 const 的位置 不受提示的影响。 const 虽然提示仍然适用,但忽略对连接顺序提示中的表的 引用 例如,这些是等价的:

    JOIN_ORDER(T1, const_tbl,t2)
    JOIN_ORDER(t1,t2)
    

    扩展 EXPLAIN 输出中 显示的接受提示 包括 const 指定的表。

  • 与连接操作类型的交互

    MySQL支持多种类型的连接: LEFT RIGHT INNER CROSS STRAIGHT_JOIN 将忽略与指定类型的连接冲突的提示,不会发出警告。

    例:

    SELECT / * + JOIN_PREFIX(t1,t2)* / FROM t2 LEFT JOIN t1;
    

    这里在提示中请求的连接顺序和所需的顺序之间发生冲突 LEFT JOIN 忽略提示,没有任何警告。

表级优化器提示

表级提示会影响:

这些提示类型适用于特定表或查询块中的所有表。

表级提示的语法:

hint_name([@ query_block_name] [ tbl_name[,tbl_name] ...])
 hint_name([ tbl_name@ query_block_name[,tbl_name@ query_block_name] ...])

语法指的是这些术语:

  • hint_name :允许使用以下提示名称:

    • BKA NO_BKA :为指定的表启用或禁用BKA。

    • BNL NO_BNL :为指定的表启用或禁用BNL。

    • MERGE NO_MERGE :为指定的表,视图引用或公用表表达式启用合并; 或者禁用合并并使用实现。

    注意

    要使用BNL或BKA提示为外部联接的任何内部表启用联接缓冲,必须为外部联接的所有内部表启用联接缓冲。

  • tbl_name :语句中使用的表的名称。 提示适用于它命名的所有表。 如果提示没有命名表,则它适用于发生它的查询块的所有表。

    如果表具有别名,则提示必须引用别名,而不是表名。

    提示中的表名不能使用模式名限定。

  • query_block_name :提示适用的查询块。 如果提示不包含前导 ,则提示将应用于发生它的查询块。 对于 语法,提示适用于命名查询块中的命名表。 要为查询块指定名称,请参阅 命名查询块的优化程序提示 @query_block_name tbl_name@query_block_name

例子:

SELECT / * + NO_BKA(t1,t2)* / t1。* FROM t1 INNER JOIN t2 INNER JOIN t3;
SELECT / * + NO_BNL()BKA(t1)* / t1。* FROM t1 INNER JOIN t2 INNER JOIN t3;
SELECT / * + NO_MERGE(dt)* / * FROM(SELECT * FROM t1)AS dt;

表级提示适用于从先前表而不是发件人表接收记录的表。 请考虑以下声明:

SELECT / * + BNL(t2)* / FROM t1,t2;

如果优化器选择首先处理 t1 ,则 t2 通过 t1 在开始读取之前 缓冲行来 应用块嵌套循环连接 t2 如果优化器选择首先处理 t2 ,则提示无效,因为 t2 它是发送方表。

对于 MERGE NO_MERGE 提示,这些优先规则适用:

  • 提示优先于任何不是技术约束的优化程序启发式。 (如果提供提示作为建议没有效果,优化器有理由忽略它。)

  • 提示优先于 系统变量 derived_merge 标志 optimizer_switch

  • 对于视图引用, ALGORITHM={MERGE|TEMPTABLE} 视图定义中 子句优先于引用视图的查询中指定的提示。

索引级优化器提示

索引级提示会影响优化程序用于特定表或索引的索引处理策略。 这些提示类型会影响索引条件下推(ICP),多范围读取(MRR),索引合并和范围优化的使用(请参见 第8.2.1节“优化SELECT语句”) )。

索引级提示的语法:

hint_name([@ query_block_name] tbl_name[ index_name[,index_name] ...])
 hint_nametbl_name@ query_block_name[ index_name[,index_name] ...])

语法指的是这些术语:

  • hint_name :允许使用以下提示名称:

    • INDEX_MERGE NO_INDEX_MERGE :为指定的表或索引启用或禁用索引合并访问方法。 有关此访问方法的信息,请参见 第8.2.1.3节“索引合并优化” 这些提示适用于所有三种索引合并算法。

      INDEX_MERGE 提示强制使用索引合并使用指定的索引集指定表的优化。 如果未指定索引,优化程序将考虑所有可能的索引组合并选择最便宜的索引组合。 如果索引组合不适用于给定语句,则可以忽略提示。

      NO_INDEX_MERGE 提示禁用索引合并涉及任何特定指标的组合。 如果提示未指定索引,则表不允许使用索引合并。

    • MRR NO_MRR :为指定的表或索引启用或禁用MRR。 MRR提示仅适用于 InnoDB MyISAM 表格。 有关此访问方法的信息,请参见 第8.2.1.10节“多范围读取优化”

    • NO_ICP :禁用指定表或索引的ICP。 默认情况下,ICP是候选优化策略,因此没有提示启用它。 有关此访问方法的信息,请参见 第8.2.1.5节“索引条件下推优化”

    • NO_RANGE_OPTIMIZATION :禁用指定表或索引的索引范围访问。 此提示还会禁用表或索引的索引合并和松散索引扫描。 默认情况下,范围访问是候选优化策略,因此没有提示启用它。

      当范围的数量可能很高并且范围优化需要许多资源时,该提示可能是有用的。

    • SKIP_SCAN NO_SKIP_SCAN :为指定的表或索引启用或禁用Skip Scan访问方法。 有关此访问方法的信息,请参阅 跳过扫描范围访问方法 从MySQL 8.0.13开始提供这些提示。

      SKIP_SCAN 提示强制使用跳跃扫描使用指定的索引集指定表的优化。 如果未指定索引,优化程序将考虑所有可能的索引并选择最便宜的索引。 如果索引不适用于给定语句,则可以忽略提示。

      NO_SKIP_SCAN 提示禁用跳过扫描指定索引。 如果提示未指定索引,则表不允许跳过扫描。

  • tbl_name :提示适用的表。

  • index_name :指定表中索引的名称。 提示适用于它命名的所有索引。 如果提示不指定索引,则它适用于表中的所有索引。

    要引用主键,请使用名称 PRIMARY 要查看表的索引名称,请使用 SHOW INDEX

  • query_block_name :提示适用的查询块。 如果提示不包含前导 ,则提示将应用于发生它的查询块。 对于 语法,提示适用于命名查询块中的命名表。 要为查询块指定名称,请参阅 命名查询块的优化程序提示 @query_block_name tbl_name@query_block_name

例子:

SELECT / * + INDEX_MERGE(t1 f3,PRIMARY)* / f2 FROM t1
  在哪里f1 ='o'和f2 = f3 AND f3 <= 4;
SELECT / * + MRR(t1)* / * FROM t1 WHERE f2 <= 3 AND 3 <= f3;
SELECT / * + NO_RANGE_OPTIMIZATION(t3 PRIMARY,f2_idx)* / f1
  从t3,其中f1> 30且f1 <33;
INSERT INTO t3(f1,f2,f3)
  (SELECT / * + NO_ICP(t2)* / t2.f1,t2.f2,t2.f3 FROM t1,t2
   其中t1.f1 = t2.f1和t2.f2 BETWEEN t1.f1
   AND t1.f2 AND t2.f2 + 1> = t1.f1 + 1);
SELECT / * + SKIP_SCAN(t1 PRIMARY)* / f1,f2
  从t1 WHERE f2> 40;

以下示例使用索引合并提示,但其他索引级提示遵循与提示忽略和优化程序提示相对于 optimizer_switch 系统变量或索引提示的 优先级相同的原则

假设表 t1 中的列 a b c ,和 d ; 和索引命名 i_a i_b 以及 i_c 存在于 a b c ,分别为:

SELECT / * + INDEX_MERGE(t1 i_a,i_b,i_c)* / * FROM t1
  其中a = 1 AND b = 2 AND c = 3 AND d = 4;

(i_a, i_b, i_c) 在这种情况下, 索引合并用于

SELECT / * + INDEX_MERGE(t1 i_a,i_b,i_c)* / * FROM t1
  其中b = 1且c = 2且d = 3;

(i_b, i_c) 在这种情况下, 索引合并用于

/ * + INDEX_MERGE(t1 i_a,i_b)NO_INDEX_MERGE(t1 i_b)* /

NO_INDEX_MERGE 被忽略,因为同一个表有一个先前的提示。

/ * + NO_INDEX_MERGE(t1 i_a,i_b)INDEX_MERGE(t1 i_b)* /

INDEX_MERGE 被忽略,因为同一个表有一个先前的提示。

对于 INDEX_MERGE NO_INDEX_MERGE 优化程序提示,这些优先级规则适用:

  • 如果指定了优化程序提示并且适用,则它优先于 optimizer_switch 系统变量 的与索引合并相关的标志

    SET optimizer_switch ='index_merge_intersection = off';
    SELECT / * + INDEX_MERGE(t1 i_b,i_c)* / * FROM t1
    其中b = 1且c = 2且d = 3;
    

    提示优先于 optimizer_switch (i_b, i_c) 在这种情况下, 索引合并用于

    SET optimizer_switch ='index_merge_intersection = on';
    SELECT / * + INDEX_MERGE(t1 i_b)* / * FROM t1
    其中b = 1且c = 2且d = 3;
    

    提示仅指定一个索引,因此它不适用,并且 optimizer_switch flag( on )适用。 如果优化程序评估它是成本有效的,则使用索引合并。

    SET optimizer_switch ='index_merge_intersection = off';
    SELECT / * + INDEX_MERGE(t1 i_b)* / * FROM t1
    其中b = 1且c = 2且d = 3;
    

    提示仅指定一个索引,因此它不适用,并且 optimizer_switch flag( off )适用。 未使用索引合并。

  • USE INDEX FORCE INDEX IGNORE INDEX 索引提示具有比更高的优先级 INDEX_MERGE NO_INDEX_MERGE 优化提示。

    / * + INDEX_MERGE(t1 i_a,i_b,i_c)* / ... IGNORE INDEX i_a
    

    IGNORE INDEX 优先于 INDEX_MERGE ,因此索引 i_a 从索引合并的可能范围中排除。

    / * + NO_INDEX_MERGE(t1 i_a,i_b)* / ... FORCE INDEX i_a,i_b
    

    索引合并是不允许的 i_a, i_b ,因为 FORCE INDEX ,但优化被迫使用两种 i_a i_b range ref 访问。 没有冲突; 这两个提示都适用。

  • 如果 IGNORE INDEX 提示命名多个索引,则这些索引不可用于索引合并。

  • FORCE INDEX USE INDEX 提示使只有指定索引可用于索引合并。

    SELECT / * + INDEX_MERGE(t1 i_a,i_b,i_c)* / a FROM t1
    FORCE INDEX(i_a,i_b)WHERE c ='h'和a = 2 AND b ='b';
    

    索引合并交集访问算法用于 (i_a, i_b) 如果 FORCE INDEX 改为, 也是如此 USE INDEX

子查询优化器提示

子查询提示影响是否使用半连接的转换和半连接策略允许,而当半联接未使用,是否使用子查询物化或 IN 至- EXISTS 变换。 有关这些优化的更多信息,请参见 第8.2.2节“优化子查询,派生表,视图引用和公用表表达式”

影响半连接策略的提示语法:

hint_name([@ query_block_name] [ strategy[,strategy] ...])

语法指的是这些术语:

  • hint_name :允许使用以下提示名称:

  • strategy :要启用或禁用的半连接策略。 这些策略名允许: DUPSWEEDOUT FIRSTMATCH LOOSESCAN MATERIALIZATION

    对于 SEMIJOIN 提示,如果未命名策略,则根据 optimizer_switch 系统变量 启用的策略,如果可能,将使用半连接 如果策略被命名但不适用于该语句, DUPSWEEDOUT 则使用。

    对于 NO_SEMIJOIN 提示,如果未命名策略,则不使用半连接。 如果使用策略来排除声明的所有适用策略, DUPSWEEDOUT 则使用策略。

如果一个子查询嵌套在另一个子查询中并且两者都合并为外部查询的半连接,则忽略最内层查询的任何半连接策略规范。 SEMIJOIN NO_SEMIJOIN 提示仍可用于启用或禁用此类嵌套子查询的半连接转换。

如果 DUPSWEEDOUT 禁用,有时优化程序可能会生成远非最佳的查询计划。 这是由于在贪婪搜索期间进行启发式修剪,这可以通过设置来避免 optimizer_prune_level=0

例子:

SELECT / * + NO_SEMIJOIN(@ subq1 FIRSTMATCH,LOOSESCAN)* / * FROM t2
  在哪里t2.a IN(SELECT / * + QB_NAME(subq1)* / a FROM t3);
SELECT / * + SEMIJOIN(@ subq1 MATERIALIZATION,DUPSWEEDOUT)* / * FROM t2
  在哪里t2.a IN(SELECT / * + QB_NAME(subq1)* / a FROM t3);

影响是否使用子查询物化或暗示的语法 IN -到- EXISTS 转换:

SUBQUERY([@ query_block_name] strategy

提示名称始终为 SUBQUERY

对于 SUBQUERY 提示, strategy 允许使用 以下 值: INTOEXISTS MATERIALIZATION

例子:

SELECT id,IN(SELECT / * + SUBQUERY(MATERIALIZATION)* / a FROM t1)FROM t2;
SELECT * FROM t2 WHERE t2.a IN(SELECT / * + SUBQUERY(INTOEXISTS)* / a FROM t1);

对于半连接和 SUBQUERY 提示,前导 指定提示适用的查询块。 如果提示不包含前导 ,则提示将应用于发生它的查询块。 要为查询块指定名称,请参阅 命名查询块的优化程序提示 @query_block_name @query_block_name

如果提示注释包含多个子查询提示,则使用第一个。 如果还有其他类型的提示,则会产生警告。 以下隐藏的其他类型的提示被忽略。

语句执行时间优化器提示

MAX_EXECUTION_TIME 提示仅允许用于 SELECT 语句。 N 在服务器终止语句之前允许执行语句的时间 限制 (超时值,以毫秒为单位):

MAX_EXECUTION_TIME(N

超时为1秒(1000毫秒)的示例:

SELECT / * + MAX_EXECUTION_TIME(1000)* / * FROM t1 INNER JOIN t2 WHERE ...

提示设置的语句执行超时 毫秒。 如果此选项不存在或 为0,则 系统变量 建立的语句超时 适用。 MAX_EXECUTION_TIME(N) N N max_execution_time

MAX_EXECUTION_TIME 提示适用如下:

  • 对于具有多个 SELECT 关键字的 语句 ,例如联合或带有子查询的语句, MAX_EXECUTION_TIME 适用于整个语句,并且必须出现在第一个语句之后 SELECT

  • 它适用于只读 SELECT 语句。 不是只读的语句是那些调用存储函数的语句,该函数将数据修改为副作用。

  • 它不适用于 SELECT 存储程序中的语句而被忽略。

变量设置提示语法

所述 SET_VAR 提示暂时设定的系统变量的会话值(用于单个语句的持续时间)。 例子:

SELECT / * + SET_VAR(sort_buffer_size = 16M)* / name FROM people ORDER BY name;
INSERT / * + SET_VAR(foreign_key_checks = OFF)* / INTO t2 VALUES(2);
SELECT / * + SET_VAR(optimizer_switch ='mrr_cost_based = off')* / 1;

SET_VAR 提示 语法

SET_VAR(var_name= value

var_name 命名一个具有会话值的系统变量(尽管并非所有这些变量都可以命名,如后面所述)。 value 是赋给变量的值; 值必须是标量。

SET_VAR 进行临时变量更改,如以下语句所示:

MySQL的> SELECT @@unique_checks;
+ ----------------- +
| @@ unique_checks |
+ ----------------- +
| 1 |
+ ----------------- +
MySQL的> SELECT /*+ SET_VAR(unique_checks=OFF) */ @@unique_checks;
+ ----------------- +
| @@ unique_checks |
+ ----------------- +
| 0 |
+ ----------------- +
MySQL的> SELECT @@unique_checks;
+ ----------------- +
| @@ unique_checks |
+ ----------------- +
| 1 |
+ ----------------- +

使用时 SET_VAR ,无需保存和恢复变量值。 这使您可以通过单个语句替换多个语句。 考虑这一系列陈述:

SET @saved_val = @@ SESSION。var_name;
SET @@ SESSION。var_name= value;
选择 ...
SET @@ SESSION。var_name= @saved_val;

该序列可以由以下单个语句替换:

SELECT / * + SET_VAR(var_name= value)...

独立 SET 语句允许使用以下任何语法来命名会话变量:

SET SESSION var_name= value;
SET @@ SESSION。var_name= value;
SET @@。var_name= value;

由于该 SET_VAR 提示仅适用于会话变量,会话范围是隐含的,和 SESSION @@SESSION. 以及 @@ 既不需要也不允许。 包括显式会话指示符语法会导致 SET_VAR 提示被忽略并带有警告。

并非所有会话变量都允许使用 SET_VAR 各个系统变量描述表明每个变量是否可以生成; 请参见 第5.1.8节“服务器系统变量” 您还可以通过尝试使用它来在运行时检查系统变量 SET_VAR 如果变量不可用,则会发出警告:

MySQL的> SELECT /*+ SET_VAR(collation_server = 'utf8') */ 1;
+ --- +
| 1 |
+ --- +
| 1 |
+ --- +
1排,1警告(0.00秒)

MySQL的> SHOW WARNINGS\G
*************************** 1。排******************** *******
  等级:警告
   代码:4537
消息:无法使用SET_VAR提示设置变量'collat​​ion_server'。

SET_VAR 语法允许只设置一个变量,但可以给出多个提示来设置多个变量:

SELECT / * + SET_VAR(optimizer_switch ='mrr_cost_based = off')
           SET_VAR(max_heap_table_size = 1G)* / 1;

如果在同一语句中出​​现多个具有相同变量名称的提示,则应用第一个提示,并忽略其他提示并显示警告:

SELECT / * + SET_VAR(max_heap_table_size = 1G)
           SET_VAR(max_heap_table_size = 3G)* / 1;

在这种情况下,将忽略第二个提示,并显示它是冲突的警告。

一个 SET_VAR 提示与警告忽略,如果没有系统变量指定名称或变量值不正确:

SELECT / * + SET_VAR(max_size = 1G)* / 1;
SELECT / * + SET_VAR(optimizer_switch ='mrr_cost_based = yes')* / 1;

对于第一个陈述,没有 max_size 变量。 对于第二个语句, mrr_cost_flag 取值为 on off ,因此尝试将其设置 yes 为不正确。 在每种情况下,都会忽略提示并发出警告。

SET_VAR 提示仅在语句级别是允许的。 如果在子查询中使用,则会忽略提示并显示警告。

从属服务器忽略 SET_VAR 复制语句中的提示,以避免出现安全问题。

资源组提示语法

RESOURCE_GROUP 优化程序提示用于资源组管理(见 第8.12.5,“资源组” )。 此提示将临时执行语句的线程(在语句的持续时间内)分配给命名资源组。 它需要 RESOURCE_GROUP_ADMIN RESOURCE_GROUP_USER 特权。

例子:

SELECT / * + RESOURCE_GROUP(USR_default)* / name FROM people ORDER BY name;
INSERT / * + RESOURCE_GROUP(批量)* / INTO t2 VALUES(2);

RESOURCE_GROUP 提示 语法

RESOURCE_GROUP(group_name

group_name 表示在语句执行期间应为其分配线程的资源组。 如果该组不存在,则会发出警告并忽略提示。

RESOURCE_GROUP 提示必须在初始声明关键字(后出现 SELECT INSERT REPLACE UPDATE ,或 DELETE )。

另一种方法 RESOURCE_GROUP SET RESOURCE GROUP 语句,它非线性地将线程分配给资源组。 请参见 第13.7.2.4节“SET RESOURCE GROUP语法”

用于命名查询块的优化器提示

表级,索引级和子查询优化器提示允许将特定查询块命名为其参数语法的一部分。 要创建这些名称,请使用 QB_NAME 提示,该提示为发生它的查询块指定名称:

QB_NAME(name

QB_NAME 提示可用于以明确的方式明确显示其他提示适用于哪些查询块。 它们还允许在单个提示注释中指定所有非查询块名称提示,以便于理解复杂语句。 请考虑以下声明:

选择 ...
  FROM(选择......)
  FROM(SELECT ... FROM ...))...

QB_NAME 提示为语句中的查询块指定名称:

SELECT / * + QB_NAME(qb1)* / ...
  FROM(SELECT / * + QB_NAME(qb2)* / ...
  FROM(SELECT / * + QB_NAME(qb3)* / ... FROM ...))...

然后其他提示可以使用这些名称来引用相应的查询块:

SELECT / * + QB_NAME(qb1)MRR(@ qb1 t1)BKA(@ qb2)NO_MRR(@ qb3t1 idx1,id2)* / ...
  FROM(SELECT / * + QB_NAME(qb2)* / ...
  FROM(SELECT / * + QB_NAME(qb3)* / ... FROM ...))...

由此产生的效果如下:

查询块名称是标识符,并遵循有关哪些名称有效以及如何引用它们的常规规则(请参见 第9.2节“模式对象名称” )。 例如,必须引用包含空格的查询块名称,这可以使用反引号来完成:

SELECT / * + BKA(@`我的提示名称`)* / ...
  FROM(SELECT / * + QB_NAME(`我的提示名称)* / ...)...

如果 ANSI_QUOTES 启用 SQL模式,则还可以在双引号内引用查询块名称:

SELECT / * + BKA(@“我的提示名称”)* / ...
  FROM(SELECT / * + QB_NAME(“我的提示名称”)* / ...)...

8.9.4索引提示

索引提示为优化程序提供有关如何在查询处理期间选择索引的信息。 此处描述的索引提示与优化程序提示不同,如 第8.9.3节“优化程序提示”中所述 索引和优化器提示可以单独使用,也可以一起使用。

索引提示仅适用于 SELECT 语句。 (它们被解析器接受, UPDATE 但是被忽略并且没有效果。)

索引提示在表名后面指定。 (有关在语句中指定表的一般语法 SELECT ,请参见 第13.2.10.2节“JOIN语法” 。)引用单个表的语法(包括索引提示)如下所示:

tbl_name[[AS] alias] [ index_hint_list]

index_hint_listindex_hint[ index_hint] ......

index_hint
    使用{INDEX | KEY}
      [FOR {JOIN | ORDER BY | GROUP BY}]([ index_list])
  | {IGNORE | FORCE} {INDEX | KEY}
      [FOR {JOIN | ORDER BY | GROUP BY}](index_list

index_listindex_name[,index_name] ......

提示告诉MySQL只使用其中一个命名索引来查找表中的行。 替代语法 告诉MySQL不使用某些特定的索引或索引。 如果 显示MySQL正在使用可能索引列表中的错误索引,则 这些提示很有 用。 USE INDEX (index_list) IGNORE INDEX (index_list) EXPLAIN

FORCE INDEX 提示的作用就像 ,增加表扫描被认为是 非常 昂贵的。 换句话说,仅当无法使用其中一个命名索引查找表中的行时,才使用表扫描。 USE INDEX (index_list)

每个提示都需要索引名称,而不是列名。 要引用主键,请使用名称 PRIMARY 要查看表的索引名称,请使用 SHOW INDEX 语句或 INFORMATION_SCHEMA.STATISTICS 表。

index_name 价值不一定是完整的索引名。 它可以是索引名称的明确前缀。 如果前缀不明确,则会发生错误。

例子:

SELECT * FROM table1 USE INDEX(col1_index,col2_index)
  在哪里col1 = 1 AND col2 = 2 AND col3 = 3;

SELECT * FROM table1 IGNORE INDEX(col3_index)
  在哪里col1 = 1 AND col2 = 2 AND col3 = 3;

索引提示的语法具有以下特征:

  • 省略 index_list for 在语法上是有效 USE INDEX ,这意味着 “不 使用索引。 省略 index_list FORCE INDEX 或者 IGNORE INDEX 是一个语法错误。

  • 您可以通过向提示添加 FOR 子句来 指定索引提示的范围 这为查询处理的各个阶段的执行计划的优化器选择提供了更细粒度的控制。 要仅影响MySQL决定如何在表中查找行以及如何处理联接时使用的索引,请使用 FOR JOIN 要影响对行进行排序或分组的索引用法,请使用 FOR ORDER BY FOR GROUP BY

  • 您可以指定多个索引提示:

    SELECT * FROM t1 USE INDEX(i1)IGNORE INDEX for ORDER BY(i2)ORDER by a;
    

    在几个提示中命名相同的索引(即使在相同的提示中)也不是错误:

    SELECT * FROM t1 USE INDEX(i1)USE INDEX(i1,i1);
    

    但是,混合 USE INDEX FORCE INDEX 同一个表 是一个错误

    SELECT * FROM t1 USE INDEX FOR JOIN(i1)FORCE INDEX FOR JOIN(i2);
    

如果索引提示不包含 FOR 子句,则提示的范围将应用于语句的所有部分。 例如,这个提示:

IGNORE INDEX(i1)

等同于这种提示组合:

IGNORE INDEX FOR JOIN(i1)
订购的IGNORE索引(i1)
IGNORE INDEX FOR GROUP BY(i1)

在MySQL 5.0中,没有 FOR 子句的 提示范围 仅适用于行检索。 要在没有 FOR 子句 时使服务器使用此旧行为 ,请 old 在服务器启动时 启用 系统变量。 请注意在复制设置中启用此变量。 使用基于语句的二进制日志记录,为主服务器和从服务器设置不同的模式可能会导致复制错误。

当索引提示进行处理,它们是由式(收集在一个单一的列表 USE FORCE IGNORE )和范围( FOR JOIN FOR ORDER BY FOR GROUP BY )。 例如:

SELECT * FROM t1
  使用指数()IGNORE指数(i2)使用指数(i1)使用指数(i2);

相当于:

SELECT * FROM t1
   使用指数(i1,i2)IGNORE INDEX(i2);

然后按以下顺序为每个范围应用索引提示:

  1. {USE|FORCE} INDEX 如果存在则适用。 (如果不是,则使用优化程序确定的索引集。)

  2. IGNORE INDEX 应用于上一步的结果。 例如,以下两个查询是等效的:

    SELECT * FROM t1 USE INDEX(i1)IGNORE INDEX(i2)USE INDEX(i2);
    
    SELECT * FROM t1 USE INDEX(i1);
    

对于 FULLTEXT 搜索,索引提示的工作方式如下:

  • 对于自然语言模式搜索,将默默忽略索引提示。 例如, IGNORE INDEX(i1) 忽略没有警告,仍然使用索引。

  • 对于布尔模式搜索,使用 FOR ORDER BY FOR GROUP BY 静默忽略 索引提示 使用 FOR JOIN 或不使用 FOR 修饰符的 索引提示 与提示如何应用于非 FULLTEXT 搜索 相反 ,提示用于查询执行的所有阶段(查找行和检索,分组和排序)。 即使为非 FULLTEXT 索引 提供了提示,也是如此

    例如,以下两个查询是等效的:

    SELECT * FROM t
      使用指数(index1)
      IGNORE INDEX(index1)FOR ORDER BY
      IGNORE INDEX(index1)FOR GROUP BY
      在......布尔模式......;
    
    SELECT * FROM t
      使用指数(index1)
      在......布尔模式......;
    

8.9.5优化器成本模型

为了生成执行计划,优化程序使用基于对查询执行期间发生的各种操作的成本的估计的成本模型。 优化器有一组可编译的默认 成本常量 ”, 可用于制定有关执行计划的决策。

优化器还具有在执行计划构建期间使用的成本估算数据库。 这些估计值存储在 系统数据库 server_cost engine_cost 表中, mysql 并且可以随时进行配置。 这些表的目的是使得可以轻松调整优化程序在尝试获取查询执行计划时使用的成本估算。

成本模型一般操作

可配置的优化器成本模型的工作方式如下:

  • 服务器在启动时将成本模型表读入内存,并在运行时使用内存中的值。 NULL 表中指定的 任何非 成本估算优先于相应的已编译默认成本常量。 任何 NULL 估计都表明优化器使用编译的默认值。

  • 在运行时,服务器可能会重新读取成本表。 当动态加载存储引擎或 FLUSH OPTIMIZER_COSTS 执行语句 时会发生这种情况

  • 成本表使服务器管理员可以通过更改表中的条目轻松调整成本估算。 通过将条目的成本设置为,也可以轻松恢复为默认值 NULL 优化程序使用内存中的成本值,因此应该对表进行更改才能 FLUSH OPTIMIZER_COSTS 生效。

  • 客户端会话开始时的当前内存成本估算在整个会话期间应用,直到结束。 特别是,如果服务器重新读取成本表,则任何更改的估计值仅适用于随后启动的会话。 现有会话不受影响。

  • 成本表特定于给定的服务器实例。 服务器不会将成本表更改复制到复制从属服务器。

成本模型数据库

优化程序成本模型数据库由 mysql 系统数据库中 的两个表组成,这两个表 包含查询执行期间发生的操作的成本估算信息:

  • server_cost :一般服务器操作的优化程序成本估算

  • engine_cost :特定于特定存储引擎的操作的优化程序成本估算

server_cost 表包含以下列:

  • cost_name

    成本模型中使用的成本估算的名称。 该名称不区分大小写。 如果服务器在读取此表时无法识别成本名称,则会向错误日志写入警告。

  • cost_value

    成本估算值。 如果值为非 NULL ,则服务器将其用作成本。 否则,它使用默认估计值(已编译的值)。 DBA可以通过更新此列来更改成本估算。 如果服务器在读取此表时发现成本值无效(非正数),则会向错误日志写入警告。

    要覆盖默认成本估算(对于指定的条目 NULL ),请将成本设置为非 NULL 值。 要恢复为默认值,请将值设置为 NULL 然后执行 FLUSH OPTIMIZER_COSTS 以告诉服务器重新读取成本表。

  • last_update

    最后一行更新的时间。

  • comment

    与成本估算相关的描述性评论。 DBA可以使用此列提供有关成本估算行存储特定值的原因的信息。

  • default_value

    成本估算的默认值(已编译)。 此列是只读生成的列,即使关联的成本估算值发生更改,也会保留其值。 对于在运行时添加到表中的行,此列的值为 NULL

server_cost 的主键 cost_name 列,因此无法为任何成本估算创建多个条目。

服务器识别 表的 这些 cost_name server_cost

  • disk_temptable_create_cost disk_temptable_row_cost

    用于将存储在基于磁盘的存储引擎内部创建的临时表的费用估计(或者 InnoDB MyISAM )。 增加这些值会增加使用内部临时表的成本估算,并使优化程序更喜欢使用查询计划。 有关此类表的信息,请参阅 第8.4.4节“MySQL中的内部临时表使用”

    与相应内存参数( memory_temptable_create_cost memory_temptable_row_cost 的默认值相比,这些磁盘参数的较大默认值 反映了处理基于磁盘的表的更高成本。

  • key_compare_cost

    比较记录键的成本。 增加此值会导致比较许多键的查询计划变得更加昂贵。 例如, filesort 与避免使用索引进行排序的查询计划相比 ,执行a的查询计划 变得相对更昂贵。

  • memory_temptable_create_cost memory_temptable_row_cost

    存储在 MEMORY 存储引擎 中的内部创建的临时表的成本估算 增加这些值会增加使用内部临时表的成本估算,并使优化程序更喜欢使用查询计划。 有关此类表的信息,请参见 第8.4.4节“MySQL中的内部临时表使用”

    与相应磁盘参数( disk_temptable_create_cost disk_temptable_row_cost 的默认值相比,这些内存参数的较小默认值 反映了处理基于内存的表的较低成本。

  • row_evaluate_cost

    评估记录条件的成本。 与检查较少行的查询计划相比,增加此值会导致检查许多行的查询计划变得更加昂贵。 例如,与读取较少行的范围扫描相比,表扫描变得相对更昂贵。

engine_cost 表包含以下列:

  • engine_name

    此成本估算适用的存储引擎的名称。 该名称不区分大小写。 如果值为 default ,则它适用于没有自己的命名条目的所有存储引擎。 如果服务器在读取此表时无法识别引擎名称,则会向错误日志写入警告。

  • device_type

    此成本估算适用的设备类型。 该列旨在为不同的存储设备类型指定不同的成本估算,例如硬盘驱动器与固态驱动器。 目前,不使用此信息,0是唯一允许的值。

  • cost_name

    server_cost 表中 相同

  • cost_value

    server_cost 表中 相同

  • last_update

    server_cost 表中 相同

  • comment

    server_cost 表中 相同

  • default_value

    成本估算的默认值(已编译)。 此列是只读生成的列,即使关联的成本估算值发生更改,也会保留其值。 对于在运行时添加到表中的行,此列的值为 NULL ,但如果该行 cost_name 与原始行之一 具有相同的 值,则该 default_value 列将具有与该行相同的值。

对于主键 engine_cost 表是包含(一个元组 cost_name engine_name device_type )个列,所以它不可能在这些列中的值的任意组合来创建多个条目。

服务器识别 表的 这些 cost_name engine_cost

  • io_block_read_cost

    从磁盘读取索引或数据块的成本。 与读取较少磁盘块的查询计划相比,增加此值会导致读取许多磁盘块的查询计划变得更加昂贵。 例如,与读取较少块的范围扫描相比,表扫描变得相对更昂贵。

  • memory_block_read_cost

    类似于 io_block_read_cost ,但表示从内存数据库缓冲区读取索引或数据块的成本。

如果 io_block_read_cost memory_block_read_cost 值不同,则执行计划可能会在同一查询的两次运行之间更改。 假设内存访问的成本低于磁盘访问的成本。 在这种情况下,在数据被读入缓冲池之前的服务器启动时,您可能会获得与运行查询之后不同的计划,因为这样数据将在内存中。

对成本模型数据库进行更改

对于希望从默认值更改成本模型参数的DBA,请尝试将值加倍或减半并测量效果。

参数 io_block_read_cost memory_block_read_cost 参数的 更改 最有可能产生有价值的结果。 这些参数值使数据访问方法的成本模型能够考虑从不同来源读取信息的成本; 也就是说,从磁盘读取信息与读取内存缓冲区中已有信息的成本。 例如,所有其他条件相同,设置 io_block_read_cost 为大于的值 memory_block_read_cost 会使优化器更喜欢将已经保存在内存中的信息的查询计划更新为必须从磁盘读取的计划。

此示例显示如何更改默认值 io_block_read_cost

更新mysql.engine_cost
  SET cost_value = 2.0
  WHERE cost_name ='io_block_read_cost';
FLUSH OPTIMIZER_COSTS;

此示例显示如何 io_block_read_cost 更改 InnoDB 存储引擎 的值

INSERT INTO mysql.engine_cost
  VALUES('InnoDB',0,'io_block_read_cost',3.0,
  CURRENT_TIMESTAMP,'为InnoDB使用较慢的磁盘');
FLUSH OPTIMIZER_COSTS;

8.9.6优化器统计

column_statistics 数据字典表存储直方图统计有关列值,以供在构建查询执行计划的优化。 要执行直方图管理,请使用该 ANALYZE TABLE 语句; 请参见 第13.7.3.1节“ANALYZE TABLE语法”

column_statistics 表具有以下特征:

  • 该表包含除几何类型(空间数据)和之外的所有数据类型的列的统计信息 JSON

  • 该表是持久的,因此每次服务器启动时都不需要创建列统计信息。

  • 服务器对表执行更新; 用户没有。

column_statistics 用户无法直接访问 表,因为它是数据字典的一部分。 可以使用直方图信息 INFORMATION_SCHEMA.COLUMN_STATISTICS ,其作为数据字典表上的视图实现。 COLUMN_STATISTICS 有这些列:

  • SCHEMA_NAME TABLE_NAME COLUMN_NAME :架构,表和列其统计应用的名称。

  • HISTOGRAM JSON 描述列统计信息的值,存储为直方图。

列直方图包含存储在列中的值范围部分的存储桶。 直方图是 JSON 允许灵活地表示列统计信息的对象。 这是一个示例直方图对象:

{
  “水桶”:[
    [
      1,
      0.3333333333333333
    ]
    [
      2,
      0.6666666666666666
    ]
    [
      3,
      1
    ]
  ]
  “null-values”:0,
  “最后更新”:“2017-03-24 13:32:40.000000”,
  “采样率”:1,
  “histogram-type”:“singleton”,
  “指定桶数”:128,
  “data-type”:“int”,
  “collat​​ion-id”:8
}

直方图对象具有以下键:

  • buckets :直方图桶。 桶结构取决于直方图类型。

    对于 singleton 直方图,存储桶包含两个值:

    • 值1:桶的值。 类型取决于列数据类型。

    • 值2:表示值的累积频率的double。 例如,.25和.75表示列中25%和75%的值小于或等于桶值。

    对于 equi-height 直方图,存储桶包含四个值:

    • 值1,2:桶的下限和上限值。 类型取决于列数据类型。

    • 值3:表示值的累积频率的double。 例如,.25和.75表示列中25%和75%的值小于或等于桶上限值。

    • 值4:从桶较低值到其较高值的范围内的不同值的数量。

  • null-values :介于0.0和1.0之间的数字,表示作为SQL NULL 的列值的分数 如果为0,则列不包含任何 NULL 值。

  • last-updated :生成直方图时, YYYY-MM-DD hh:mm:ss.uuuuuu 格式 为UTC值

  • sampling-rate :介于0.0和1.0之间的数字,表示为创建直方图而采样的数据部分。 值为1表示已读取所有数据(无采样)。

  • histogram-type :直方图类型:

    • singleton :一个存储桶表示列中的一个值。 当列中的不同值的数量小于或等于 ANALYZE TABLE 生成直方图 语句中 指定的桶的数量时,将创建此直方图类型

    • equi-height :一个桶代表一系列值。 当列中的不同值的数量大于 ANALYZE TABLE 生成直方图 语句中 指定的桶的数量时,将创建此直方图类型

  • number-of-buckets-specified ANALYZE TABLE 生成直方图 语句中 指定的存储桶数

  • data-type :此直方图包含的数据类型。 在将持久存储中的直方图读取和解析到内存中时,需要这样做。 值之一 int uint (无符号整数), , double decimal datetime string (包括字符和二进制串)。

  • collation-id :直方图数据的排序规则ID。 data-type 值为 时,它最有意义 string 值对应 ID INFORMATION_SCHEMA.COLLATIONS 表中的 列值

要从直方图对象中提取特定值,可以使用 JSON 操作。 例如:

MySQL的> SELECT
         TABLE_NAME, COLUMN_NAME,
         HISTOGRAM->>'$."data-type"' AS 'data-type',
         JSON_LENGTH(HISTOGRAM->>'$."buckets"') AS 'bucket-count'
       FROM INFORMATION_SCHEMA.COLUMN_STATISTICS;
+ ----------------- + ------------- ----------- + ----- + --------- +
| TABLE_NAME | COLUMN_NAME | 数据类型| 桶数|
+ ----------------- + ------------- ----------- + ----- + --------- +
| 国家| 人口| int | 226 |
| 城市| 人口| int | 1024 |
| countrylanguage | 语言| 字符串| 457 |
+ ----------------- + ------------- ----------- + ----- + --------- +

优化程序使用直方图统计信息(如果适用),用于收集统计信息的任何数据类型的列。 优化程序应用直方图统计信息,以根据列值与常量值的比较的选择性(过滤效果)确定行估计值。 这些形式的谓词符合直方图使用的条件:

col_name= <> != > < > = <= IS NULL
 IS NOT NULL
 BETWEEN AND NOT BETWEEN AND IN([,] ...)
 NOT IN([,] ...)
constant
col_nameconstant
col_nameconstant
col_nameconstant
col_nameconstant
col_nameconstant
col_nameconstant
col_namecol_namecol_nameconstantconstant
col_nameconstantconstant
col_nameconstantconstantcol_nameconstantconstant

例如,这些语句包含符合直方图使用条件的谓词:

SELECT * FROM orders WHERE amount BETWEEN 100.0 AND 300.0;
SELECT * FROM tbl WHERE col1 = 15 AND col2> 100;

与常量值进行比较的要求包括常量函数,例如 ABS() FLOOR()

SELECT * FROM tbl WHERE col1 <ABS(-34);

直方图统计信息主要用于非索引列。 向可应用直方图统计信息的列添加索引也可能有助于优化程序进行行估计。 权衡是:

  • 修改表数据时必须更新索引。

  • 直方图仅在需要时创建或更新,因此在修改表数据时不会增加任何开销。 另一方面,当表修改发生时,统计信息逐渐变得过时,直到下次更新为止。

优化器优选范围优化器行估计与从直方图统计获得的估计。 如果优化程序确定范围优化程序适用,则不使用直方图统计信息。

对于索引的列,可以使用索引潜水获得行估计以进行相等比较(请参见 第8.2.1.2节“范围优化” )。 在这种情况下,直方图统计不一定有用,因为索引潜水可以产生更好的估计。

在某些情况下,使用直方图统计信息可能无法改善查询执行(例如,如果统计信息已过期)。 要检查是否是这种情况,请使用 ANALYZE TABLE 重新生成直方图统计信息,然后再次运行查询。

或者,要禁用直方图统计信息,请使用 ANALYZE TABLE 删除它们。 禁用直方图统计信息的另一种方法是关闭 系统变量 condition_fanout_filter 标志 optimizer_switch (尽管这可能会禁用其他优化):

SET optimizer_switch ='condition_fanout_filter = off';

如果使用直方图统计,则使用可以看到生成的效果 EXPLAIN 请考虑以下查询,其中没有可用于列的索引 col1

SELECT * FROM t1 WHERE col1 <24;

如果直方图统计信息表明57%的行 t1 满足 col1 < 24 谓词,即使没有索引也可以进行过滤,并且 EXPLAIN filtered 列中 显示57.00

8.10缓冲和缓存

MySQL使用几种策略来缓存内存缓冲区中的信息以提高性能。

8.10.1 InnoDB缓冲池优化

InnoDB 维护一个称为 缓冲池 的存储区域, 用于缓存内存中的数据和索引。 知道 InnoDB 缓冲池 如何 工作,并利用它来将频繁访问的数据保存在内存中,是MySQL调优的一个重要方面。

有关 InnoDB 缓冲池 内部工作原理的说明, LRU替换算法的概述以及常规配置信息,请参见 第15.5.1节“缓冲池”

有关其他 InnoDB 缓冲池配置和调整信息,请参阅以下部分:

8.10.2 MyISAM密钥缓存

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

  • 对于索引块, 维护 称为 密钥缓存 (或 密钥缓冲区 的特殊结构 该结构包含许多块缓冲区,其中放置了最常用的索引块。

  • 对于数据块,MySQL不使用特殊缓存。 相反,它依赖于本机操作系统文件系统缓存。

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

  • 多个会话可以同时访问缓存。

  • 您可以设置多个密​​钥缓存并将表索引分配给特定缓存。

要控制密钥缓存的大小,请使用 key_buffer_size 系统变量。 如果此变量设置为零,则不使用密钥缓存。 如果 key_buffer_size 值太小而不能分配最小数量的块缓冲区, 则不使用密钥缓存 (8)。

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

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

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

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

如果发生了选择替换的块已被修改,则该块被认为是 脏的”。 在这种情况下,在被替换之前,其内容被刷新到它所来自的表索引。

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

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

8.10.2.1共享密钥缓存访问

线程可以同时访问密钥缓存缓冲区,但要符合以下条件:

  • 多个会话可以访问未更新的缓冲区。

  • 正在更新的缓冲区会导致需要使用它的会话等待更新完成。

  • 多个会话可以发起导致高速缓存块替换的请求,只要它们不相互干扰(即,只要它们需要不同的索引块,从而导致不同的高速缓存块被替换)。

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

8.10.2.2多个密钥缓存

注意

从MySQL 8.0开始,这里讨论的用于引用多个 MyISAM 密钥缓存 的复合部分结构变量语法 已被弃用。

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

在存在多个密钥缓存的情况下,服务器必须知道在处理给定 MyISAM 表的 查询时要使用哪个缓存 默认情况下,所有 MyISAM 表索引都缓存在默认密钥缓存中。 要将表索引分配给特定的密钥缓存,请使用该 CACHE INDEX 语句(请参见 第13.7.7.2节“CACHE INDEX语法” )。 例如,下面的语句从表中分配指标 t1 t2 以及 t3 名为键缓存 hot_cache

MySQL的> CACHE INDEX t1, t2, t3 IN hot_cache;
+ --------- + -------------------- + ---------- + ------- --- +
| 表| Op | Msg_type | Msg_text |
+ --------- + -------------------- + ---------- + ------- --- +
| test.t1 | assign_to_keycache | 状态| 好的
| test.t2 | assign_to_keycache | 状态| 好的
| test.t3 | assign_to_keycache | 状态| 好的
+ --------- + -------------------- + ---------- + ------- --- +

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 | 价值|
+ ----------------- + --------- +
| key_buffer_size | 8384512 |
+ ----------------- + --------- +

密钥缓存变量是具有名称和组件的结构化系统变量。 For keycache1.key_buffer_size keycache1 是缓存变量名称, key_buffer_size 是缓存组件。 有关 用于引用结构化密钥缓存系统变量的语法的说明, 请参见 第5.1.9.5节“结构化系统变量”

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

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

  • 一个 键高速占用分配给所有键高速缓冲空间的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-directorymysqld_init.sql

mysqld_init.sql 每次服务器启动时都会执行 语句 该文件每行应包含一个SQL语句。 以下示例分别为 hot_cache 分配了几个表 cold_cache

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

8.10.2.3中点插入策略

默认情况下,密钥缓存管理系统使用简单的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.然后,在索引扫描操作期间,有价值的频繁命中节点也会保留在热子列表中。

8.10.2.4索引预加载

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

在没有预加载的情况下,块仍然根据查询的需要放入密钥缓存中。 虽然这些块将保留在缓存中,因为所有缓冲区都有足够的缓冲区,它们是以随机顺序从磁盘中提取的,而不是按顺序提取的。

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

MySQL的> LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES;
+ --------- + -------------- + ---------- + ---------- +
| 表| Op | Msg_type | Msg_text |
+ --------- + -------------- + ---------- + ---------- +
| test.t1 | preload_keys | 状态| 好的
| test.t2 | preload_keys | 状态| 好的
+ --------- + -------------- + ---------- + ---------- +

所述 IGNORE LEAVES 改性剂导致要预装只为索引的非叶结点的块。 因此,显示的语句预加载所有索引块 t1 ,但仅从非叶节点 预加载 t2

如果已使用 CACHE INDEX 语句将索引 分配给键高速缓存 ,则预加载会将索引块放入该高速缓存中。 否则,索引将加载到默认密钥缓存中。

8.10.2.5密钥缓存块大小

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

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

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

8.10.2.6重构密钥缓存

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

MySQL的> SET GLOBAL cold_cache.key_buffer_size=4*1024*1024;

如果为 key_buffer_size key_cache_block_size 缓存组件 分配 的值与组件的当前值不同,则服务器会销毁缓存的旧结构,并根据新值创建新结构。 如果缓存包含任何脏块,则服务器会在销毁和重新创建缓存之前将它们保存到磁盘。 如果更改其他键缓存参数,则不会进行重组。

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

8.10.3准备好的声明和存储程序的缓存

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

  • 准备好的语句,包括在SQL级别(使用 PREPARE 语句)处理 语句和使用二进制客户端/服务器协议(使用 mysql_stmt_prepare() C API函数) 处理 语句 所述 max_prepared_stmt_count 系统变量控制语句的服务器高速缓存的总数量。 (所有会话中准备好的语句总数。)

  • 存储的程序(存储过程和函数,触发器和事件)。 在这种情况下,服务器转换并缓存整个程序体。 stored_program_cache 系统变量指示存储的程序每个会话的服务器缓存的大致数量。

服务器基于每个会话维护预准备语句和存储程序的高速缓存。 其他会话无法访问为一个会话缓存的语句。 会话结束时,服务器会丢弃为其缓存的任何语句。

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

以下是该问题的说明。 假设客户准备此声明:

PREPARE s1 FROM'SELECT * FROM t1';

SELECT * 内部结构扩展为表中列的列表。 如果修改了表中的列集,则 ALTER TABLE 预准备语句将过期。 如果服务器在下次客户端执行时未检测到此更改 s1 ,则预准备语句将返回不正确的结果。

为避免由预准备语句引用的表或视图的元数据更改导致的问题,服务器会检测这些更改并在下次执行时自动重新表示该语句。 也就是说,服务器重新声明语句并重建内部结构。 在从表定义高速缓存中刷新引用的表或视图之后,也会发生重新分析,或者隐式地为缓存中的新条目腾出空间,或者显式由于 FLUSH TABLES

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

服务器还检测表达式中对象的元数据更改。 这些可能会在特定的存储方案,如语句中使用 DECLARE CURSOR 或流量控制语句,如 IF CASE RETURN

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

  • 假设更改了表或视图的元数据。 重新解析为一个发生 SELECT * 访问的表或视图的程序中,但不是一个 SELECT * 不访问该表或视图。

  • 当语句受到影响时,如果可能,服务器仅对其进行部分重新分析。 请考虑以下 CASE 声明:

    情况case_expr
      什么时候when_expr1......
      什么时候when_expr2......
      什么时候when_expr3......
      ...
    结束案例
    

    如果元数据更改仅影响 ,则会重新解析该表达式。 并且其他 表达式没有被重新解析。 WHEN when_expr3 case_expr WHEN

重新分析使用对原始转换为内部表单有效的默认数据库和SQL模式。

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

重新分析是自动的,但在发生这种情况时,会减少准备好的语句和存储的程序性能。

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

8.11优化锁定操作

MySQL使用 锁定来 管理对表内容的争用

  • 内部锁定在MySQL服务器本身内执行,以管理多个线程对表内容的争用。 这种类型的锁定是内部的,因为它完全由服务器执行,不涉及其他程序。 请参见 第8.11.1节“内部锁定方法”

  • 当服务器和其他程序锁定 MyISAM 表文件以在它们之间进行协调 时,会发生外部锁定 ,哪个程序可以访问这些表。 请参见 第8.11.5节“外部锁定”

8.11.1内部锁定方法

本节讨论内部锁定; 也就是说,在MySQL服务器本身内执行锁定以管理多个会话对表内容的争用。 这种类型的锁定是内部的,因为它完全由服务器执行,不涉及其他程序。 有关其他程序对MySQL文件执行的锁定,请参见 第8.11.5节“外部锁定”

行级锁定

MySQL 使用 行级锁定 InnoDB 以支持多个会话的同时写访问,使其适用于多用户,高度并发和OLTP应用程序。

为了避免 在单个 上执行多个并发写操作时出现 死锁 InnoDB 通过 SELECT ... FOR UPDATE 为每个预期要修改的行组 发出一个 语句 ,在事务开始时获取必要的锁 ,即使数据更改语句在事务中稍后出现。 如果事务修改或锁定多个表,则在每个事务中以相同的顺序发出适用的语句。 死锁会影响性能而不是表示严重错误,因为 默认情况下会 InnoDB 自动 检测 死锁条件并回滚其中一个受影响的事务。

在高并发系统上,当许多线程等待同一个锁时,死锁检测会导致速度减慢。 有时,禁用死锁检测可能更有效,并且在 innodb_lock_wait_timeout 发生死锁时 依赖于 事务回滚 设置。 可以使用 innodb_deadlock_detect 配置选项 禁用死锁检测

行级锁定的优点:

  • 当不同的会话访问不同的行时,更少的锁冲突。

  • 回滚的变化较少。

  • 可以长时间锁定单行。

表级锁定

MySQL使用 表级锁 MyISAM MEMORY MERGE 表,只允许一个会话更新一次这些表。 此锁定级别使这些存储引擎更适合于只读,大多数读取或单用户应用程序。

这些存储引擎 通过始终在查询开始时立即请求所有需要的锁并始终以相同的顺序锁定表来 避免 死锁 权衡是这种策略降低了并发性; 其他想要修改表的会话必须等到当前数据更改语句完成。

表级锁定的优点:

  • 需要相对较少的内存(行锁定需要每行或锁定行组的内存)

  • 在大部分表上使用时速度很快,因为只涉及一个锁。

  • 如果您经常 GROUP BY 对大部分数据 进行 操作,或者必须经常扫描整个表,请快速。

MySQL授予表写锁定如下:

  1. 如果表上没有锁,则在其上放置一个写锁。

  2. 否则,将锁定请求放入写锁定队列。

MySQL授予表读锁定如下:

  1. 如果表上没有写锁定,则对其进行读锁定。

  2. 否则,将锁定请求放入读锁定队列中。

表更新的优先级高于表检索。 因此,当释放锁定时,锁定对写入锁定队列中的请求可用,然后对读取锁定队列中的请求可用。 这可确保 即使 表格活动 繁重 ,对表格的 更新也不会 缺乏 SELECT 但是,如果表有很多更新,则 SELECT 语句会等到没有更新更新。

有关更改读写优先级的信息,请参见 第8.11.2节“表锁定问题”

您可以通过检查 Table_locks_immediate Table_locks_waited 状态变量 来分析系统上的表锁争用 ,这些变量分别指示可以立即授予对表锁的请求的次数和必须等待的次数:

MySQL的> SHOW STATUS LIKE 'Table%';
+ ----------------------- + --------- +
| Variable_name | 价值|
+ ----------------------- + --------- +
| Table_locks_immediate | 1151552 |
| Table_locks_waited | 15324 |
+ ----------------------- + --------- +

性能模式锁定表还提供锁定信息。 请参见 第26.12.12节“性能模式锁定表”

MyISAM 存储引擎支持并发插入,减少读者和作者之间的竞争给定表:如果一个 MyISAM 表有数据文件的中间没有空闲块,行总是在数据文件的末尾插入。 在这种情况下,您可以自由地混合 没有锁 表的 并发 INSERT SELECT 语句 MyISAM 也就是说,您可以将行插入到 MyISAM 其他客户正在从中读取表格。 在表格中间删除或更新的行可能会导致漏洞。 如果存在漏洞,则会禁用并发插入,但在所有漏洞都填充新数据后会自动再次启用。 要控制此行为,请使用 concurrent_insert 系统变量。 请参见 第8.11.3节“并发插入”

如果显式获取表锁 LOCK TABLES ,则可以请求 READ LOCAL 锁而不是 READ 锁,以使其他会话在锁定表时执行并发插入。

执行许多 INSERT SELECT 操作上的表 t1 时并发的插入是不可能的,你可以插入行到一个临时表 temp_t1 ,并从临时表中的行更新真正的表:

mysql> LOCK TABLES t1 WRITE, temp_t1 WRITE;
mysql> INSERT INTO t1 SELECT * FROM temp_t1;
mysql> DELETE FROM temp_t1;
mysql>UNLOCK TABLES;

选择锁定类型

通常,在以下情况下,表锁优于行级锁:

  • 该表的大多数陈述都是读数。

  • 表的语句是读写的混合,其中写入是单行的更新或删除,可以使用一个键读取:

    UPDATE tbl_nameSET column= valueWHERE unique_key_col= key_value;
    DELETE FROM tbl_nameWHERE unique_key_col= key_value;
    
  • SELECT 结合并发 INSERT 语句,很少 UPDATE DELETE 语句。

  • GROUP BY 在没有任何编写器的情况下对整个表进行 多次扫描或 操作。

使用更高级别的锁,您可以通过支持不同类型的锁来更轻松地调整应用程序,因为锁开销小于行级锁。

行级锁定以外的选项:

  • 版本控制(例如MySQL中用于并发插入的版本控制),其中可以与许多读者同时拥有一个编写器。 这意味着数据库或表支持数据的不同视图,具体取决于访问何时开始。 这个其他常见的术语是 时间旅行 ” “ 上写副本 , ” 按需复制。

  • 在许多情况下,按需复制优于行级锁定。 但是,在最坏的情况下,它可以使用比使用普通锁更多的内存。

  • 而不是使用行级锁,你可以使用应用程序级锁,如提供的 GET_LOCK() RELEASE_LOCK() 在MySQL。 这些是咨询锁,因此它们仅适用于彼此协作的应用程序。 请参见 第12.14节“锁定功能”

8.11.2表锁定问题

InnoDB 表使用行级锁定,以便多个会话和应用程序可以同时读取和写入同一个表,而不会使彼此等待或产生不一致的结果。 对于此存储引擎,请避免使用该 LOCK TABLES 语句,因为它不提供任何额外保护,而是降低并发性。 自动行级锁定使这些表适用于最繁忙的数据库,包含最重要的数据,同时还简化了应用程序逻辑,因为您不需要锁定和解锁表。 因此, InnoDB 存储引擎是MySQL中的默认引擎。

MySQL对所有存储引擎使用表锁定(而不是页锁,行锁或列锁) InnoDB 锁定操作本身没有太多开销。 但是因为在任何时候只有一个会话可以写入表,为了获得这些其他存储引擎的最佳性能,主要将它们用于经常查询但很少插入或更新的表。

性能考虑因素支持InnoDB

在选择是使用 InnoDB 还是使用其他存储引擎 创建表时 ,请记住表锁定的以下缺点:

  • 表锁定允许多个会话同时从表中读取,但如果会话想要写入表,则必须首先获得独占访问权,这意味着它可能必须等待其他会话首先完成表。 在更新期间,要访问此特定表的所有其他会话必须等到更新完成。

  • 在会话等待时,表锁定会导致问题,因为磁盘已满并且在会话可以继续之前需要可用空间。 在这种情况下,所有想要访问问题表的会话也会处于等待状态,直到有更多磁盘空间可用。

  • 一个 SELECT 是需要长时间运行的语句防止其他会话更新表的同时,使其他场次出现缓慢或无响应。 当会话等待获得对表的独占访问以进行更新时,发出 SELECT 语句的 其他会话 将在其后排队,即使对于只读会话也会降低并发性。

锁定性能问题的变通方法

以下各项描述了避免或减少表锁定引起的争用的一些方法:

  • 考虑将表切换到 InnoDB 存储引擎,可以 CREATE TABLE ... ENGINE=INNODB 在安装期间 使用,也可以使用 ALTER TABLE ... ENGINE=INNODB 现有表。 有关 此存储引擎 的更多详细信息 请参见 第15章, InnoDB 存储引擎。

  • 优化 SELECT 语句以更快地运行,以便在更短的时间内锁定表。 您可能必须创建一些汇总表来执行此操作。

  • 启动 mysqld的 使用 --low-priority-updates 对于仅使用表级锁(如存储引擎 MyISAM MEMORY MERGE ),这给所有的语句更新(修改),比表低优先级 SELECT 的语句。 在这种情况下, SELECT 前一个场景中 的第二个 语句将在 UPDATE 语句 之前执行 ,并且不会等待第一个语句 SELECT 完成。

  • 要指定在特定连接中发出的所有更新都应以低优先级完成,请将 low_priority_updates 服务器系统变量 设置 为1。

  • 要使特定的 INSERT UPDATE DELETE 语句具有较低的优先级,请使用该 LOW_PRIORITY 属性。

  • 要为特定 SELECT 语句 赋予 更高的优先级,请使用该 HIGH_PRIORITY 属性。 请参见 第13.2.10节“SELECT语法”

  • 使用 系统变量 的低值 启动 mysqld max_write_lock_count 以强制MySQL临时提升在 SELECT 对表进行特定数量的插入后等待表 的所有 语句 的优先级 这允许 READ 在一定数量的锁之后 WRITE 锁定。

  • 如果你有问题 INSERT 联合 SELECT ,可考虑改用 MyISAM 表,它支持并发 SELECT INSERT 报表。 (参见 第8.11.3节“并发插入” 。)

  • 如果你有混合 SELECT DELETE 语句的 问题 LIMIT 选项 DELETE 可能会有所帮助。 请参见 第13.2.2节“删除语法”

  • 使用 SQL_BUFFER_RESULT with SELECT 语句有助于缩短表锁的持续时间。 请参见 第13.2.10节“SELECT语法”

  • 通过允许查询针对一个表中的列运行,将表内容拆分为单独的表可能会有所帮助,而更新仅限于不同表中的列。

  • 您可以更改锁定代码 mysys/thr_lock.c 以使用单个队列。 在这种情况下,写锁和读锁具有相同的优先级,这可能有助于某些应用程序。

8.11.3并发插入

MyISAM 存储引擎支持并发插入,减少读者和作者之间的竞争给定表:如果一个 MyISAM 表已经在数据文件中没有孔(中删除的行),一个 INSERT 语句可以执行行添加到表的末尾同时 SELECT 语句正在从表中读取行。 如果有多个 INSERT 语句,它们将按顺序排队并与 SELECT 语句 同时执行 并发的结果 INSERT 可能不会立即显示。

所述 concurrent_insert 系统变量可以被设置为修改并发插入处理。 默认情况下,变量设置为 AUTO (或1),并且如前所述处理并发插入。 如果 concurrent_insert 设置为 NEVER (或0),则禁用并发插入。 如果变量设置为 ALWAYS (或2),则即使对于已删除行的表,也允许在表末尾进行并发插入。 另请参见 concurrent_insert 系统变量 的说明

如果使用二进制日志,则并发插入将转换为 CREATE ... SELECT INSERT ... SELECT 语句的 正常插入 这样做是为了确保您可以通过在备份操作期间应用日志来重新创建表的精确副本。 请参见 第5.4.4节“二进制日志” 此外,对于这些语句,读取锁定放置在selected-from表中,以便阻止对该表的插入。 结果是该表的并发插入也必须等待。

使用 LOAD DATA ,如果使用 满足并发插入条件 CONCURRENT MyISAM 指定 (即,中间不包含空闲块),则其他会话可以在 LOAD DATA 执行时 从表中检索数据 即使没有其他会话同时使用该表, 使用该 CONCURRENT 选项 LOAD DATA 也会 影响 某个位 的性能

如果指定 HIGH_PRIORITY ,则在 --low-priority-updates 使用该选项启动服务器时 ,它将覆盖该 选项 的效果 它还会导致不使用并发插入。

LOCK TABLE ,之间的差 READ LOCAL 并且 READ READ LOCAL 允许非冲突性的 INSERT 语句(并发插入),而锁被保持来执行。 但是,如果要在保持锁定时使用服务器外部的进程操作数据库,则无法使用此功能。

8.11.4元数据锁定

MySQL使用元数据锁定来管理对数据库对象的并发访问并确保数据一致性。 元数据锁定不仅适用于表,还适用于模式,存储程序(过程,函数,触发器和预定事件)和表空间。

性能模式 metadata_locks 表公开了元数据锁定信息,这对于查看哪些会话持有锁定,被阻塞等待锁定等非常有用。 有关详细信息,请参见 第26.12.12.3节“metadata_locks表”

元数据锁定确实涉及一些开销,随着查询量的增加而增加。 元数据争用会增加多个查询尝试访问相同对象的次数。

元数据锁定不是表定义高速缓存的替代,其互斥锁和锁与互斥锁不同 LOCK_open 以下讨论提供了有关元数据锁定如何工作的一些信息。

元数据锁定获取

如果给定锁定有多个服务员,则首先满足优先级最高的锁定请求,并且与 max_write_lock_count 系统变量有关。 写锁定请求的优先级高于读取锁定请求。 但是,如果 max_write_lock_count 设置为某个低值(例如10),则如果已经传递了读取锁定请求以支持10个写入锁定请求,则读取锁定请求可能优先于挂起的写入锁定请求。 通常不会发生此行为,因为 max_write_lock_count 默认情况下具有非常大的值。

语句逐个获取元数据锁,而不是同时获取,并在此过程中执行死锁检测。

DML语句通常按照语句中提到的表的顺序获取锁。

DDL语句 LOCK TABLES 和其他类似语句尝试通过按名称顺序获取显式命名表上的锁来减少并发DDL语句之间可能出现的死锁数。 对于隐式使用的表(例如也必须锁定的外键关系中的表),可以以不同的顺序获取锁。

例如, RENAME TABLE 是一个按名称顺序获取锁的DDL语句:

  • RENAME TABLE 语句重命名 tbla 为其他内容,并重命名 tblc tbla

    RENAME TABLE tbla TO tbld,tblc TO tbla;
    

    声明获取元数据锁,按顺序上 tbla tblc tbld (因为 tbld 如下 tblc 的名称顺序排列):

  • 这个稍微不同的语句也会重命名 tbla 为其他 语句 ,并重命名 tblc tbla

    RENAME TABLE tbla TO tblb,tblc TO tbla;
    

    在这种情况下,该语句获取元数据锁,按顺序上 tbla tblb tblc (因为 tblb 先于 tblc 在名称顺序排列):

这两个语句获取锁上 tbla tblc 按此顺序,但在剩下的表名的锁是否之前或之后获得的不同 tblc

当多个事务同时执行时,元数据锁获取顺序可以在操作结果上产生差异,如下例所示。

与两个表开始 x x_new 具有相同的结构。 三个客户端发出涉及这些表的语句:

客户1:

LOCK TABLE x WRITE,x_new WRITE;

声明请求并获得写名字顺序锁在 x x_new

客户2:

插入x值(1);

语句请求并阻塞等待写锁定 x

客户3:

RENAME TABLE x TO x_old,x_new TO x;

声明要求独占锁在名字顺序 x x_new x_old ,而是块等待上了锁 x

客户1:

解锁表;

该语句释放写锁 x x_new x 客户端3 的独占锁定请求 具有比客户端2的写入锁定请求更高的优先级,因此客户端3获取其锁定 x ,然后还打开 x_new x_old 执行重命名,并释放其锁定。 然后,客户端2获取其锁定 x ,执行插入并释放其锁定。

锁定获取顺序导致 RENAME TABLE 执行之前的执行 INSERT x 其插入到发生的是,被命名表 x_new 时,客户端2日插入,并更名为 x 通过客户端3:

MySQL的> SELECT * FROM x;
+ ------ +
| 我|
+ ------ +
| 1 |
+ ------ +

MySQL的> SELECT * FROM x_old;
空集(0.01秒)

现在开始使用已命名 x new_x 具有相同结构的表。 同样,三个客户端发出涉及这些表的语句:

客户1:

LOCK TABLE x WRITE,new_x WRITE;

声明请求并获得写名字顺序锁在 new_x x

客户2:

插入x值(1);

语句请求并阻塞等待写锁定 x

客户3:

RENAME TABLE x TO old_x,new_x TO x;

声明要求独占锁在名字顺序 new_x old_x x ,而是块等待上了锁 new_x

客户1:

解锁表;

该语句释放写锁 x new_x 对于 x ,唯一的待处理请求是客户端2,因此客户端2获取其锁定,执行插入并释放锁定。 因为 new_x ,唯一的待处理请求是客户端3,允许获取该锁(以及锁定 old_x )。 重命名操作仍会阻止锁定, x 直到客户端2插入完成并释放其锁定。 然后,客户端3获取锁定 x ,执行重命名并释放其锁定。

在这种情况下,锁定获取顺序导致在 INSERT 执行之前执行 RENAME TABLE x 进入时发生的插入是原来的 x ,现在改名为 old_x 通过重命名操作:

MySQL的> SELECT * FROM x;
空集(0.01秒)

MySQL的> SELECT * FROM old_x;
+ ------ +
| 我|
+ ------ +
| 1 |
+ ------ +

如果并发语句中的锁获取顺序对操作结果中的应用程序产生影响,如前面的示例所示,您可以调整表名以影响锁获取的顺序。

必要时,元数据锁扩展到由外键约束相关的表,以防止冲突的DML和DDL操作在相关表上并发执行。 更新父表时,在更新外键元数据时对子表执行元数据锁定。 外键元数据由子表拥有。

元数据锁定版本

为确保事务可序列化,服务器不得允许一个会话在另一个会话中未完成的显式或隐式启动的事务中使用的表上执行数据定义语言(DDL)语句。 服务器通过获取事务中使用的表的元数据锁并延迟释放这些锁直到事务结束来实现此目的。 表上的元数据锁可防止更改表的结构。 这种锁定方法的含义是,在事务结束之前,其他会话不能在DDL语句中使用一个会话中的事务正在使用的表。

该原则不仅适用于事务表,也适用于非事务表。 假设会话开始使用事务表 t 和非 事务表的 事务 nt ,如下所示:

开始交易;
SELECT * FROM t;
SELECT * FROM nt;

服务器在两者上都保持元数据锁 t nt 直到事务结束。 如果另一个会话在任一个表上尝试DDL或写入锁定操作,它将阻塞,直到事务结束时元数据锁定释放。 例如,第二个会话阻止它是否尝试以下任何操作:

DROP TABLE t;
ALTER TABLE t ...;
DROP TABLE nt;
ALTER TABLE nt ......;
LOCK TABLE t ... WRITE;

同样的行为适用于The LOCK TABLES ... READ 也就是说,更新任何表(事务性或非事务性)的显式或隐式启动事务将阻塞并被 LOCK TABLES ... READ 该表 阻止

如果服务器获取语法有效但在执行期间失败的语句的元数据锁,则它不会提前释放锁。 锁定释放仍然延迟到事务结束,因为失败的语句被写入二进制日志,并且锁定保护日志一致性。

在自动提交模式下,每个语句实际上都是一个完整的事务,因此为语句获取的元数据锁只保留在语句的末尾。

PREPARE 即使在多语句事务中进行了准备,也会在语句准备好后释放语句中 获取的元数据锁

从MySQL 8.0.13开始,对于 PREPARED 状态中的 XA事务, 在客户端断开连接和服务器重新启动之间维护元数据锁,直到 执行 XA COMMIT XA ROLLBACK 执行。

8.11.5外部锁定

外部锁定是使用文件系统锁定来管理 MyISAM 多个进程 数据库表的 争用 外部锁定用于不能假定单个进程(如MySQL服务器)是唯一需要访问表的进程的情况。 这里有些例子:

  • 如果运行多个使用相同数据库目录的服务器(不推荐),则每个服务器都必须启用外部锁定。

  • 如果使用 myisamchk 对表执行表维护操作 MyISAM ,则必须确保服务器未运行,或者服务器已启用外部锁定,以便根据需要锁定表文件以与 myisamchk 协调以 访问表。 使用 myisampack 打包 MyISAM 也是如此

    如果服务器在启用外部锁定的情况下运行,则可以随时使用 myisamchk 进行读取操作,例如检查表。 在这种情况下,如果服务器尝试更新 myisamchk 正在使用 的表, 服务器将等待 myisamchk 完成后再继续。

    如果使用 myisamchk 进行写操作(例如修复或优化表),或者使用 myisampack 打包表,则 必须 始终确保 mysqld 服务器不使用该表。 如果你没有停止 mysqld ,至少 在你运行 myisamchk 之前 做一个 mysqladmin flush-tables 如果服务器和 myisamchk 同时访问表, 您的表 可能会损坏

在外部锁定生效的情况下,需要访问表的每个进程都会在继续访问表之前获取表文件的文件系统锁。 如果无法获取所有必需的锁,则阻止进程访问表,直到可以获取锁(在当前持有锁的进程释放它们之后)。

外部锁定会影响服务器性能,因为服务器有时必须等待其他进程才能访问表。

如果运行单个服务器来访问给定的数据目录(通常情况下), 并且在服务器运行 时没有其他程序(如 myisamchk) 需要修改表, 则不需要外部锁定 如果您只 读取 其他程序的表,则不需要外部锁定,尽管 如果服务器在 myisamchk 读取时 更改了表 myisamchk 可能会报告警告

禁用外部锁定后,要使用 myisamchk ,必须在 执行 myisamchk时 停止服务器, 或者在运行 myisamchk 之前锁定并刷新表 (请参阅 系统因素 。)要避免此要求,请使用 CHECK TABLE REPAIR TABLE 语句检查和修复 MyISAM 表。

对于 mysqld ,外部锁定由 skip_external_locking 系统变量 的值控制 启用此变量时,将禁用外部锁定,反之亦然。 默认情况下禁用外部锁定。

可以使用 --external-locking --skip-external-locking 选项 在服务器启动时控制外部锁定的使用

如果使用外部锁定选项来启用 MyISAM 来自许多MySQL进程的表的 更新 ,请不要在 delay_key_write 系统变量设置为的情况下 启动服务器, ALL 或者 DELAY_KEY_WRITE=1 对任何共享表 使用 表选项。 否则,可能会发生索引损坏。

满足这种条件的最简单方法是始终 --external-locking 与...一起使用 --delay-key-write=OFF (默认情况下不会这样做,因为在许多设置中,混合使用前面的选项很有用。)

8.12优化MySQL服务器

本节讨论数据库服务器的优化技术,主要处理系统配置而不是调优SQL语句。 本节中的信息适用于希望确保所管理服务器的性能和可伸缩性的DBA; 用于构建包括设置数据库的安装脚本的开发人员; 和人们自己运行MySQL进行开发,测试等等,他们希望最大限度地提高自己的工作效率。

8.12.1优化磁盘I / O.

本节介绍在将更多,更快的存储硬件投入数据库服务器时配置存储设备的方法。 有关优化 InnoDB 配置以提高I / O性能的信息,请参见 第8.5.8节“优化InnoDB磁盘I / O”

  • 磁盘搜索是一个巨大的性能瓶颈。 当数据量开始增长到无法实现有效缓存时,这个问题就变得更加明显了。 对于您或多或少随机访问数据的大型数据库,您可以确保至少需要一个磁盘读取和一些磁盘寻求写入内容。 要最小化此问题,请使用寻道时间较短的磁盘。

  • 通过将文件符号链接到不同磁盘或条带化磁盘来增加可用磁盘轴数(从而减少搜索开销):

    • 使用符号链接

      这意味着,对于 MyISAM 表,您将索引文件和数据文件从它们在数据目录中的通常位置符号链接到另一个磁盘(也可能是条带化的)。 这使得搜索和读取时间都更好,假设磁盘也不用于其他目的。 请参见 第8.12.2节“使用符号链接”

      InnoDB 表格 不支持符号链接 但是,可以将 InnoDB 数据和日志文件放在不同的物理磁盘上。 有关更多信息,请参见 第8.5.8节“优化InnoDB磁盘I / O”

    • 条带化

      条带化意味着您有许多磁盘并将第一个块放在第一个磁盘上,第二个块放在第二个磁盘上,第二 N 个块放在( )磁盘上,依此类推。 这意味着如果您的正常数据大小小于条带大小(或完全对齐),您将获得更好的性能。 条带化非常依赖于操作系统和条带大小,因此使用不同的条带大小对应用程序进行基准测试。 请参见 第8.13.2节“使用您自己的基准” N MOD number_of_disks

      对于分拆的速度差是 非常 依赖的参数。 根据您设置条带化参数和磁盘数量的方式,您可能会获得以数量级计量的差异。 您必须选择优化随机或顺序访问。

  • 为了提高可靠性,您可能需要使用RAID 0 + 1(条带加镜像),但在这种情况下,您需要2个 N 驱动器来保存 N 数据驱动器。 如果你有钱的话,这可能是最好的选择。 但是,您可能还需要投资一些卷管理软件才能有效地处理它。

  • 一个好的选择是根据数据类型的重要程度来改变RAID级别。 例如,存储可在RAID 0磁盘上重新生成的半重要数据,但将非常重要的数据(如主机信息和日志)存储在RAID 0 + 1或RAID N 磁盘上。 N 由于更新奇偶校验位所需的时间,如果您有多次写入, RAID 可能会出现问题。

  • 您还可以设置数据库使用的文件系统的参数:

    如果您不需要知道上次访问文件的时间(这在数据库服务器上确实没用),您可以使用该 -o noatime 选项 安装文件系统 这会跳过文件系统上inode中最后一次访问时间的更新,从而避免了一些磁盘搜索。

    在许多操作系统上,您可以通过使用该 -o async 选项 挂载来设置要异步更新的文件系统 如果您的计算机相当稳定,这应该可以在不牺牲太多可靠性的情况下提供更好的性能。 (默认情况下,此标志在Linux上处于启用状态。)

使用NFS与MySQL

在考虑将NFS与MySQL一起使用时,建议小心。 潜在问题因操作系统和NFS版本而异,包括:

  • 放置在NFS卷上的MySQL数据和日志文件将被锁定且无法使用。 例如,由于断电导致MySQL的多个实例访问同一数据目录或MySQL被不正确地关闭的情况,可能会发生锁定问题。 NFS版本4通过引入咨询和基于租约的锁定解决了潜在的锁定问题。 但是,不建议在MySQL实例之间共享数据目录。

  • 由于无序收到的消息或网络流量丢失而​​引入的数据不一致。 要避免此问题,请使用TCP with hard intr mount选项。

  • 最大文件大小限制。 NFS版本2客户端只能访问文件的最低2GB(带符号的32位偏移)。 NFS版本3客户端支持更大的文件(最多64位偏移)。 支持的最大文件大小还取决于NFS服务器的本地文件系统。

在专业SAN环境或其他存储系统中使用NFS往往比在此类环境之外使用NFS提供更高的可靠性。 但是,SAN环境中的NFS可能比直接连接或总线连接的非旋转存储更慢。

如果选择使用NFS,则建议使用NFS版本4或更高版本,以及在部署到生产环境之前彻底测试NFS设置。

8.12.2使用符号链接

您可以将数据库或表从数据库目录移动到其他位置,并使用指向新位置的符号链接替换它们。 您可能希望这样做,例如,将数据库移动到具有更多可用空间的文件系统,或者通过将表扩展到不同的磁盘来提高系统的速度。

对于 InnoDB 表,请使用 DATA DIRECTORY 语句上 子句 CREATE TABLE 而不是符号链接,如 第15.6.3.6节“在数据目录外创建表空间”中所述 此新功能是一种受支持的跨平台技术。

建议的方法是将整个数据库目录符号链接到其他磁盘。 符号链表 MyISAM 仅作为最后的手段。

要确定数据目录的位置,请使用以下语句:

显示变量像'datadir';

8.12.2.1在Unix上使用数据库的符号链接

在Unix上,符号链接数据库的方法是首先在某个磁盘上创建一个目录,在该磁盘上有可用空间,然后从MySQL数据目录创建一个到它的软链接。

shell> mkdir /dr1/databases/test
shell>ln -s /dr1/databases/test /path/to/datadir

MySQL不支持将一个目录链接到多个数据库。 只要不在数据库之间建立符号链接,就可以使用符号链接替换数据库目录。 假设您 db1 在MySQL数据目录下 有一个数据库 ,然后创建一个 db2 指向以下内容 的符号链接 db1

shell> 
shell>cd /path/to/datadirln -s db1 db2

其结果是,对于任何表 tbl_a db1 ,也似乎是一个表 tbl_a db2 如果一个客户端更新 db1.tbl_a 而另一个客户端更新 db2.tbl_a ,则可能会出现问题。

8.12.2.2在Unix上使用MyISAM表的符号链接

注意

此处描述的符号链接支持以及 --symbolic-links 控制它 选项已弃用,将在未来的MySQL版本中删除。 此外,默认情况下禁用该选项。

仅对 MyISAM 表格 完全支持符号链接 对于表用于其他存储引擎的文件,如果尝试使用符号链接,可能会遇到奇怪的问题。 对于 InnoDB 表,请使用 第15.6.3.6节“在数据目录外创建表空间”中 说明的替代技术

不要在没有完全可操作 realpath() 调用的 系统上进行符号链接表 (Linux和Solaris支持 realpath() )。 要确定您的系统是否支持符号链接,请 have_symlink 使用以下语句 检查 系统变量 的值

SHOW VARIABLES喜欢'have_symlink';

MyISAM 的符号链接的处理 如下:

  • 在数据目录中,始终具有data( .MYD )文件和index( .MYI )文件。 数据文件和索引文件可以移动到其他位置,并通过符号链接在数据目录中替换。

  • 您可以将数据文件和索引文件独立地符号链接到不同的目录。

  • 要指示正在运行的MySQL服务器执行符号链接,请使用 DATA DIRECTORY INDEX DIRECTORY 选项 CREATE TABLE 请参见 第13.1.20节“CREATE TABLE语法” 或者,如果 mysqld 未运行,则可以使用 命令行中的 ln -s 手动完成符号链接

    注意

    用的一方或双方使用的路径 DATA DIRECTORY INDEX DIRECTORY 选项可能不包括MySQL的 data 目录。 (缺陷号32167)

  • myisamchk 不会用数据文件或索引文件替换符号链接。 它直接在符号链接指向的文件上工作。 在数据文件或索引文件所在的目录中创建任何临时文件。 同样是真实的 ALTER TABLE OPTIMIZE TABLE REPAIR TABLE 语句。

  • 注意

    删除使用符号链接的表时,符号 链接和符号链接指向的文件都将被删除 这是一个非常好的理由 mysqld 作为 root 操作系统用户运行或允许操作系统用户具有对MySQL数据库目录的写访问权。

  • 如果使用 ALTER TABLE ... RENAME or 重命名表, RENAME TABLE 并且不将表移动到另一个数据库,则数据库目录中的符号链接将重命名为新名称,并相应地重命名数据文件和索引文件。

  • 如果使用 ALTER TABLE ... RENAME RENAME TABLE 将表移动到另一个数据库,则表将移动到另一个数据库目录。 如果表名更改,则新数据库目录中的符号链接将重命名为新名称,并相应地重命名数据文件和索引文件。

  • 如果您没有使用符号链接,请使用 选项 启动 mysqld --skip-symbolic-links 以确保没有人可以使用 mysqld 删除或重命名数据目录之外的文件。

不支持这些表符号链接操作:

  • ALTER TABLE 忽略 DATA DIRECTORY INDEX DIRECTORY 表选项。

8.12.2.3在Windows上使用数据库的符号链接

在Windows上,符号链接可用于数据库目录。 这使您可以通过设置指向它的符号链接将数据库目录放在不同的位置(例如,在不同的磁盘上)。 在Windows上使用数据库符号链接类似于在Unix上使用数据库符号链接,尽管设置链接的过程有所不同。

假设你要放置数据库目录中指定的数据库 mydb D:\data\mydb 为此,请在MySQL数据目录中创建指向的符号链接 D:\data\mydb 但是,在创建符号链接之前,请确保 D:\data\mydb 在必要时通过创建目录来存在 目录。 如果已 mydb 在数据目录中 指定了数据库 目录,请将其移至 D:\data 否则,符号链接将无效。 为避免出现问题,请确保在移动数据库目录时服务器未运行。

在Windows上,您可以使用 mklink 命令 创建符号链接 此命令需要管理权限。

  1. 将位置更改为数据目录:

    C:\> cd \path\to\datadir
    
  2. 在数据目录中,创建一个 mydb 指向数据库目录位置的 符号链接

    C:\> mklink /d mydb D:\data\mydb
    

在此之后,将在中创建在数据库 mydb 中创建的 所有表 D:\data\mydb

8.12.3优化内存使用

8.12.3.1 MySQL如何使用内存

MySQL分配缓冲区和缓存以提高数据库操作的性能。 默认配置旨在允许MySQL服务器在具有大约512MB RAM的虚拟机上启动。 您可以通过增加某些缓存和缓冲区相关系统变量的值来提高MySQL性能。 您还可以修改默认配置以在内存有限的系统上运行MySQL。

以下列表描述了MySQL使用内存的一些方法。 适用时,引用相关的系统变量。 某些项目是存储引擎或特定功能。

  • 所述 InnoDB 缓冲器池是保持缓存的存储区 InnoDB 表,索引,及其它辅助缓冲器中的数据。 为了提高大容量读取操作的效率,缓冲池被分成 可以容纳多行的 页面 为了提高缓存管理的效率,缓冲池被实现为链接的页面列表; 使用 LRU 算法 的变体,很少使用的数据在缓存中老化 有关更多信息,请参见 第15.5.1节“缓冲池”

    缓冲池的大小对系统性能很重要:

  • 存储引擎接口使优化器能够提供有关用于扫描的记录缓冲区大小的信息,优化程序估计这些扫描将读取多行。 缓冲区大小可以根据估计的大小而变化。 InnoDB 使用这种可变大小的缓冲功能来利用行预取,并减少锁存和B树导航的开销。

  • 所有线程共享 MyISAM 密钥缓冲区。 key_buffer_size 系统变量决定其大小。

    对于 MyISAM 服务器打开的 每个 表,索引文件打开一次; 为访问该表的每个并发运行的线程打开一次数据文件。 对于每个并发线程, 分配 表结构,每列的列结构和大小的缓冲区 (其中 ,最大行长度,不计算 列)。 列需要五到八字节加上的长度 的数据。 存储引擎维护用于内部使用一个额外的行缓冲。 3 * N N BLOB BLOB BLOB MyISAM

  • 所述 myisam_use_mmap 系统变量可以被设置为1,使能对所有内存映射 MyISAM 表。

  • 如果内部内存临时表变得太大(使用 tmp_table_size max_heap_table_size 系统变量 确定 ),MySQL会自动将表从内存转换为磁盘格式。 从MySQL 8.0.16开始,磁盘上的临时表总是使用InnoDB存储引擎。 (以前,用于此目的的存储引擎由 internal_tmp_disk_storage_engine 系统变量 确定, 不再支持 系统变量。)您可以按 第8.4.4节“MySQL中的内部临时表使用”中 所述增加允许的临时表大小

    对于使用 MEMORY 显式创建的表 CREATE TABLE ,只有 max_heap_table_size 系统变量决定了表的增长程度,并且没有转换为磁盘格式。

  • MySQL性能模式 是在低级别监控MySQL服务器执行的功能。 性能模式以递增方式动态分配内存,将其内存使用量扩展到实际服务器负载,而不是在服务器启动期间分配所需的内存。 分配内存后,在重新启动服务器之前不会释放内存。 有关更多信息,请参见 第26.17节“性能模式内存分配模型”

  • 服务器用于管理客户端连接的每个线程都需要一些特定于线程的空间。 以下列表指出了这些以及哪些系统变量控制它们的大小:

    连接缓冲区和结果缓冲区的大小都等于 net_buffer_length 字节 大小 ,但 max_allowed_packet 根据需要 动态放大到 字节。 结果缓冲区 net_buffer_length 在每个SQL语句后 缩小为 字节。 在语句运行时,还会分配当前语句字符串的副本。

    每个连接线程使用内存来计算语句摘要。 服务器 max_digest_length 为每个会话 分配 字节。 请参见 第26.10节“性能模式语句摘要和采样”

  • 所有线程共享相同的基本内存。

  • 当不再需要线程时,分配给它的内存被释放并返回给系统,除非线程返回到线程缓存中。 在这种情况下,内存仍然是分配的。

  • 执行表的顺序扫描的每个请求都分配一个 读缓冲区 read_buffer_size 系统变量决定缓冲器大小。

  • 当以任意顺序读取行时(例如,在排序之后), 可以分配 随机读取缓冲器 以避免磁盘搜索。 read_rnd_buffer_size 系统变量决定缓冲器大小。

  • 所有连接都是一次执行,大多数连接都可以在不使用临时表的情况下完成。 大多数临时表都是基于内存的哈希表。 具有大行长度(计算为所有列长度的总和)或包含 BLOB 列的 临时表 存储在磁盘上。

  • 执行排序的大多数请求根据结果集大小分配排序缓冲区和零到两个临时文件。 请参见 第B.4.3.5节“MySQL存储临时文件的位置”

  • 几乎所有的解析和计算都是在线程本地和可重用的内存池中完成的。 小项目不需要内存开销,因此避免了正常的慢速内存分配和释放。 内存仅分配给意外大的字符串。

  • 对于具有 BLOB 列的 每个表, 动态放大缓冲区以读取更大的 BLOB 值。 如果扫描表,缓冲区会增大到 BLOB 最大值。

  • MySQL需要表缓存的内存和描述符。 所有正在使用的表的处理程序结构都保存在表缓存中,并作为 先进先出 (FIFO)进行管理。 所述 table_open_cache 系统变量定义初始表高速缓存大小; 请参见 第8.4.3.1节“MySQL如何打开和关闭表”

    MySQL还需要内存用于表定义缓存。 所述 table_definition_cache 系统变量定义的,可以存储在表中定义的高速缓存表的定义的数量。 如果使用大量表,则可以创建大型表定义高速缓存以加快表的打开速度。 与表缓存不同,表定义缓存占用的空间更少,不使用文件描述符。

  • 一个 FLUSH TABLES 语句或 中mysqladmin冲水表 命令关闭不在使用一次的所有表,并标记所有在用的表被关闭当前正在执行的线程结束时。 这有效地释放了大多数使用中的内存。 FLUSH TABLES 在所有表格关闭之前不会返回。

  • 服务器在内存中缓存信息的结果 GRANT CREATE USER CREATE SERVER ,和 INSTALL PLUGIN 语句。 该内存不能由相应的释放 REVOKE DROP USER DROP SERVER ,和 UNINSTALL PLUGIN 语句,所以执行导致缓存报表的多个实例的服务器上,将有内存使用的增加。 可以释放此缓存的内存 FLUSH PRIVILEGES

  • 在复制拓扑中,以下设置会影响内存使用情况,可以根据需要进行调整:

    • max_allowed_packet 复制主机上 系统变量限制主服务器发送到其从属服务器以进行处理的最大消息大小。 此设置默认为64M。

    • slave_pending_jobs_size_max 多线程从站上 系统变量设置可用于保存等待处理的消息的最大内存量。 此设置默认为128M。 内存仅在需要时分配,但如果复制拓扑有时处理大型事务,则可以使用它。 这是一个软限制,可以处理更大的交易。

    • rpl_read_size 复制主机或从机上 系统变量控制从二进制日志文件和中继日志文件中读取的最小数据量(以字节为单位)。 默认值为8192个字节。 为从二进制日志和中继日志文件读取的每个线程分配一个大小为此值的缓冲区,包括主服务器上的转储线程和从服务器上的协调器线程。

    • 所述 binlog_transaction_dependency_history_size 系统变量限制保持为一个内存历史行哈希的数量。

    • 所述 max_binlog_cache_size 系统变量指定由单个事务存储器使用的上限。

    • max_binlog_stmt_cache_size 系统变量指定由语句缓存内存使用的上限。

ps 和其他系统状态程序可能会报告 mysqld 使用大量内存。 这可能是由不同内存地址上的线程堆栈引起的。 例如,Solaris版本的 ps 将堆栈之间未使用的内存计为已用内存。 要验证这一点,请检查可用的交换 swap -s 我们 使用几个内存泄漏检测器(商业和开源) 测试 mysqld ,因此应该没有内存泄漏。

监控MySQL内存使用情况

以下示例演示如何使用 性能模式 sys模式 来监视MySQL内存使用情况。

默认情况下禁用大多数性能架构内存检测。 可以通过更新 ENABLED Performance Schema setup_instruments 来启用仪器 记忆仪器的名称形式为 ,其中 的值为 ,并且 是仪器的详细信息。 memory/code_area/instrument_name code_area sql innodb instrument_name

  1. 要查看可用的MySQL内存仪器,请查询Performance Schema setup_instruments 表。 以下查询返回所有代码区域的数百个内存工具。

    MySQL的> SELECT * FROM performance_schema.setup_instruments
           WHERE NAME LIKE '%memory%';

    您可以通过指定代码区域来缩小结果范围。 例如,您可以 InnoDB 通过指定 innodb 代码区域 将结果限制为 存储器械

    MySQL的> SELECT * FROM performance_schema.setup_instruments
           WHERE NAME LIKE '%memory/innodb%';
    + ------------------------------------------- + ----- ---- + ------- +
    | NAME | 启用| 定时|
    + ------------------------------------------- + ----- ---- + ------- +
    | 内存/ innodb /自适应哈希索引| 没有| 没有|
    | 记忆/ innodb / buf_buf_pool | 没有| 没有|
    | memory / innodb / dict_stats_bg_recalc_pool_t | 没有| 没有|
    | memory / innodb / dict_stats_index_map_t | 没有| 没有|
    | memory / innodb / dict_stats_n_diff_on_level | 没有| 没有|
    | 记忆/ innodb /其他| 没有| 没有|
    | memory / innodb / row_log_buf | 没有| 没有|
    | memory / innodb / row_merge_sort | 没有| 没有|
    | 记忆/ innodb / std | 没有| 没有|
    | memory / innodb / trx_sys_t :: rw_trx_ids | 没有| 没有|
    ...
    

    根据您的MySQL安装代码区域可能包括 performance_schema sql client innodb myisam csv memory blackhole archive partition ,和其他人。

  2. 要启用内存仪器, performance-schema-instrument 请在MySQL配置文件中 添加 规则。 例如,要启用所有内存仪器,请将此规则添加到配置文件并重新启动服务器:

    性能架构仪器=“存储器/%=计数”
    注意

    启动时启用内存仪器可确保计算启动时发生的内存分配。

    重新启动服务器后, ENABLED Performance Schema setup_instruments 应报告 YES 您启用的内存仪器。 对于内存仪器 TIMED setup_instruments 表中 列将 被忽略,因为内存操作没有定时。

    MySQL的> SELECT * FROM performance_schema.setup_instruments
           WHERE NAME LIKE '%memory/innodb%';
    + ------------------------------------------- + ----- ---- + ------- +
    | NAME | 启用| 定时|
    + ------------------------------------------- + ----- ---- + ------- +
    | 内存/ innodb /自适应哈希索引| 没有| 没有|
    | 记忆/ innodb / buf_buf_pool | 没有| 没有|
    | memory / innodb / dict_stats_bg_recalc_pool_t | 没有| 没有|
    | memory / innodb / dict_stats_index_map_t | 没有| 没有|
    | memory / innodb / dict_stats_n_diff_on_level | 没有| 没有|
    | 记忆/ innodb /其他| 没有| 没有|
    | memory / innodb / row_log_buf | 没有| 没有|
    | memory / innodb / row_merge_sort | 没有| 没有|
    | 记忆/ innodb / std | 没有| 没有|
    | memory / innodb / trx_sys_t :: rw_trx_ids | 没有| 没有|
    ...
    
  3. 查询内存仪器数据。 在此示例中,在Performance Schema memory_summary_global_by_event_name 表中 查询内存仪器数据,该 表汇总了数据 EVENT_NAME EVENT_NAME 是仪器的名称。

    以下查询返回 InnoDB 缓冲池的 内存数据 有关列说明,请参见 第26.12.16.10节“内存汇总表”

    MySQL的> SELECT * FROM performance_schema.memory_summary_global_by_event_name
           WHERE EVENT_NAME LIKE 'memory/innodb/buf_buf_pool'\G
                      EVENT_NAME:memory / innodb / buf_buf_pool
                     COUNT_ALLOC:1
                      COUNT_FREE:0
       SUM_NUMBER_OF_BYTES_ALLOC:137428992
        SUM_NUMBER_OF_BYTES_FREE:0
                  LOW_COUNT_USED:0
              CURRENT_COUNT_USED:1
                 HIGH_COUNT_USED:1
        LOW_NUMBER_OF_BYTES_USED:0
    CURRENT_NUMBER_OF_BYTES_USED:137428992
       HIGH_NUMBER_OF_BYTES_USED:137428992
    

    可以使用 sys 模式 memory_global_by_current_bytes 查询相同的基础数据, 模式 表显示服务器内的当前内存使用情况,按分配类型细分。

    MySQL的> SELECT * FROM sys.memory_global_by_current_bytes
           WHERE event_name LIKE 'memory/innodb/buf_buf_pool'\G
    *************************** 1。排******************** *******
           event_name:memory / innodb / buf_buf_pool
        current_count:1
        current_alloc:131.06 MiB
    current_avg_alloc:131.06 MiB
           high_count:1
           high_alloc:131.06 MiB
       high_avg_alloc:131.06 MiB
    

    sys 架构查询 current_alloc 按代码区域 聚合当前分配的内存( ):

    MySQL的> SELECT SUBSTRING_INDEX(event_name,'/',2) AS
           code_area, sys.format_bytes(SUM(current_alloc))
           AS current_alloc
           FROM sys.x$memory_global_by_current_bytes
           GROUP BY SUBSTRING_INDEX(event_name,'/',2)
           ORDER BY SUM(current_alloc) DESC;
    + --------------------------- + -------- +
    | code_area | current_alloc |
    + --------------------------- + -------- +
    | 记忆/ innodb | 843.24 MiB |
    | memory / performance_schema | 81.29 MiB |
    | 记忆/ mysys | 8.20 MiB |
    | memory / sql | 2.47 MiB |
    | 记忆/记忆| 174.01 KiB |
    | 记忆/ myisam | 46.53 KiB |
    | 记忆/黑洞| 512字节|
    | 内存/联合| 512字节|
    | 记忆/ csv | 512字节|
    | 记忆/ vio | 496字节|
    + --------------------------- + -------- +
    

    有关 sys 模式的 更多信息 ,请参见 第27章, MySQL sys Schema

8.12.3.2启用大页面支持

某些硬件/操作系统体系结构支持大于默认值的内存页(通常为4KB)。 此支持的实际实现取决于底层硬件和操作系统。 执行大量内存访问的应用程序可能会因为减少了转换后备缓冲区(TLB)丢失而使用大页面来提高性能。

在MySQL中,InnoDB可以使用大页面来为其缓冲池和额外的内存池分配内存。

在MySQL中标准使用大页面试图使用支持的最大大小,最多4MB。 在Solaris下, 超大页面 功能允许使用高达256MB的页面。 此功能适用于最新的SPARC平台。 可以使用 --super-large-pages --skip-super-large-pages 选项 启用或禁用它

MySQL还支持Linux实现大页面支持(在Linux中称为HugeTLB)。

在Linux上可以使用大页面之前,必须启用内核来支持它们,并且必须配置HugeTLB内存池。 作为参考,HugeTBL API记录在 Documentation/vm/hugetlbpage.txt Linux源文件中。

最近一些系统(如Red Hat Enterprise Linux)的内核似乎默认启用了大页面功能。 要检查内核是否为真,请使用以下命令查找包含 huge ”的 输出行

外壳> cat /proc/meminfo | grep -i huge
HugePages_Total:0
HugePages_Free:0
HugePages_Rsvd:0
HugePages_Surp:0
Hugepagesize:4096 kB

非空命令输出表示存在大页面支持,但零值表示没有配置使用的页面。

如果需要重新配置内核以支持大页面,请查阅该 hugetlbpage.txt 文件以获取相关说明。

假设您的Linux内核启用了大页面支持,请使用以下命令将其配置为供MySQL使用。 通常,将它们放在 rc 系统引导序列期间执行 文件或等效的启动文件中,以便每次系统启动时执行命令。 在MySQL服务器启动之前,命令应该在引导序列的早期执行。 请务必根据您的系统更改分配编号和组编号。

#设置要使用的页数。
#每个页面通常为2MB,因此值为20 = 40MB。
#这个命令实际上分配了内存,所以这么多
#memory必须可用。
echo 20> / proc / sys / vm / nr_hugepages

#设置允许访问的组号
#memory(在这种情况下为102)。mysql用户必须是
#该组成员。
echo 102> / proc / sys / vm / hugetlb_shm_group

#增加每个段允许的shmem数量
#(在这种情况下为12G)。
echo 1560281088> / proc / sys / kernel / shmmax

#增加共享内存总量。价值
#是页数。4KB /页,4194304 = 16GB。
echo 4194304> / proc / sys / kernel / shmall

对于MySQL的使用,通常希望值 shmmax 接近于的值 shmall

要验证大页面配置,请 /proc/meminfo 按照前面的说明再次 检查 现在您应该看到一些非零值:

外壳> cat /proc/meminfo | grep -i huge
HugePages_Total:20
HugePages_Free:20
HugePages_Rsvd:0
HugePages_Surp:0
Hugepagesize:4096 kB

使用它的最后一步 hugetlb_shm_group 是为 mysql 用户提供 memlock限制 无限 值。 这可以通过编辑 /etc/security/limits.conf 或将以下命令添加到 mysqld_safe 脚本来完成:

ulimit -l无限制

ulimit 命令 添加 mysqld_safe 会导致 root 用户 unlimited 在切换到 mysql 用户 之前 将memlock限制设置为 (这假设 mysqld_safe 是由 root 。) 启动的 。)

默认情况下禁用MySQL中的大页面支持。 要启用它,请使用该 --large-pages 选项 启动服务器 例如,您可以在服务器 my.cnf 文件中 使用以下行

的[mysqld]
大型网页

使用此选项,会 InnoDB 自动为其缓冲池和附加内存池使用大页面。 如果 InnoDB 无法执行此操作,则会回退到使用传统内存并向错误日志写入警告: 警告:使用常规内存池

要验证是否正在使用大页面,请 /proc/meminfo 再次 检查

外壳> cat /proc/meminfo | grep -i huge
HugePages_Total:20
HugePages_Free:20
HugePages_Rsvd:2
HugePages_Surp:0
Hugepagesize:4096 kB

8.12.4优化网络使用

8.12.4.1 MySQL如何处理客户端连接

本节介绍MySQL服务器如何管理客户端连接的各个方面。

网络接口和连接管理器线程

服务器能够侦听多个网络接口上的客户端连接。 连接管理器线程处理服务器侦听的网络接口上的客户端连接请求:

  • 在所有平台上,一个管理器线程处理TCP / IP连接请求。

  • 在Unix上,同一个管理器线程也处理Unix套接字文件连接请求。

  • 在Windows上,管理器线程处理共享内存连接请求,另一个处理命名管道连接请求。

  • 在所有平台上,可以启用其他网络接口以接受管理TCP / IP连接请求。 此接口可以使用处理 普通 TCP / IP请求 的管理器线程 或单独的线程。

服务器不会创建线程来处理它不听的接口。 例如,不支持启用命名管道连接的Windows服务器不会创建处理它们的线程。

客户端连接线程管理

连接管理器线程将每个客户端连接与专用于它的线程相关联,该线程处理该连接的身份验证和请求处理。 管理器线程在必要时创建一个新线程,但首先通过查询线程缓存来查看它是否包含可用于连接的线程,从而避免这样做。 当连接结束时,如果缓存未满,则将其线程返回到线程缓存。

在此连接线程模型中,存在与当前连接的客户端一样多的线程,当服务器工作负载必须扩展以处理大量连接时,这具有一些缺点。 例如,线程创建和处理变得昂贵。 此外,每个线程都需要服务器和内核资源,例如堆栈空间。 为了容纳大量的并发连接,每个线程的堆栈大小必须保持很小,从而导致它太小或服务器消耗大量内存。 也可能发生其他资源的耗尽,并且调度开销可能变得很大。

MySQL企业版包含一个线程池插件,它提供了另一种线程处理模型,旨在减少开销并提高性能。 它实现了一个线程池,通过有效管理大量客户端连接的语句执行线程来提高服务器性能。 请参见 第5.6.3节“MySQL Enterprise Thread Pool”

为了控制和监视服务器如何管理处理客户端连接的线程,几个系统和状态变量是相关的。 (请参见 第5.1.8节“服务器系统变量” 第5.1.10节“服务器状态变量” 。)

所述 thread_cache_size 系统变量确定线程缓存大小。 默认情况下,服务器在启动时自动调整值,但可以显式设置以覆盖此默认值。 值为0将禁用缓存,这会导致为每个新连接设置一个线程,并在连接终止时将其丢弃。 要启用 N 非活动连接线程, thread_cache_size N 在服务器启动时或运行时设置。 当与之关联的客户端连接终止时,连接线程将变为非活动状态。

要监视缓存中的线程数以及由于无法从缓存中获取线程而创建了多少线程,请检查 Threads_cached Threads_created 状态变量。

当线程堆栈太小时,这限制了服务器可以处理的SQL语句的复杂性,存储过程的递归深度以及其他消耗内存的操作。 N 为每个线程 设置堆栈大小的 字节,请使用启动服务器 thread_stack 设置为 N 服务器启动。

连接卷管理

要控制服务器允许同时连接的最大客户端数,请 max_connections 在服务器启动时或运行时 设置 系统变量。 max_connections 如果更多客户端尝试同时连接,则 可能需要增加 服务器配置为处理(请参见 第B.4.2.6节“连接太多” )。

mysqld 实际上允许 max_connections + 1个客户端连接。 额外连接保留供具有 CONNECTION_ADMIN SUPER 特权的 帐户使用 通过向管理员而不是普通用户(不应该使用它)授予权限,管理员可以连接到服务器并用于 SHOW PROCESSLIST 诊断问题,即使连接了最大数量的非特权客户端也是如此。 请参见 第13.7.6.29节“SHOW PROCESSLIST语法” (服务器还允许在专用接口上进行管理连接。请参阅 管理连接管理 。)

如果服务器因为 max_connections 达到限制而 拒绝连接 ,则会递增 Connection_errors_max_connections 状态变量。

MySQL支持的最大连接数(即 max_connections 可以设置 的最大值 )取决于几个因素:

  • 给定平台上的线程库的质量。

  • 可用的RAM量。

  • RAM的数量用于每个连接。

  • 每个连接的工作量。

  • 期望的响应时间。

  • 可用的文件描述符数。

Linux或Solaris应该能够支持至少500到1000个并发连接以及多达10,000个连接,如果你有许多GB的可用RAM,并且每个连接的工作负载很低或响应时间目标不高。

增加该 max_connections 值会增加 mysqld 所需的 文件描述符数 如果所需的描述符数量不可用,则服务器会减小其值 max_connections 有关文件描述符限制的注释,请参见 第8.4.3.1节“MySQL如何打开和关闭表”

open_files_limit 可能需要 增加 系统变量,这可能还需要提高操作系统对MySQL可以使用的文件描述符数量的限制。 请参阅操作系统文档以确定是否可以增加限制以及如何增加限制。 另请参见 第B.4.2.17节“找不到文件和类似错误”

管理连接管理

从MySQL 8.0.14开始,服务器允许专门为管理连接配置TCP / IP端口。 这提供了用于普通连接的网络接口上允许的单个管理连接的替代方法,即使 max_connections 已建立 连接 (请参阅 连接卷管理 )。

管理网络接口具有以下特征:

  • 仅当 admin_address 在启动时设置系统变量以指示管理接口的IP地址时, 该接口才可用 如果未 admin_address 指定 任何 值,则服务器不维护管理界面。

  • 所述 admin_port 系统变量指定接口的TCP / IP端口号(默认33062)。

  • 管理连接的数量没有限制。

  • 只有拥有该 SERVICE_CONNECTION_ADMIN 权限的 用户才允许连接

create_admin_listener_thread 系统变量使DBA在启动时选择使用用于普通的连接监听线程的管理接口是否实现,还是有它自己的独立的线程。 默认设置是使用用于普通连接的侦听器线程来实现管理接口。

8.12.4.2 DNS查找优化和主机缓存

MySQL服务器在内存中维护一个主机缓存,其中包含有关客户端的信息:IP地址,主机名和错误信息。 Performance Schema host_cache 表公开了主机缓存的内容,以便可以使用 SELECT 语句 对其进行检查 这可以帮助您诊断连接问题的原因。 请参见 第26.12.17.1节“host_cache表”

注意

服务器仅将主机缓存用于非本地TCP连接。 它不使用缓存来建立使用环回接口地址建立的TCP连接(例如, 127.0.0.1 ::1 ),或者使用Unix套接字文件,命名管道或共享内存建立的连接。

主机缓存操作

服务器将主机缓存用于以下目的:

  • 通过缓存IP到主机名称查找的结果,服务器可以避免为每个客户端连接执行域名系统(DNS)查找。 相反,对于给定的主机,它只需要为该主机的第一个连接执行查找。

  • 缓存包含有关连接过程中发生的错误的信息。 一些错误被认为是 阻塞”。 如果在没有成功连接的情况下从给定主机连续发生过多这些,则服务器会阻止来自该主机的进一步连接。 max_connect_errors 系统变量决定连续误差的允许数量发生阻塞(参见前 节B.4.2.5,“主机‘的host_name’被阻塞” )。

对于每个新客户端连接,服务器使用客户端IP地址检查客户端主机名是否在主机缓存中。 如果是,则服务器拒绝或继续处理连接请求,具体取决于主机是否被阻止。 如果主机不在缓存中,则服务器会尝试解析主机名。 首先,它将IP地址解析为主机名,并将该主机名解析回IP地址。 然后,它将结果与原始IP地址进行比较,以确保它们相同。 服务器将有关此操作结果的信息存储在主机缓存中。 如果缓存已满,则丢弃最近最少使用的条目。

服务器处理主机缓存中的条目,如下所示:

  1. 当第一个TCP客户端连接从给定IP地址到达服务器时,将创建一个新的缓存条目以记录客户端IP,主机名和客户端查找验证标志。 最初,主机名设置为 NULL ,标志为false。 此条目还用于来自同一发起IP的后续客户端TCP连接。

  2. 如果客户端IP条目的验证标志为false,则服务器尝试IP到主机的名称到IP DNS解析。 如果成功,则使用已解析的主机名更新主机名,并将验证标志设置为true。 如果解决方案不成功,则采取的措施取决于错误是永久性还是暂时性。 对于永久性故障,主机名仍然存在 NULL ,验证标志设置为true。 对于瞬态故障,主机名和验证标志保持不变。 (在这种情况下,下次客户端从此IP连接时会发生另一次DNS解析尝试。)

  3. 如果在处理来自给定IP地址的传入客户端连接时发生错误,则服务器会更新该IP条目中的相应错误计数器。 有关记录的错误的说明,请参见 第26.12.17.1节“host_cache表”

服务器使用 gethostbyaddr() gethostbyname() 系统调用 执行主机名解析

要取消阻止被阻止的主机,请通过执行 语句,截断Performance Schema FLUSH HOSTS TRUNCATE TABLE 语句 host_cache mysqladmin flush-hosts 命令 来刷新主机缓存 FLUSH HOSTS mysqladmin flush-hosts 需要 RELOAD 特权。 TRUNCATE TABLE 需要 DROP 权限 host_cache

如果自上次连接尝试来自被阻止主机的活动后发生其他主机的活动,则即使没有刷新主机缓存,也可以解除阻塞主机的阻塞。 发生这种情况的原因是,当连接从不在缓存中的客户端IP到达时,如果缓存已满,则服务器会丢弃最近最少使用的缓存条目以为新条目腾出空间。 如果丢弃的条目用于被阻止的主机,则该主机将被解除阻止。

某些连接错误与TCP连接无关,在连接过程中很早就会发生(甚至在知道IP地址之前),或者不是特定于任何特定IP地址(例如内存不足情况)。 有关这些错误的信息,请检查 状态变量(请参见 第5.1.10节“服务器状态变量” )。 Connection_errors_xxx

主机缓存配置

默认情况下启用主机缓存。 host_cache_size 系统变量控制它的大小,以及绩效模式的大小 host_cache 暴露缓存目录。 可以在服务器启动时设置缓存大小,并在运行时更改。 例如,要在启动时将大小设置为100,请将这些行放在服务器 my.cnf 文件中:

的[mysqld]
host_cache_size = 200

要在运行时将大小更改为300,请执行以下操作:

SET GLOBAL host_cache_size = 300;

host_cache_size 在服务器启动或运行时 设置 为0会禁用主机缓存。 禁用缓存后,服务器会在每次客户端连接时执行DNS查找。

在运行时更改缓存大小会导致隐式 FLUSH HOSTS 操作清除主机缓存,从而截断 host_cache 表并取消阻止任何被阻止的主机。

使用该 --skip-host-cache 选项类似于将 host_cache_size 系统变量 设置 为0,但 host_cache_size 更灵活,因为它还可用于在运行时调整大小,启用和禁用主机缓存,而不仅仅是在服务器启动时。 启动服务器 --skip-host-cache 并不会阻止更改值 host_cache_size ,但这些更改不起作用,即使 host_cache_size 在运行时将大于0设置 为高速缓存,也不会重新启用高速缓存

要禁用DNS主机名查找,请使用该 --skip-name-resolve 选项 启动服务器 在这种情况下,服务器仅使用IP地址而不使用主机名来匹配连接主机与MySQL授权表中的行。 只能使用使用IP地址在这些表中指定的帐户。 (如果不存在指定客户端IP地址的帐户,则客户端可能无法连接。)

如果您的DNS和许多主机速度非常慢,您可以通过使用 --skip-name-resolve 或通过增加 DNS查找 host_cache_size 来使主机缓存更大 来提高性能

要完全禁止TCP / IP连接,请使用该 --skip-networking 选项 启动服务器

8.12.5资源组

MySQL支持资源组的创建和管理,并允许将服务器内运行的线程分配给特定组,以便线程根据组可用的资源执行。 组属性可以控制其资源,以启用或限制组中线程的资源消耗。 DBA可以根据不同的工作负载修改这些属性。

目前,CPU时间是可管理的资源,由 虚拟CPU 的概念表示 为包括CPU核心,超线程,硬件线程等的术语。 服务器在启动时确定可用的虚拟CPU数量,具有适当权限的数据库管理员可以将这些CPU与资源组关联,并将线程分配给组。

例如,要管理不需要以高优先级执行的批处理作业的执行,DBA可以创建 Batch 资源组,并根据服务器的繁忙程度向上或向下调整其优先级。 (也许分配给该组的批处理作业应该在白天以较低的优先级运行,在夜间以较高的优先级运行。)DBA还可以调整该组可用的CPU集。 可以启用或禁用组来控制线程是否可分配给它们。

以下部分描述了MySQL中资源组使用的各个方面:

重要

在某些平台或MySQL服务器配置上,资源组不可用或具有限制。 特别是,对于某些安装方法,Linux系统可能需要手动步骤。 有关详细信息,请参阅 资源组限制

资源组组件

这些功能为MySQL中的资源组管理提供了SQL接口:

  • SQL语句支持创建,更改和删除资源组,并允许将线程分配给资源组。 优化程序提示可以将单个语句分配给资源组。

  • 资源组权限可控制哪些用户可以执行资源组操作。

  • INFORMATION_SCHEMA.RESOURCE_GROUPS 表公开有关资源组定义的信息,而Performance Schema threads 表显示每个线程的资源组分配。

  • 状态变量为每个管理SQL语句提供执行计数。

资源组属性

资源组具有定义组的属性。 可以在创建组时设置所有属性。 某些属性在创建时被修复; 其他人可以在此后的任何时间修改。

这些属性在资源组创建时定义,无法修改:

  • 每个组都有一个名字。 资源组名称是表和列名称之类的标识符,不需要在SQL语句中引用,除非它们包含特殊字符或保留字。 组名称不区分大小写,最长可达64个字符。

  • 每个组都有一个类型,或者是 SYSTEM 或者 USER 资源组类型会影响可分配给组的优先级值范围,如稍后所述。 此属性与允许的优先级的差异一起使得能够识别系统线程,以便保护它们免于针对用户线程争用CPU资源。

    系统和用户线程对应于Performance Schema threads 表中 列出的后台和前台线程

这些属性在资源组创建时定义,并且可以在以后的任何时间进行修改:

  • CPU亲缘关系是资源组可以使用的一组虚拟CPU。 亲和关系可以是可用CPU的任何非空子集。 如果组没有亲缘关系,则可以使用所有可用的CPU。

  • 线程优先级是分配给资源组的线程的执行优先级。 优先级值范围从-20(最高优先级)到19(最低优先级)。 系统组和用户组的默认优先级均为0。

    系统组的优先级高于用户组,确保用户线程的优先级不会高于系统线程:

    • 对于系统资源组,允许的优先级范围是-20到0。

    • 对于用户资源组,允许的优先级范围是0到19。

  • 可以启用或禁用每个组,从而使管理员可以控制线程分配。 线程只能分配给已启用的组。

资源组管理

默认情况下,有一个系统组和一个用户组 ,分别 名为 SYS_default USR_default 无法删除这些默认组,并且无法修改其属性。 每个默认组都没有CPU关联,优先级为0。

新创建的系统和用户线程分别分配给 SYS_default USR_default 组。

对于用户定义的资源组,将在创建组时分配所有属性。 创建组后,可以修改其属性,但名称和类型属性除外。

要创建和管理用户定义的资源组,请使用以下SQL语句:

这些陈述需要 RESOURCE_GROUP_ADMIN 特权。

要管理资源组分配,请使用以下功能:

这些操作需要 RESOURCE_GROUP_ADMIN RESOURCE_GROUP_USER 特权。

资源组定义存储在 resource_groups 数据字典表中,以便组在服务器重新启动时保持不变。 因为 resource_groups 它是数据字典的一部分,所以用户无法直接访问它。 使用 INFORMATION_SCHEMA.RESOURCE_GROUPS 可以获得资源组信息,该 表作为数据字典表上的视图实现。 请参见 第25.22节“INFORMATION_SCHEMA RESOURCE_GROUPS表”

最初,该 RESOURCE_GROUPS 表具有描述默认组的这些行:

MySQL的> SELECT * FROM INFORMATION_SCHEMA.RESOURCE_GROUPS\G
*************************** 1。排******************** *******
   RESOURCE_GROUP_NAME:USR_default
   RESOURCE_GROUP_TYPE:USER
RESOURCE_GROUP_ENABLED:1
              VCPU_IDS:0-3
       THREAD_PRIORITY:0
*************************** 2.排******************** *******
   RESOURCE_GROUP_NAME:SYS_default
   RESOURCE_GROUP_TYPE:SYSTEM
RESOURCE_GROUP_ENABLED:1
              VCPU_IDS:0-3
       THREAD_PRIORITY:0

THREAD_PRIORITY 值是0,表示默认的优先级。 VCPU_IDS 值示出了包括所有可用CPU的范围内。 对于默认组,显示的值会根据运行MySQL服务器的系统而有所不同。

前面的讨论提到了一个涉及一个资源组的场景,该资源组命名 Batch 为管理不需要以高优先级执行的批处理作业的执行。 要创建这样的组,请使用与此类似的语句:

CREATE RESOURCE GROUP批处理
  TYPE = USER
  VCPU = 2-3  - 假设系统至少有4个CPU
  THREAD_PRIORITY = 10;

要验证资源组是否按预期创建,请检查 RESOURCE_GROUPS 表:

MySQL的> SELECT * FROM INFORMATION_SCHEMA.RESOURCE_GROUPS
       WHERE RESOURCE_GROUP_NAME = 'Batch'\G
*************************** 1。排******************** *******
   RESOURCE_GROUP_NAME:批处理
   RESOURCE_GROUP_TYPE:USER
RESOURCE_GROUP_ENABLED:1
              VCPU_IDS:2-3
       THREAD_PRIORITY:10

如果 THREAD_PRIORITY 值为0而不是10,请检查您的平台或系统配置是否限制资源组功能; 请参阅 资源组限制

要将线程分配给 Batch 组,请执行以下操作:

SET RESOURCE GROUP Batch FOR thread_id;

此后,命名线程中的语句将使用 Batch 组资源 执行

如果会话自己的当前线程应该在 Batch 组中,请在会话中执行以下语句:

SET RESOURCE GROUP Batch;

此后,会话中的语句将使用 Batch 组资源 执行

要使用 Batch 执行单个语句 ,请使用 RESOURCE_GROUP 优化程序提示:

INSERT / * + RESOURCE_GROUP(批量)* / INTO t2 VALUES(2);

分配给 Batch 组的 线程 使用其资源执行,可以根据需要进行修改:

  • 对于系统高负载的时间,减少分配给该组的CPU数量,降低其优先级,或者(如图所示):

    ALTER RESOURCE GROUP批处理
      VCPU = 3
      THREAD_PRIORITY = 19;
    
  • 对于系统负载较轻的时间,增加分配给组的CPU数量,提高其优先级,或者(如图所示):

    ALTER RESOURCE GROUP批处理
      VCPU = 0-3
      THREAD_PRIORITY = 0;
    

资源组复制

资源组管理是发生它的服务器的本地管理。 资源组SQL语句和对 resource_groups 数据字典表的 修改 不会写入二进制日志,也不会被复制。

资源组限制

在某些平台或MySQL服务器配置上,资源组不可用或具有限制:

  • 如果安装了线程池插件,则资源组不可用。

  • 资源组在macOS上不可用,它不提供用于将CPU绑定到线程的API。

  • 在FreeBSD和Solaris上,忽略资源组线程优先级。 (实际上,所有线程都以优先级0运行。)尝试更改优先级会导致警告:

    MySQL的> ALTER RESOURCE GROUP abc THREAD_PRIORITY = 10;
    查询正常,0行受影响,1警告(0.18秒)
    
    MySQL的> SHOW WARNINGS;
    + --------- + ------ + -------------------------------- ----------------------------- +
    | 等级| 代码| 消息|
    + --------- + ------ + -------------------------------- ----------------------------- +
    | 警告| 4560 | 忽略属性thread_priority(使用默认值)。|
    + --------- + ------ + -------------------------------- ----------------------------- +
    
  • 在Linux上,除非 CAP_SYS_NICE 设置 功能, 否则将忽略资源组线程优先级 授予 CAP_SYS_NICE 流程功能可以实现一系列权限; 有关完整列表, 请参阅 http://man7.org/linux/man-pages/man7/capabilities.7.html 启用此功能时请小心。

    在使用systemd和内核支持Ambient Capabilities(Linux 4.3或更高版本)的Linux平台上,启用 CAP_SYS_NICE 功能 的推荐方法 是修改MySQL服务文件并保持 mysqld 二进制 文件不被 修改。 要调整MySQL的服务文件,请使用以下过程:

    1. 为您的平台运行适当的命令:

      • Oracle Linux,Red Hat和Fedora系统:

        外壳> sudo systemctl edit mysqld
        
      • SUSE,Ubuntu和Debian系统:

        外壳> sudo systemctl edit mysql
        
    2. 使用编辑器,将以下文本添加到服务文件中:

      [服务]
      AmbientCapabilities = CAP_SYS_NICE
      
    3. 重启MySQL服务。

    如果您无法启用 CAP_SYS_NICE 刚才描述 功能,可以使用 setcap 命令 手动设置 ,指定 mysqld 可执行文件 的路径名 (这需要 sudo 访问权限)。 您可以使用 getcap 检查功能 例如:

    shell> 
    shell> = cap_sys_nice + ep
    sudo setcap cap_sys_nice+ep /path/to/mysqldgetcap /path/to/mysqld
    /path/to/mysqld

    作为安全措施,将 mysqld 二进制文件的 执行限制为 root 具有 mysql 组成员身份 用户和用户

    shell> 
    shell>sudo chown root:mysql /path/to/mysqldsudo chmod 0750 /path/to/mysqld
    
    重要

    如果需要手动使用 setcap ,则必须在每次重新安装后执行。

  • 在Windows上,线程以五个线程优先级之一运行。 资源组线程优先级范围-20到19映射到这些级别,如下表所示。

    表8.3 Windows上的资源组线程优先级

    优先范围 Windows优先级
    -20到-10 THREAD_PRIORITY_HIGHEST
    -9到-1 THREAD_PRIORITY_ABOVE_NORMAL
    0 THREAD_PRIORITY_NORMAL
    1到10 THREAD_PRIORITY_BELOW_NORMAL
    11至19 THREAD_PRIORITY_LOWEST

8.13衡量绩效(基准)

要衡量绩效,请考虑以下因素:

  • 无论您是在安静系统上测量单个操作的速度,还是 在一段时间内 如何操作一组操作( 工作负载 )。 通过简单的测试,您通常可以测试更改一个方面(配置设置,表上的索引集,查询中的SQL子句)如何影响性能。 基准测试通常是长时间运行和精心设计的性能测试,其结果可能决定高级选择,例如硬件和存储配置,或者升级到新MySQL版本的时间。

  • 对于基准测试,有时您必须模拟繁重的数据库工作负载才能获得准确的图像。

  • 表现可能会因许多不同的因素而有所不同,因为几个百分点的差异可能不是决定性的胜利。 在不同的环境中进行测试时,结果可能会发生相反的变化。

  • 某些MySQL功能根据工作负载提供帮助或无法帮助提高性能。 为了完整性,请始终在打开和关闭这些功能的情况下测试性能。 尝试与每个工作负载最重要的特点是 适应性的散列索引 InnoDB 表。

本节从单个开发人员可以执行的简单和直接测量技术发展到需要额外专业知识来执行和解释结果的更复杂测量技术。

8.13.1测量表达式和函数的速度

要测量特定MySQL表达式或函数的速度,请 BENCHMARK() 使用 mysql 客户端程序 调用该 函数 它的语法是 返回值始终为零,但 mysql 打印一行显示语句执行的时间。 例如: BENCHMARK(loop_count,expr)

MySQL的> SELECT BENCHMARK(1000000,1+1);
+ ------------------------ +
| 基准(1000000,1 + 1)|
+ ------------------------ +
| 0 |
+ ------------------------ +
1排(0.32秒)

该结果在Pentium II 400MHz系统上获得。 它表明MySQL可以在0.32秒内在该系统上执行1,000,000个简单的加法表达式。

内置的MySQL函数通常是高度优化的,但可能有一些例外。 BENCHMARK() 是一个很好的工具,用于查明某些功能是否是您的查询的问题。

8.13.2使用您自己的基准

对您的应用程序和数据库进行基准测试,以找出瓶颈所在。 在修复一个瓶颈(或者用 虚拟 模块 替换它 )之后,您可以继续识别下一个瓶颈。 即使您的应用程序的整体性能目前是可以接受的,您至少应该为每个瓶颈做一个计划,并决定如果有一天您真的需要额外的性能,如何解决它。

免费的基准测试套件是开源数据库基准测试,可从 http://osdb.sourceforge.net/获得

仅在系统负载很重时才会出现问题。 我们有许多客户在生产(已测试)系统并遇到负载问题时与我们联系。 在大多数情况下,性能问题最终是由于基本数据库设计问题(例如,高负载下的表扫描不好)或操作系统或库的问题。 大多数情况下,如果系统尚未投入生产,这些问题将更容易解决。

为了避免这样的问题,在最糟糕的负载下对整个应用程序进行基准测试:

这些程序或软件包可以使系统瘫痪,因此请务必仅在开发系统上使用它们。

8.13.3使用performance_schema测量性能

您可以查询 performance_schema 数据库中 的表, 以查看有关服务器及其运行的应用程序的性能特征的实时信息。 有关 详细信息 请参见 第26章, MySQL性能架构

8.14检查线程信息

当您尝试确定MySQL服务器正在执行的操作时,检查进程列表会很有帮助,该列表是当前在服务器中执行的线程集。 可从以下来源获取流程列表信息:

访问 threads 不需要互斥锁,对服务器性能的影响最小。 INFORMATION_SCHEMA.PROCESSLIST 并且 SHOW PROCESSLIST 因为它们需要互斥量 产生负面的性能影响。 threads 还显示有关后台线程的信息,有哪些 INFORMATION_SCHEMA.PROCESSLIST SHOW PROCESSLIST 有时没有。 这意味着 threads 可以用来监视其他线程信息源不能的活动。

您始终可以查看有关自己的线程的信息。 要查看有关正在为其他帐户执行的线程的信息,您必须具有该 PROCESS 权限。

每个进程列表条目包含几条信息:

  • Id 是与线程关联的客户端的连接标识符。

  • User Host 指明与该线程关联的帐户。

  • db 是线程的默认数据库,或者 NULL 如果未选择任何一个。

  • Command State 指出线程正在做什么。

    大多数州对应于非常快速的操作。 如果一个线程在给定状态下停留很多秒,则可能存在需要调查的问题。

  • Time 表示线程处于当前状态的时间。 在某些情况下,线程的当前时间概念可能会改变:线程可以改变时间 对于正在处理来自主站的事件的从站上运行的线程,线程时间设置为在事件中找到的时间,因此反映了主站而不是从站的当前时间。 SET TIMESTAMP = value

  • Info 包含线程正在执行的语句的文本,或者 NULL 它是否正在执行。 默认情况下,此值仅包含语句的前100个字符。 要查看完整的语句,请使用 SHOW FULL PROCESSLIST

  • sys 架构 processlist 视图,呈现从性能架构信息 threads 表中更方便的格式: 第27.4.3.22,“ProcessList中和X $ PROCESSLIST意见”

  • sys 架构 session 视图,其中介绍有关用户会话的信息(如 sys 架构 processlist 视图,但是过滤掉后台进程): 第27.4.3.33,“会议和X $会话视图”

以下部分列出了可能的 Command 值以及 State 按类别分组的值。 其中一些价值观的含义是不言而喻的。 对于其他人,提供了额外的描述。

8.14.1线程命令值

线程可以具有以下任何 Command 值:

  • Binlog Dump

    这是主服务器上的一个线程,用于将二进制日志内容发送到从属服务器。

  • Change user

    线程正在执行更改用户操作。

  • Close stmt

    线程正在关闭准备好的声明。

  • Connect

    复制从站连接到其主站。

  • Connect Out

    复制从站正在连接到其主站。

  • Create DB

    线程正在执行create-database操作。

  • Daemon

    此线程是服务器的内部线程,而不是为客户端连接提供服务的线程。

  • Debug

    该线程正在生成调试信息。

  • Delayed insert

    该线程是一个延迟插入处理程序。

  • Drop DB

    该线程正在执行drop-database操作。

  • Error

  • Execute

    线程正在执行预准备语句。

  • Fetch

    该线程正在从执行预准备语句中获取结果。

  • Field List

    该线程正在检索表列的信息。

  • Init DB

    该线程正在选择默认数据库。

  • Kill

    该线程正在杀死另一个线程。

  • Long Data

    线程正在检索执行预准备语句的结果中的长数据。

  • Ping

    该线程正在处理服务器ping请求。

  • Prepare

    该主题正在准备一份准备好的声明。

  • Processlist

    该线程正在生成有关服务器线程的信息。

  • Query

    线程正在执行一个语句。

  • Quit

    线程正在终止。

  • Refresh

    该线程正在刷新表,日志或缓存,或重置状态变量或复制服务器信息。

  • Register Slave

    线程正在注册从服务器。

  • Reset stmt

    该线程正在重置预准备语句。

  • Set option

    该线程正在设置或重置客户端语句执行选项。

  • Shutdown

    该线程正在关闭服务器。

  • Sleep

    线程正在等待客户端向其发送新语句。

  • Statistics

    该线程正在生成服务器状态信息。

  • Table Dump

    线程正在将表内容发送到从属服务器。

  • Time

    没用过。

8.14.2一般线程状态

以下列表描述了 State 与常规查询处理相关联的 线程 值,而不是更具体的活动(如复制)。 其中许多仅用于查找服务器中的错误。

  • After create

    当线程在创建表的函数末尾创建表(包括内部临时表)时,会发生这种情况。 即使由于某些错误而无法创建表,也会使用此状态。

  • Analyzing

    线程正在计算 MyISAM 表键分布(例如,for ANALYZE TABLE )。

  • checking permissions

    线程正在检查服务器是否具有执行语句所需的权限。

  • Checking table

    该线程正在执行表检查操作。

  • cleaning up

    该线程已经处理了一个命令,并准备释放内存并重置某些状态变量。

  • closing tables

    该线程正在将更改的表数据刷新到磁盘并关闭已使用的表。 这应该是一个快速的操作。 如果没有,请验证您没有完整磁盘并且磁盘使用不是很大。

  • converting HEAP to ondisk

    该线程正在将内部临时表从 MEMORY 表转换为磁盘表。

  • copy to tmp table

    线程正在处理一个 ALTER TABLE 语句。 在创建具有新结构的表但在将行复制到其中之前,将发生此状态。

    对于处于此状态的线程,可以使用性能模式来获取有关复制操作的进度。 请参见 第26.12.5节“性能模式阶段事件表”

  • Copying to group table

    如果语句具有不同的 条件 ORDER BY GROUP BY 标准,则按组对行进行排序并将其复制到临时表。

  • Copying to tmp table

    服务器正在复制到内存中的临时表。

  • altering table

    服务器正在执行就地 ALTER TABLE

  • Copying to tmp table on disk

    服务器正在复制到磁盘上的临时表。 临时结果集变得太大(请参见 第8.4.4节“MySQL中的内部临时表使用” )。 因此,线程正在将临时表从内存更改为基于磁盘的格式以节省内存。

  • Creating index

    线程正在处理 ALTER TABLE ... ENABLE KEYS 一个 MyISAM 表。

  • Creating sort index

    线程正在处理 SELECT 使用内部临时表解析 的线程

  • creating table

    线程正在创建一个表。 这包括创建临时表。

  • Creating tmp table

    该线程正在内存或磁盘上创建临时表。 如果表在内存中创建但稍后转换为磁盘表,则该操作期间的状态将为 Copying to tmp table on disk

  • committing alter table to storage engine

    服务器已完成就地 ALTER TABLE 并提交结果。

  • deleting from main table

    服务器正在执行多表删除的第一部分。 它仅从第一个表中删除,并保存用于从其他(引用)表中删除的列和偏移量。

  • deleting from reference tables

    服务器正在执行多表删除的第二部分,并从其他表中删除匹配的行。

  • discard_or_import_tablespace

    线程正在处理 ALTER TABLE ... DISCARD TABLESPACE ALTER TABLE ... IMPORT TABLESPACE 声明。

  • end

    这发生在结束,但的清理之前 ALTER TABLE CREATE VIEW DELETE INSERT SELECT ,或 UPDATE 语句。

  • executing

    该线程已开始执行语句。

  • Execution of init_command

    线程正在执行 init_command 系统变量 值中的语句

  • freeing items

    线程执行了一个命令。 这种状态通常紧随其后 cleaning up

  • FULLTEXT initialization

    服务器正准备执行自然语言全文搜索。

  • init

    出现这种情况的初始化之前 ALTER TABLE DELETE INSERT SELECT ,或 UPDATE 语句。 服务器在此状态下执行的操作包括刷新二进制日志和 InnoDB 日志。

    对于 end 州,可能会发生以下操作:

    • 将事件写入二进制日志

    • 释放内存缓冲区,包括blob

  • Killed

    有人 KILL 向线程 发送了一个 语句,它应该在下次检查kill标志时中止。 在MySQL的每个主循环中检查该标志,但在某些情况下,线程可能仍然需要很短的时间才能死掉。 如果线程被某个其他线程锁定,则一旦另一个线程释放其锁定,kill就会生效。

  • Locking system tables

    线程正在尝试锁定系统表(例如,时区或日志表)。

  • logging slow query

    该线程正在向慢查询日志写一条语句。

  • login

    连接线程的初始状态,直到客户端成功通过身份验证。

  • manage keys

    服务器正在启用或禁用表索引。

  • NULL

    该状态用于该 SHOW PROCESSLIST 状态。

  • Opening system tables

    线程正在尝试打开系统表(例如,时区或日志表)。

  • Opening tables

    线程正在尝试打开一个表。 这应该是非常快的程序,除非有什么东西阻止打开。 例如,一个 ALTER TABLE 或一个 LOCK TABLE 语句可以阻止在语句结束之前打开表。 还值得检查您的 table_open_cache 价值是否足够大。

    对于系统表,使用 Opening system tables 状态。

  • optimizing

    服务器正在对查询执行初始优化。

  • preparing

    在查询优化期间发生此状态。

  • Purging old relay logs

    该线程正在删除不需要的中继日志文件。

  • query end

    处理查询后但在 freeing items 状态 之前发生此 状态。

  • Receiving from client

    服务器正在从客户端读取数据包。

  • Removing duplicates

    该查询使用 SELECT DISTINCT 的方式是MySQL无法在早期阶段优化掉不同的操作。 因此,在将结果发送到客户端之前,MySQL需要额外的阶段来删除所有重复的行。

  • removing tmp table

    该线程在处理 SELECT 语句 后删除内部临时表 如果未创建临时表,则不使用此状态。

  • rename

    该线程正在重命名一个表。

  • rename result table

    线程正在处理一个 ALTER TABLE 语句,创建了新表,并重命名它以替换原始表。

  • Reopen tables

    该线程获得了表的锁定,但在获取锁定之后注意到基础表结构发生了变化。 它释放了锁,关闭了桌子,并试图重新打开它。

  • Repair by sorting

    修复代码使用排序来创建索引。

  • preparing for alter table

    服务器正准备执行就地 ALTER TABLE

  • Repair done

    该线程已完成对 MyISAM 的多线程修复

  • Repair with keycache

    修复代码通过密钥缓存逐个创建密钥。 这比慢得多 Repair by sorting

  • Rolling back

    该线程正在回滚一个事务。

  • Saving state

    对于 MyISAM 诸如修复或分析的表操作,线程将新表状态保存到 .MYI 文件头。 状态包括诸如行数, AUTO_INCREMENT 计数器和密钥分发之类的信息。

  • Searching rows for update

    该线程正在进行第一阶段以在更新之前查找所有匹配的行。 如果 UPDATE 要更改用于查找所涉及行的索引,则必须执行此操作。

  • Sending data

    线程正在读取和处理 SELECT 语句的 ,并将数据发送到客户端。 由于在此状态期间发生的操作往往会执行大量磁盘访问(读取),因此它通常是给定查询生命周期中运行时间最长的状态。

  • Sending to client

    服务器正在向客户端写入数据包。

  • setup

    线程正在开始一个 ALTER TABLE 操作。

  • Sorting for group

    线程正在进行排序以满足a GROUP BY

  • Sorting for order

    线程正在进行排序以满足 ORDER BY

  • Sorting index

    该线程正在对索引页面进行排序,以便在 MyISAM 表优化操作 期间进行更有效的 访

  • Sorting result

    对于 SELECT 声明,这类似于 Creating sort index 非临时表。

  • statistics

    服务器正在计算统计信息以开发查询执行计划。 如果线程长时间处于此状态,则服务器可能是磁盘绑定执行其他工作。

  • System lock

    该线程已调用 mysql_lock_tables() ,并且线程状态尚未更新。 这是一个非常普遍的状态,可能由于多种原因而发生。

    例如,线程将请求或正在等待表的内部或外部系统锁定。 InnoDB 在执行期间等待表级锁定 发生这种情况 LOCK TABLES 如果此状态是由外部锁的请求引起的,并且您没有使用多个 访问相同 表的 mysqld 服务器,则 MyISAM 可以使用该 --skip-external-locking 选项 禁用外部系统锁 但是,默认情况下禁用外部锁定,因此该选项很可能无效。 对于 SHOW PROFILE ,这个状态意味着线程正在请求锁定(不等待它)。

    对于系统表,使用 Locking system tables 状态。

  • update

    线程正准备开始更新表。

  • Updating

    线程正在搜索要更新的行并正在更新它们。

  • updating main table

    服务器正在执行多表更新的第一部分。 它仅更新第一个表,并保存用于更新其他(引用)表的列和偏移量。

  • updating reference tables

    服务器正在执行多表更新的第二部分,并更新其他表中的匹配行。

  • User lock

    该线程将要求或正在等待通过 GET_LOCK() 呼叫 请求的咨询锁 对于 SHOW PROFILE ,此状态表示线程正在请求锁定(不等待它)。

  • User sleep

    线程已经调用了一个 SLEEP() 调用。

  • Waiting for commit lock

    FLUSH TABLES WITH READ LOCK 正在等待提交锁定。

  • Waiting for global read lock

    FLUSH TABLES WITH READ LOCK 正在等待全局读锁定或 read_only 正在设置 全局 系统变量。

  • Waiting for tables

    线程得到一个通知,表明表的底层结构已经改变,它需要重新打开表以获得新结构。 但是,要重新打开表,它必须等到所有其他线程关闭了相关表。

    该通知发生如果另一个线程已使用 FLUSH TABLES 或有问题的表下面的语句之一: ,或 FLUSH TABLES tbl_name ALTER TABLE RENAME TABLE REPAIR TABLE ANALYZE TABLE OPTIMIZE TABLE

  • Waiting for table flush

    线程正在执行 FLUSH TABLES 并且正在等待所有线程关闭它们的表,或者线程得到一个表的基础结构已经更改的通知,并且它需要重新打开表以获取新结构。 但是,要重新打开表,它必须等到所有其他线程关闭了相关表。

    该通知发生如果另一个线程已使用 FLUSH TABLES 或有问题的表下面的语句之一: ,或 FLUSH TABLES tbl_name ALTER TABLE RENAME TABLE REPAIR TABLE ANALYZE TABLE OPTIMIZE TABLE

  • Waiting for lock_type lock

    服务器正在等待 THR_LOCK 从元数据锁定子系统 获取 锁定或锁定,其中 lock_type 指示锁定的类型。

    此状态表示等待 THR_LOCK

    • Waiting for table level lock

    这些状态表示等待元数据锁定:

    • Waiting for event metadata lock

    • Waiting for global read lock

    • Waiting for schema metadata lock

    • Waiting for stored function metadata lock

    • Waiting for stored procedure metadata lock

    • Waiting for table metadata lock

    • Waiting for trigger metadata lock

    有关表锁指示器的信息,请参见 第8.11.1节“内部锁定方法” 有关元数据锁定的信息,请参见 第8.11.4节“元数据锁定” 要查看哪些锁阻止了锁请求,请使用 第26.12.12节“性能模式锁表”中所述的性能模式锁表

  • Waiting on cond

    线程正在等待条件变为真的通用状态。 没有具体的州信息。

  • Writing to net

    服务器正在将数据包写入网络。

8.14.3复制主线程状态

以下列表显示了您可能在 State Binlog Dump 线程 列中 看到的最常见状态 如果 Binlog Dump 在主服务器 上看不到任何 线程,则表示复制未运行; 也就是说,当前没有连接任何从属设备。

  • Finished reading one binlog; switching to next binlog

    线程已经读完二进制日志文件并打开下一个发送给从属的文件。

  • Master has sent all binlog to slave; waiting for more updates

    该线程已从二进制日志中读取所有剩余更新并将其发送给从属。 该线程现在处于空闲状态,等待主事件上发生的新更新导致二进制日志中出现新事件。

  • Sending binlog event to slave

    二进制日志由 事件 组成 ,其中事件通常是更新以及一些其他信息。 线程已从二进制日志中读取事件,现在将其发送到从站。

  • Waiting to finalize termination

    线程停止时发生的非常短暂的状态。

8.14.4复制从站I / O线程状态

以下列表显示了在 State 从属服务器I / O线程 列中 看到的最常见状态 此状态也出现在 Slave_IO_State 显示 列中 SHOW SLAVE STATUS ,因此您可以通过使用该语句很好地了解正在发生的情况。

  • Checking master version

    在建立与主站的连接之后非常短暂地发生的状态。

  • Connecting to master

    线程正在尝试连接到主服务器。

  • Queueing master event to the relay log

    线程已读取事件并将其复制到中继日志,以便SQL线程可以处理它。

  • Reconnecting after a failed binlog dump request

    线程正在尝试重新连接到主服务器。

  • Reconnecting after a failed master event read

    线程正在尝试重新连接到主服务器。 当再次建立连接时,状态变为 Waiting for master to send event

  • Registering slave on master

    建立与主站连接后非常短暂的状态。

  • Requesting binlog dump

    在建立与主站的连接之后非常短暂地发生的状态。 线程从请求的二进制日志文件名和位置开始向主机发送对其二进制日志内容的请求。

  • Waiting for its turn to commit

    slave_preserve_commit_order 启用 了从属线程等待较旧的工作线程提交时发生的状态

  • Waiting for master to send event

    线程已连接到主服务器并正在等待二进制日志事件到达。 如果主站空闲,这可能会持续很长时间。 如果等待持续 slave_net_timeout 几秒钟,则发生超时。 此时,线程认为连接被破坏并尝试重新连接。

  • Waiting for master update

    之前的初始状态 Connecting to master

  • Waiting for slave mutex on exit

    线程停止时短暂发生的状态。

  • Waiting for the slave SQL thread to free enough relay log space

    您正在使用非零 relay_log_space_limit 值,并且中继日志已经增长到足以使其组合大小超过此值。 I / O线程正在等待,直到SQL线程通过处理中继日志内容释放足够的空间,以便它可以删除一些中继日志文件。

  • Waiting to reconnect after a failed binlog dump request

    如果二进制日志转储请求失败(由于断开连接),则线程在休眠时进入此状态,然后尝试定期重新连接。 可以使用 CHANGE MASTER TO 语句 指定重试之间的间隔

  • Waiting to reconnect after a failed master event read

    读取时发生错误(由于断开连接)。 CHANGE MASTER TO 在尝试重新连接之前 ,线程正在休眠该 语句 设置的秒数 (默认为60)。

8.14.5复制从属SQL线程状态

以下列表显示了您可能在 State 从属服务器SQL线程 列中 看到的最常见状态

  • Killing slave

    线程正在处理一个 STOP SLAVE 语句。

  • Making temporary file (append) before replaying LOAD DATA INFILE

    线程正在执行一个 LOAD DATA 语句,并将数据附加到一个临时文件中,该文件包含从属将从中读取行的数据。

  • Making temporary file (create) before replaying LOAD DATA INFILE

    线程正在执行一个 LOAD DATA 语句,并且正在创建一个临时文件,其中包含从属将从中读取行的数据。 只有在 LOAD DATA 运行MySQL版本低于MySQL 5.0.3的主服务器记录 原始 语句时 才会遇到此状态

  • Reading event from the relay log

    线程已从中继日志中读取事件,以便可以处理事件。

  • Slave has read all relay log; waiting for more updates

    该线程已处理中继日志文件中的所有事件,现在正在等待I / O线程将新事件写入中继日志。

  • Waiting for an event from Coordinator

    使用多线程从属( slave_parallel_workers 大于1),其中一个从属工作线程正在等待来自协调器线程的事件。

  • Waiting for slave mutex on exit

    线程停止时发生的非常短暂的状态。

  • Waiting for Slave Workers to free pending events

    当Workers正在处理的事件的总大小超过 slave_pending_jobs_size_max 系统变量 的大小时,会发生此等待操作 当大小低于此限制时,协调器将恢复计划。 仅当 slave_parallel_workers 设置大于0 时才会出现此状态

  • Waiting for the next event in relay log

    之前的初始状态 Reading event from the relay log

  • Waiting until MASTER_DELAY seconds after master executed event

    SQL线程已读取事件,但正在等待从属延迟失效。 此延迟通过 MASTER_DELAY 选项设置 CHANGE MASTER TO

Info SQL线程 列也可以显示语句的文本。 这表明线程已从中继日志中读取事件,从中提取语句,并可能正在执行它。

8.14.6复制从站连接线程状态

这些线程状态出现在复制从属服务器上,但与连接线程相关联,而不是与I / O或SQL线程相关联。

  • Changing master

    线程正在处理一个 CHANGE MASTER TO 语句。

  • Killing slave

    线程正在处理一个 STOP SLAVE 语句。

  • Opening master dump table

    此状态发生在 Creating table from master dump

  • Reading master dump table data

    此状态发生在 Opening master dump table

  • Rebuilding the index on master dump table

    此状态发生在 Reading master dump table data

8.14.7 NDB群集线程状态

  • Committing events to binlog

  • Opening mysql.ndb_apply_status

  • Processing events

    该线程正在处理二进制日志记录的事件。

  • Processing events from schema table

    该线程正在进行模式复制的工作。

  • Shutting down

  • Syncing ndb table schema operation and binlog

    这用于为NDB提供正确的模式操作二进制日志。

  • Waiting for allowed to take ndbcluster global schema lock

    线程正在等待获取全局模式锁的权限。

  • Waiting for event from ndbcluster

    服务器充当NDB群集中的SQL节点,并连接到群集管理节点。

  • Waiting for first event from ndbcluster

  • Waiting for ndbcluster binlog update to reach current position

  • Waiting for ndbcluster global schema lock

    线程正在等待另一个线程持有的全局模式锁定被释放。

  • Waiting for ndbcluster to start

  • Waiting for schema epoch

    线程正在等待架构时代(即全局检查点)。

8.14.8事件调度程序线程状态

这些状态发生在Event Scheduler线程,为执行调度事件而创建的线程或终止调度程序的线程中。

  • Clearing

    调度程序线程或正在执行事件的线程正在终止并即将结束。

  • Initialized

    调度程序线程或将执行事件的线程已初始化。

  • Waiting for next activation

    调度程序具有非空事件队列,但下一次激活是将来的。

  • Waiting for scheduler to stop

    线程已发出 SET GLOBAL event_scheduler=OFF 并正在等待调度程序停止。

  • Waiting on empty queue

    调度程序的事件队列为空并且正在休眠。

原文