优化

  • 8.1 优化概览
    • 8.1.1 在数据库级别进行优化
    • 8.1.2 在硬件级别进行优化
    • 8.1.3 平衡可移植性和性能
  • 8.2 优化SQL语句
    • 8.2.1 优化SELECT语句
      • 8.2.1 WHERE子句优化
      • 8.2.2 范围(range)优化
        • 8.2.2.1 单个索引的范围访问方法
        • 8.2.2.2 多列索引的范围访问方法
        • 8.2.2.3 多值比较的等价范围优化
        • 8.2.2.4 跳过扫描范围访问的方法
      • 8.2.3 索引合并优化
        • 8.2.3.1 使用intersect算法
        • 8.2.3.2 使用union算法
        • 8.2.3.3 使用Sort-Union算法
        • 8.2.3.4 影响索引合并优化
  • 8.3 优化和索引
    • 8.3.1 MySQL如何使用索引

翻译+理解,未完待续…


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

8.1 优化概览

数据库性能取决于数据库级别的几个因素,例如表,查询和配置设置。这些软件结构影响硬件级别的CPU和I/O操作,您必须尽可能地降低这些操作并使其尽可能高效。在处理数据库性能时,首先要了解软件方面的高级规则和指南,并使用挂钟时间(wall-clock)来衡量性能。当您成为专家时,您将了解内部发生的更多信息,并开始测量CPU周期和I/O操作等内容。

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

8.1.1 在数据库级别进行优化

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

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

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

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

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

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

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

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

8.1.2 在硬件级别进行优化

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

1.磁盘寻求。磁盘需要一段时间才能找到一段数据。对于现代磁盘,平均时间通常低于10毫秒,因此我们理论上可以做到1秒钟大约100次寻找。用现代磁盘提升时间改善不明显,并且很难针对单个表进行优化。优化搜索时间的方法是将数据分发到多个磁盘上。

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

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

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

8.1.3 平衡可移植性和性能

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

8.2 优化SQL语句

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

8.2.1 优化SELECT语句

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

除了SELECT语句,调整查询语句也适用于一些语句,如 CREATE TABLE...AS SELECTINSERT INTO...SELECTDELETE中的WHERE语句。这些语句具有额外的性能考虑因素,因为它们将写操作与面向读取的查询操作相结合。

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

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

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

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

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

3.最大限度地减少 查询中的全表扫描次数 ,尤其是对于大型表。

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

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

6.您可以为InnoDB表使用单查询事务,如 第8.5.3节“优化InnoDB只读事务”中所述 。

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

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

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

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

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

8.2.1 WHERE子句优化

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

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

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

  1. 删除不必要的括号:
((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)
  1. 常量合并
   (a<b AND b=c) AND a=5
-> b>5 AND b=c AND a=5
  1. 去除特定条件(因为要不断地常量合并):
   (B>=5 AND B=5) OR (B=6 AND 5=5) OR (B=7 AND 5=6)
-> B=5 OR B=6
  1. 索引使用的常量表达式仅计算一次。

  2. 对于MyISAM 和MEMORY表,COUNT(*)函数在没有WHERE的单个表上直接从表信息中检索。
    针对一张表上的NOT NULL列使用此表达式也可以产生一样的效果。

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

  4. 如果您不使用GROUP BY或聚合函数(COUNT(), MIN()等等),则HAVING会被WHERE合并 。

  5. 对于连接中的每个表,构造一个简单的WHERE语句进行表的快速计算,并尽快跳过某些行。

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

  • 一张空表或表的一行。

  • WHERE子句使用了某表中的主键或唯一索引(定义为NOT NULL),其中所有索引部分(all index parts)都与常量表达式进行比较。

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

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

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

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

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

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

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

一些非常快的查询示例:

SELECT COUNT(*) FROM tbl_name;SELECT MIN(key_part1),MAX(key_part1) FROM tbl_name;SELECT MAX(key_part2) FROM tbl_nameWHERE key_part1=constant;SELECT ... FROM tbl_nameORDER BY key_part1,key_part2,... LIMIT 10;SELECT ... FROM tbl_nameORDER BY key_part1 DESC, key_part2 DESC, ... LIMIT 10;

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

SELECT key_part1,key_part2 FROM tbl_name WHERE key_part1=val;SELECT COUNT(*) FROM tbl_nameWHERE key_part1=val1 AND key_part2=val2;SELECT key_part2 FROM tbl_name GROUP BY key_part1;

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

SELECT ... FROM tbl_nameORDER BY key_part1,key_part2,... ;SELECT ... FROM tbl_nameORDER BY key_part1 DESC, key_part2 DESC, ... ;

8.2.2 范围(range)优化

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

8.2.2.1 单个索引的范围访问方法

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

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

  1. 对于BTREE和 HASH索引,比较key和 常量 时使用 =<=>IN()IS NULL,或 IS NOT NULL操作符 就是一个范围条件。

  2. 另外,对于BTREE索引,比较key和 常量 时使用>, <,>=, <=, BETWEEN!=, <> 或者 LIKE(LIKE的参数是常量字符串,不以通配符%开始) 是一个范围条件

  3. 对于所有索引类型,多个范围条件与ORAND形成一个范围条件。

前面描述中的“ 常量 ”是以下类型之一:

  1. 来自查询字符串的常量

  2. 来自同一连接中 的const表(常量表) 或system表的列

  3. 不相关子查询的结果

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

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

SELECT * FROM t1WHERE key_col > 1AND key_col < 10;SELECT * FROM t1WHERE key_col = 1OR key_col IN (15,18,20);SELECT * FROM t1WHERE key_col LIKE 'ab%'OR key_col BETWEEN 'bar' AND 'foo';

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

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

请考虑以下语句,其中 key1是索引列,nonkey列没有创建索引:

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

key1的提取过程如下:

  1. 从原始WHERE子句开始:
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
(key1 < 'bar' AND nonkey = 4) OR
(key1 < 'uux' AND key1 > 'z')
  1. 删除nonkey = 4key1 LIKE '%b'因为它们不能用于范围扫描。删除它们的正确方法是用TRUE替换它们,这样我们在进行范围扫描时不会错过任何匹配的行:
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR
(key1 < 'bar' AND TRUE) OR
(key1 < 'uux' AND key1 > 'z')

可折叠的条件总是真或假:

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

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

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

(key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE)

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

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

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

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

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

8.2.2.2 多列索引的范围访问方法

译者注:这部分晦涩难懂,如果你感觉翻译有问题,请参考原文。

多列索引的范围条件是单个索引的范围条件的扩展。多列索引上的范围条件将索引行限制在一个或多个key元组(key1(key_part1, key_part2, key_part3))间隔内。一个key元组间隔可能由一系列key元组组成,使用索引中的排序。

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

key_part1  key_part2  key_part3NULL       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,key_part2,key_part3) < (1,+inf,+inf)

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

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

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

  1. 对于HASH索引,每个索引都要产生一个间隔(each interval containing identical values can be used)。这意味着一个间隔只能以以下形式的条件生成
key_part1 cmp const1
AND key_part2 cmp const2
AND ...
AND key_partN cmp constN;

这里const1, const2...是常量,cmp是一个=<=>或者IS NULL比较操作符,where条件包含所有索引部分。(也就是说,有N个条件,每个部分都有属于自己的N-part索引。)例如,以下是一个三列HASH索引的范围条件 :

key_part1 = 1 AND key_part2 IS NULL AND key_part3 = 'foo'

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

  1. 对于一个BTREE索引,一个可用的间隔可能是多个条件通过AND组合起来,其中每个条件就是使用常量和索引列进行比较,比较操作符为=, <=>, IS NULL, >, <, >=, <=, !=, <>, BETWEEN,或 LIKE 'pattern'(其中 ‘pattern’ 不以通配符开头)。通过对where条件演化可以得到一个关于索引的间隔,如果它可以返回正确的结果集,那么这个间隔是可用的(如果使用<> 或者!=会演化出两个间隔 )[译者注:这句翻译很吃力,原文An interval can be used as long as it is possible to determine a single key tuple containing all rows that match the condition (or two intervals if <> or != is used).]。

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

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

上面构造出的唯一间隔是:

('foo',10,-inf) < (key_part1,key_part2,key_part3) < ('foo',+inf,+inf)

创建的间隔可能包含的行数多于初始条件。例如,上面的间隔会包括('foo', 11, 0),虽然它不满足原始条件的值。

  1. 如果where中的条件由OR组合起来,那么通过where演化出来的间隔会是这样(由OR两边的条件确定的间隔的联合)形成的;如果where中的条件由AND组成 ,那么间隔是这样(由AND两边的条件确定的间隔的交集)形成的(原文太难懂了,我按照自己的理解来的,原文点这里):

例如这个条件用到了一个两列索引。

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

其产生的间隔有两个:

(1,-inf) < (key_part1,key_part2) < (1,2)
(5,-inf) < (key_part1,key_part2)

在此示例中,第一个间隔使用第一列索引做左边界,右边界使用了第二列索引。第二个间隔仅使用第一列索引。EXPLAIN输出中的key_len列指示使用的索引前缀的最大长度。

在某些情况下,key_len可能表示使用了某列索引,但这可能不是您所期望的。假设 key_part1key_part2可以 NULL,以下条件将会使key_len列显示两列索引的t长度:

key_part1 >= 1 AND key_part2 < 2

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

key_part1 >= 1 AND key_part2 IS NOT NULL

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

8.2.2.3 多值比较的等价范围优化

译者注:参考案例

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

col_name IN(val1, ..., valN)
col_name = val1 OR ... OR col_name = valN

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

  1. 如果col_name上有唯一索引 ,则每个行的范围估计值为1,因为最多一行就可以匹配到给定值。

  2. 否则,任何col_name上的索引都是非唯一的,优化器可以使用Index dives索引统计信息来估计每个范围的行数。

使用Index dives,优化程序会在范围的末尾进行dive,并使用范围中的行数作为估计值。例如,表达式 col_name IN (10, 20, 30)具有三个等值范围,优化程序对每个范围进行两次dive以生成行估计。每对dive产生具有给定值的行数的估计。

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

eq_range_index_dive_limit系统变量,可以配置其值,使得优化器从一个行估计策略到另一个策略。要允许使用 index dives进行最多N 个等价范围的比较,请设置 eq_range_index_dive_limit为N+ 1.要禁用索引统计并始终使用 index dives 无论N是多少,请将其设置为0。

要更新表索引统计以获得最佳估计,请使用ANALYZE TABLE。

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

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

  • 指定一个FORCE INDEX。我们的想法是,如果强制使用索引,再用index dive 进行额外开销就无法获得任何好处。

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

  • 没有子查询。

  • 没有DISTINCT,GROUP BYORDER BY子句存在。

如果跳过index dive,对于 EXPLAIN FOR CONNECTION 的输出更改如下:

  • 对于一般的输出,rows和 filtered值是 NULL。

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

如果没有FOR CONNECTION,跳过index dive时 EXPLAIN的输出不会改变。

在执行跳过index dive的查询后,INFORMATION_SCHEMA.OPTIMIZER_TRACE 表中的相应行 包含skipped_due_to_force_indexindex_dives_for_range_access值 。

8.2.2.4 跳过扫描范围访问的方法

请考虑以下情形:

CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL, PRIMARY KEY(f1, f2));
INSERT INTO t1 VALUES(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;
ANALYZE TABLE t1;EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 > 40;

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

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

  1. 跳过第一个索引的 distinct 值, f1(the index prefix)

  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的情况 ,如下所示:

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

  • 如果索引可用于“ 跳过扫描(Skip Scan) ”,则索引应在possible_keys 列中可见。

    在 optimizer trace中,你能看到Skip Scan 元素:

"skip_scan_range": {"potential_skip_scan_indexes": [{"index": "PRIMARY","tree_travel_cost": 0.4,"num_groups": 3,"rows": 53,"cost": 10.625}]},

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

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

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

8.2.3 索引合并优化

译者注:参考网友 实例。
索引合并访问方法检索多个range扫描并将他们的结果合并到一个。此访问方法仅合并来自单个表的索引扫描,而不扫描多个表。合并可以生成其基础扫描的联合,交叉或交叉联合。

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

SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;SELECT * FROM tbl_nameWHERE (key1 = 10 OR key2 = 20) AND non_key = 30;SELECT * FROM t1, t2WHERE (t1.key1 IN (1,2) OR t1.key2 LIKE 'value%')AND t2.key1 = t1.some_col;SELECT * FROM t1, t2WHERE t1.key1 = 1AND (t2.key1 = t1.some_col OR t2.key2 = t1.some_col2);

译者实例:

mysql> show create table tk\G;
*************************** 1. row ***************************Table: tk
Create Table: CREATE TABLE `tk` (`id` int(8) NOT NULL AUTO_INCREMENT,`username` varchar(30) NOT NULL DEFAULT '',`password` varchar(32) NOT NULL DEFAULT '',`email` varchar(32) NOT NULL DEFAULT '',`type` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`),KEY `email` (`email`) USING BTREE,KEY `username` (`username`) USING BTREE,KEY `type` (`type`) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=19670594 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)mysql> explain select * from tk where (username = '12345' or etype='sdff') and password= 'asdf';
+----+-------------+-------+------------+-------------+----------------+----------------+---------+------+------+----------+------------------------------------------+
| id | select_type | table | partitions | type        | possible_keys  | key            | key_len | ref  | rows | filtered | Extra                                    |
+----+-------------+-------+------------+-------------+----------------+----------------+---------+------+------+----------+------------------------------------------+
|  1 | SIMPLE      | tk    | NULL       | index_merge | username,etype | username,etype | 122,131 | NULL |   55 |    10.00 | Using union(username,etype); Using where |
+----+-------------+-------+------------+-------------+----------------+----------------+---------+------+------+----------+------------------------------------------+
1 row in set, 1 warning (0.00 sec)

注意
索引合并优化算法具有以下限制:
1.如果您查询的WHERE子句有深度嵌套的AND/ OR, 并且MySQL未选择最佳计划,请尝试使用以下转换:
(x AND y) OR z => (x OR z) AND (y OR z)
(x OR y) AND z => (x AND z) OR (y AND z)
2.索引合并不适用于全文索引。

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

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

  • Using intersect(...)

  • Using union(...)

  • Using sort_union(...)

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

8.2.3.1 使用intersect算法

当WHERE子句在不同的键上通过AND组合后转换为多个范围条件,此算法生效 ,并且每个范围条件都是以下之一:

  • N个索引有N个表达式(即,所有索引部分都被覆盖):
key_part1 = const1 AND key_part2 = const2 ... AND key_partN = constN
  • InnoDB表的主键上的任何范围条件 。

例子:

SELECT * FROM innodb_tableWHERE primary_key < 10 AND key_col1 = 20;SELECT * FROM tbl_nameWHERE key1_part1 = 1 AND key1_part2 = 2 AND key2 = 2;

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

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

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

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

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

8.2.3.2 使用union算法

该算法的标准与Index Merge交集算法的标准类似。当WHERE子句在不同的键上通过OR组合后转换为多个范围条件,此算法生效 ,并且每个范围条件都是以下之一:

  • N个索引有N个表达式(即,所有索引部分都被覆盖):
key_part1 = const1 AND key_part2 = const2 ... AND key_partN = constN
  • InnoDB表 的主键上的任何范围条件 。

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

例子:

SELECT * FROM t1WHERE key1 = 1 OR key2 = 2 OR key3 = 3;SELECT * FROM innodb_tableWHERE (key1 = 1 AND key2 = 2)OR (key3 = 'foo' AND key4 = 'bar') AND key5 = 5;

8.2.3.3 使用Sort-Union算法

当WHERE子句在不同的键上通过OR组合后转换为多个范围条件,此算法生效,但索引合并并集(union)算法不适用。

例子:

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

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

8.2.3.4 影响索引合并优化

索引合并的使用是optimizer_switch系统变量中的 index_merge, index_merge_intersection, index_merge_union, index_merge_sort_union 标志的影响 。请参见第8.9.3节“Switchable的优化”。默认情况下,所有这些标志都是on。要仅启用某些算法,请设置index_merge为off,并仅启用应允许的其他算法 。

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

8.3 优化和索引

8.3.1 MySQL如何使用索引

索引用于快速查找具有特定列值的行。如果没有索引,MySQL必须从第一行开始,然后读取整个表以查找相关行。表越大,成本越高。如果表中有相关​​列的索引,MySQL可以快速确定要在数据文件中间寻找的位置,而无需查看所有数据。这比按顺序读取每一行要快得多。

大多数MySQL索引(PRIMARY KEYUNIQUEINDEXFULLTEXT)存储在 B树。例外:空间数据类型的索引使用R树; MEMORY 表也​​支持哈希索引 ; InnoDB使用反向列表作为FULLTEXT索引。

通常,如以下讨论中所述使用的索引。特殊的哈希索引的特性(如MEMORY表中所用 )在 第8.3.9节“B树和哈希索引的比较”中描述。

MySQL使用索引进行这些操作:

  1. 快速查找与WHERE子句匹配的行。

  2. 考虑消除行。如果在多个索引之间有选择,MySQL通常使用找到最小行数(最具选择性的索引)的索引。

  3. 如果表具有多列索引,则优化程序可以使用索引的任何最左前缀来查找行。例如,如果你有一个三列上的索引(col1, col2, col3),你在(col1)(col1, col2)以及(col1, col2, col3)上便具有索引的搜索功能。有关更多信息,请参见 第8.3.6节“多列索引”。

  4. 在执行连接时从其他表中检索行。如果声明它们的类型和大小相同,MySQL可以更有效地使用列上的索引。在这种情况下, VARCHAR与 CHAR被认为是相同的,如果它们被声明为相同的大小。例如, VARCHAR(10)CHAR(10)大小相同,但 VARCHAR(10)CHAR(15)不是。

对于非二进制字符串列之间的比较,两列应使用相同的字符集。例如,将utf8列与 latin1列进行比较会排除使用索引。

不相似列的比较(例如,将字符串列与时间或数字列进行比较)可能会在没有转换的情况下直接比较值时阻止使用索引。对于给定的值,如在数值列的1,在进行比较时,它可能等于在字符串列的'1'' 1''00001',或'01.e1'。这排除了对字符串列的任何索引的使用。

  1. 查找特定索引列key_col的MIN()值或 MAX()值。这是由预处理器优化的,该预处理器检查您是否正在使用 WHERE key_part_N = constant 在索引 key_col之前出现的所有key部分。在这种情况下,MySQL对每个MIN()或 MAX()表达式执行单个键查找,并用常量替换它。如果所有表达式都替换为常量,则查询立即返回。例如:
SELECT MIN(key_part2),MAX(key_part2)FROM tbl_name WHERE key_part1=10;

6.如果对可用索引的最左前缀(例如ORDER BY key_part1, key_part2)进行排序或分组,则对表进行排序或分组 。如果所有key part部分后面跟着 DESC,则按相反顺序读取key。(或者,如果索引是降序索引,则按正向顺序读取key。)请参见 第8.2.1.14节“ORDER BY优化”, 第8.2.1.15节“GROUP BY优化”和 第8.3.13节“降序索引“。

  1. 在某些情况下,可以优化查询以在不咨询数据行的情况下检索值。(为查询提供所有必要结果的索引称为 覆盖索引。)如果查询仅使用表中包含某些索引的列,则可以从索引树中检索所选值以获得更快的速度:
SELECT key_part3 FROM tbl_nameWHERE key_part1=1

对于小型表或报表用来处理大多数查询或所有行的大型表,索引不太重要。当查询需要访问大多数行时,顺序读取比通过索引更快。顺序读取可以最大限度地减少磁盘搜索,即使查询不需要所有行也是如此。有关详细信息,请参见第8.2.1.21节“避免全表扫描”。

Mysql 8.0 第8章 优化相关推荐

  1. mysql 5.622_新特新解读 | MySQL 8.0 对 count(*)的优化

    我们知道,MySQL 一直依赖对 count(*) 的执行很头疼.很早的时候,MyISAM 引擎自带计数器,可以秒回:不过 InnoDB 就需要实时计算,所以很头疼.以前有多方法可以变相解决此类问题, ...

  2. 新特性解读 | MySQL 8.0 对 limit 的优化

    作者:杨奇龙 网名"北在南方",资深 DBA,主要负责数据库架构设计和运维平台开发工作,擅长数据库性能调优.故障诊断. 本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授权不 ...

  3. Mysql 8.0 第3章 简单教程 (翻译+理解)

    教程 3.1 从服务器连接和断开 3.2 输入查询 3.3 创建和使用数据库 3.3.1 创建和选择数据库 3.3.2 创建表 3.3.3 将数据加载到表中 3.3.4 从表中检索信息 3.5 在批处 ...

  4. mysql 表上线_腾讯云上线数据库 MySQL 8.0 版

    原标题:腾讯云上线数据库 MySQL 8.0 版 北京商报讯(记者 魏蔚)7月8日,腾讯云正式发布数据库MySQL 8.0版,通过优化锁系统,事务系统等模块,进一步提升性能,使得QPS(每秒查询率)超 ...

  5. json替换table下的所有值_京东云所有地域正式支持 MySQL 8.0!

    京东云云数据库 RDS 在所有地域正式支持 MySQL 8.0! MySQL 8.0 是目前 MySQL 社区的最新版本,相比较 MySQL 5.7,其支持了很多新的特性以及对原有功能做了重大更新和优 ...

  6. 京东云主机 mysql_京东云所有地域正式支持 MySQL 8.0!

    京东云云数据库 RDS 在所有地域正式支持 MySQL 8.0!MySQL 8.0 是目前 MySQL 社区的最新版本,相比较 MySQL 5.7,其支持了很多新的特性以及对原有功能做了重大更新和优化 ...

  7. MySQL 8.0 OCP(1Z0-908)中文题库解析

    作者介绍:姚远,Oracle ACE(Oracle和MySQL数据库方向),华为云MVP,<MySQL 8.0运维与优化>的作者.中国第一个Oracle高可用大师,拥有包括 Oracle ...

  8. 庖丁解牛-图解MySQL 8.0优化器查询解析篇

    简介:本文重点介绍了优化器的基于规则的其中一部分优化,更多的偏重于SQL中的基本操作符 一  背景和架构 我们都知道,利用编写程序来动态实现我们应用所需要的逻辑,从而程序执行时得到我们需要的结果.那么 ...

  9. mate30pro 优化 开发者选项_10 个开发者必知的 MySQL 8.0 新功能

    文章转发自专业的Laravel开发者社区,原始链接:https://learnku.com/laravel/t/10243/mysql-8-new-features-required-by-10-de ...

最新文章

  1. 使用多尺度空间注意力的语义分割方法
  2. Python基础03 序列
  3. 【学习笔记】SAP CO成本估算相关
  4. centos6 图形界面root免密直接登录问题
  5. MATLAB矩阵操作和算术运算符
  6. chrome jsp 显示不正常_selenium+java谷歌浏览器 网站打开不正常
  7. [论文评析] ArXiv-2021,Pyramid Vision Transformer A Versatile Backbone for Dense Prediction without Convo
  8. 当遥感卫星有了AI……
  9. [译] 人人都是设计师。我们可以的。
  10. C语言扫雷游戏代码以及基本原理教学(一看就会)
  11. 无敌python爬虫教程学习笔记(二)
  12. 苹果电脑mysql安装流程_mac系统下安装mysql步骤
  13. pytorch 报告bug: Assertion idx_dim >= 0 idx_dim < index_size “index out of bounds“
  14. java-net-php-python-11jspm健身管理网站计算机毕业设计程序
  15. 掌握5大技巧,变身Istio 达人!
  16. 关于element-ui的输入框限制只能输入数字的问题
  17. 关于5G的NSA和SA,看完秒懂
  18. 用指针实现strcmp函数功能
  19. Python爬取安居客房产经纪人信息
  20. 聚合支付怎么开发的,模式有哪些

热门文章

  1. Go语言 一级函数 --- 匿名函数、高阶函数、闭包
  2. 统计学专业综合【个人笔记】
  3. C语言的艺术之——头文件
  4. html文件损坏,PPT文件损坏怎么修复?
  5. Unity3D (C#)事件分发机制的实现
  6. NovAtel71x系列,GNSS原始观测量打开指令
  7. 【GUI】Python图形界面(一)
  8. 高光谱图像分类的发展前景
  9. Top7大网络可视化分析工具介绍
  10. 办公室局域网监控员工上网记录合适吗?