mysql自我优化

  • 1、WHERE 子句优化
  • 2、范围优化
  • 1. 单部分索引的范围访问方法
  • 2. 多部分索引的范围访问方法
  • 3. 多值比较的相等范围优化
  • 4.行构造函数表达式的范围优化
  • 5. 限制内存使用范围优化
  • 3、行构造函数表达式优化
  • 4、索引合并优化
  • 1. 索引合并交集访问算法
  • 2. 索引合并联合(并集)访问算法
  • 3. 索引合并排序-联合访问算法
  • 5、引擎条件下推优化
  • 1.介绍
  • 2.引擎条件下推受以下限制
  • 6、索引条件下推优化



首先这里要简要说明,该文章翻译自

mysql手册,并经过我的整合,所以文字内容较多,但是实际含金量比较高,

建议大家仔细阅读

必有收获

1、WHERE 子句优化

对处理WHERE子句进行的优化。这些示例使用 SELECT语句,但相同的优化适用WHERE于DELETEUPDATE语句中的子句。

我们可能想重写查询以加快算术运算,同时牺牲可读性。由于 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 OR b=6
  • 索引使用的常量表达式只计算一次。
  • COUNT(*)在没有 a 的单个表上WHERE直接从表信息中检索MyISAM 和MEMORY表。这也适用于任何NOT NULL仅与一张表一起使用的表达式。
  • 早期检测无效的常量表达式。MySQL 很快检测到某些 SELECT语句是不可能的并且不返回任何行。
  • 如果不使用GROUP BY或聚合函数(COUNT()MIN()等),HAVING就会与WHERE合并。
  • 对于连接中的每个表,将构造一个更简单的WHERE,以获得表的快速WHERE计算,并尽快跳过行。
  • 在查询中,首先读取所有常量表,然后读取其他表。常量表是以下任意一种:
  • 空表或只有一行的表。
  • PRIMARY KEYUNIQUE索引上与WHERE子句一起使用的表,其中所有索引部分都与常量表达式相比较,并被定义为NOT NULL。

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

SELECT * FROM t WHERE primary_key=1;
SELECT * FROM t1,t2
  WHERE t1.primary_key=1 AND t2.primary_key=t1.id;
  • 连接表的最佳连接组合是通过尝试所有的可能性找到的。如果ORDER BYGROUP BY子句中的所有列都来自同一个表,则在连接时优先使用该表。
  • 如果有一个ORDER BY子句和一个不同的GROUP BY子句,或者如果 ORDER BYGROUP 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_part1,key_part2,... LIMIT 10;

SELECT ... FROM tbl_name
  ORDER 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_name
  WHERE key_part1=val1 AND key_part2=val2;

SELECT MAX(key_part2) FROM tbl_name GROUP BY key_part1;

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

SELECT ... FROM tbl_name
  ORDER BY key_part1,key_part2,... ;

SELECT ... FROM tbl_name
  ORDER BY key_part1 DESC, key_part2 DESC, ... ;

2、范围优化

范围访问方法使用单个索引来检索包含在一个或几个索引值间隔中的表行子集。它可以用于单个部分或多个部分的索引。以下部分描述了优化器使用范围访问的条件。

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

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

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

  • 对于BTREEHASH索引,当使用=、<=>、IN()、is NULL或is NOT NULL操作符时,将键部分与常量值进行比较是一个范围条件。
  • 此外,对于BTREE索引,当使用>, <, >=, <=, BETWEEN, !=, 或 <> 运算符时或者LIKE(LIKE是不以通配符开头的常量字符串),键部分与常量值的比较是范围条件 。
  • 对于所有索引类型,多个范围条件结合ORAND形成范围条件。

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

  • 来自查询字符串的常量
  • 常量列 或来自同一连接 的system表的列
  • 不相关子查询的结果
  • 完全由上述类型的子表达式组成的任何表达式

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

SELECT * FROM t1
  WHERE key_col > 1
  AND key_col < 10;

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

SELECT * FROM t1
  WHERE 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');

key的提取过程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 = 4和key1、 LIKE '%b’因为它们不能用于范围扫描。删除它们的正确方法是用 替换它们TRUE,这样我们在进行范围扫描时就不会遗漏任何匹配的行。用TRUE代替它们
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR
(key1 < 'bar' AND TRUE) OR
(key1 < 'uux' AND key1 > 'z')
  1. 始终为真或假的折叠条件
(key1 LIKE 'abcde%' OR TRUE) 永远是真的

(key1 < 'uux' AND key1 > 'z') 总是假的
  1. 用常量替换这些条件会产生
(key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE)

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

(key1 < 'abc') OR (key1 < 'bar')
  1. 将重叠区间合并为一个,产生用于范围扫描的最终条件
(key1 < 'bar')

一般来说(如上例所示),用于范围扫描的条件比WHERE子句的限制要小。MySQL执行额外的检查以过滤出满足range条件但不满足完整WHERE子句的行。 范围条件提取算法可以处理任意深度的嵌套AND/OR结构,其输出不依赖于条件在WHERE子句中出现的顺序。对于空间索引的范围访问方法,MySQL不支持合并多个范围。为了解决这个限制,可以将UNION与相同的SELECT语句一起使用,只是要将每个空间谓词放在不同的SELECT中。

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

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

例如,考虑一个定义为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,key_part2,key_part3) < (1,+inf,+inf)

该区间涵盖了上述数据集中的第4、5、6个元组,可用于范围访问方法。

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

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

  • 对于HASH索引,可以使用包含相同值的每个间隔。这意味着只有在以下情况下才能产生间隔:
key_part1 cmp const1
AND key_part2 cmp const2
AND ...
AND key_partN cmp constN;

在这里,const1、const2、…是常量,cmp是=、<=>或is NULL比较运算符之一,条件涵盖了所有索引部分。(也就是说,有N个条件,N部分索引的每个部分都有一个条件。)例如,下面是一个由三部分组成的HASH索引的范围条件:

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

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

  • 对于BTREE索引,间隔可能用于与AND结合的条件,其中每个条件使用=、<=>、IS NULL、>、<、>=、<=、!=、<>、BETWEEN或LIKE ‘pattern’(其中’pattern’不以通配符开头)将键部分与常量值进行比较。只要能够确定包含匹配条件的所有行的单个键元组,就可以使用interval(如果使用<>或!=则可以使用两个interval)。

只要比较运算符是=、<=>或is NULL,优化器就会尝试使用其他键部分来确定间隔。如果操作符是>、<、>=、<=、!=、<>、BETWEEN或LIKE,优化器使用它不在考虑其他键部分。对于下面的表达式,优化器使用第一个比较中的=。它还使用了第二次比较中的>=,但没有考虑其他键部分,也没有使用第三次比较中的interval构造:

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)。

  • 如果覆盖区间内包含的行集的条件与OR结合,则它们形成一个覆盖区间并集内包含的行集的条件。如果这些条件与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_part1和key_part2可以为NULL。然后key_len列显示以下条件的两个关键部分的长度:

key_part1 >= 1 AND key_part2 < 2

但是,事实上,条件被转换成这样:

key_part1 >= 1 AND key_part2 IS NOT NULL

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

3. 多值比较的相等范围优化

考虑以下表达式,其中col_name是一个索引列:

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

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

  • 如果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

即使在使用索引潜水的情况下,对于满足所有这些条件的查询,它们也会被跳过:

  • 出现了一个单索引FORCE INDEX索引提示。其思想是,如果强制使用索引,那么从执行深入索引的额外开销中不会得到任何好处。
  • 该索引是非唯一的,也不是FULLTEXT索引。
  • 没有子查询。
  • 没有DISTINCT、GROUP BY或ORDER BY子句。

这些跳过条件只适用于单表查询。对于多表查询(连接),不会跳过索引潜水

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

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

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

以前,要使用范围扫描,必须将查询写为:

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

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

  • 只使用IN()谓词,而不是not IN()。
  • 在IN()谓词的左侧,行构造函数只包含列引用。
  • 在IN()谓词的右侧,行构造函数只包含运行时常量,这些常量要么是字面量,要么是在执行期间绑定到常量的本地列引用
  • 在IN()谓词的右侧,有多个行构造函数。

有关优化器和行构造函数的更多信息,请参见“行构造函数表达式优化”。

5. 限制内存使用范围优化

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

  • 值为0表示“没有限制”。
  • 如果值大于0,则优化器会在考虑范围访问方法时跟踪所消耗的内存。如果即将超过指定的限制,则放弃范围访问方法,而考虑其他方法,包括全表扫描。这可能不是最理想的。如果发生这种情况,会出现以下警告(其中N是当前的range_optimizer_max_mem_size值):
Warning    3170    Memory capacity of N bytes for
                   'range_optimizer_max_mem_size' exceeded. Range
                   optimization was not done for this query.
  • 对于UPDATE和DELETE语句,如果优化器返回到全表扫描,并且启用sql_safe_updates系统变量(安全更新模式),则会出现错误而不是警告,因为实际上没有使用键来确定要修改哪些行。
  • 对于超出可用范围优化内存的单个查询,优化器会退回到较不优化的计划,增加range_optimizer_max_mem_size值可能会提高性能。

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

  • 对于以下这样的简单查询,其中有一个范围访问方法的候选键,每个谓词与OR结合使用大约230字节:
SELECT COUNT(*) FROM t
WHERE a=1 OR a=2 OR a=3 OR .. . a=N;
  • 类似地,对于下面这样的查询,每个谓词结合AND使用大约125字节:
SELECT COUNT(*) FROM t
WHERE a=1 AND b=1 AND c=1 ... N;
  • 对于带有IN()谓词的查询:
SELECT COUNT(*) FROM t
WHERE a IN (1,2, ..., M) AND b IN (1,2, ..., N);

in()列表中的每个文字值都作为与OR组合的谓词。如果有两个IN()列表,结合OR的谓词数量是每个列表中文字值数量的乘积。因此,在上述情况下,与OR结合的谓词数为M × N。

在5.7.11之前,每个谓词与OR结合的字节数更高,大约为700字节。

3、行构造函数表达式优化

行构造函数允许同时比较多个值。例如,这两个语句在语义上是等价的,优化器以相同的方式处理这两个表达式:

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),
  PRIMARY KEY(c1,c2,c3)
);

在这个查询中,WHERE子句使用索引中的所有列。但是,行构造函数本身并不包含索引前缀,结果优化器只使用c1 (key_len=4, c1的大小):

mysql> EXPLAIN SELECT * FROM t1
       WHERE c1=1 AND (c2,c3) > (1,1)\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 3
     filtered: 100.00
        Extra: Using where

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

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

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

mysql> EXPLAIN SELECT * FROM t1
       WHERE c1 = 1 AND (c2 > 1 OR ((c2 = 1) AND (c3 > 1)))\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 12
          ref: NULL
         rows: 3
     filtered: 100.00
        Extra: Using where

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

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

4、索引合并优化

Index Merge访问方法检索具有多个范围扫描的行,并将其结果合并为一个。此访问方法仅合并来自单个表的索引扫描,而不是跨多个表进行扫描。合并可以产生其底层扫描的并集、交集或交集并集。

可以使用索引合并的查询示例:

SELECT * FROM tbl_name WHERE 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.key1 IN (1,2) OR t1.key2 LIKE 'value%')
  AND t2.key1 = t1.some_col;

SELECT * FROM t1, t2
  WHERE t1.key1 = 1
  AND (t2.key1 = t1.some_col OR t2.key2 = t1.some_col2);

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

  • 如果你的查询有一个复杂的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)

  • 索引合并不适用于全文索引。

EXPLAIN输出中,Index Merge方法在type列中显示为index_merge。在本例中,键列包含所用索引的列表,key_len包含这些索引最长键部分的列表。

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

  • Using intersect(…)
  • Using union(…)
  • Using sort_union(…)

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

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

1. 索引合并交集访问算法

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

  • 这种形式的N部分表达式,其中索引恰好有N部分(即覆盖了所有索引部分):
key_part1 = const1 AND 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;

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

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

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

如果使用的索引没有覆盖查询中使用的所有列,则只有在满足所有使用的键的范围条件时才检索完整的行。如果合并的条件中有一个是InnoDB表的主键上的条件,那么它不用于行检索,而是用于过滤使用其他条件检索的行

2. 索引合并联合(并集)访问算法

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

  • 这种形式的N部分表达式,其中索引恰好有N部分(即覆盖了所有索引部分):
key_part1 = const1 AND 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;

3. 索引合并排序-联合访问算法

当WHERE子句被OR组合成多个范围条件时,该访问算法适用,但Index Merge联合算法不适用。

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;

排序联合算法和联合算法的区别在于,排序联合算法必须首先获取所有行的行id,并在返回任何行之前对它们进行排序。

5、引擎条件下推优化

1.介绍

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

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

假设一个NDB Cluster表定义如下:

CREATE TABLE t1 (
    a INT,
    b INT,
    KEY(a)
) ENGINE=NDB;
  • 引擎条件下推可以用于下面这样的查询,它包括一个非索引列和一个常量之间的比较:
SELECT a, b FROM t1 WHERE b = 10;

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

mysql> EXPLAIN SELECT a,b FROM t1 WHERE b = 10\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
        Extra: Using where with pushed condition
  • 但是,引擎条件下推不能用于这两个查询中的任何一个:
SELECT a,b FROM t1 WHERE a = 10;
SELECT a,b FROM t1 WHERE b + 1 = 10;

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

  • 当使用>或<操作符将索引列与常量进行比较时,也可以使用引擎条件下推:
mysql> EXPLAIN SELECT a, b FROM t1 WHERE a < 2\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
         type: range
possible_keys: a
          key: a
      key_len: 5
          ref: NULL
         rows: 2
        Extra: Using where with pushed condition
  • 其他支持的引擎条件下推比较包括以下内容:
  • column [NOT] LIKE pattern
    pattern必须是包含要匹配的模式的字符串文字;有关语法,请参阅第 12.8.1 节,“字符串比较函数和运算符”。
  • column IS [NOT] NULL
  • column IN (value_list)
    value_list中的每一项都 必须是一个常量、字面值。
  • column BETWEEN constant1 AND constant2
    constant1并且 constant2每个都必须是常量、文字值。

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

引擎条件下推是默认启用的。要在服务器启动时禁用它,请将optimizer_switch系统变量的engine_condition_pushdown标志设置为off。例如,在my.cnf文件中,使用以下行:

[mysqld]
optimizer_switch=engine_condition_pushdown=off

在运行时,像这样禁用条件下推:

SET optimizer_switch='engine_condition_pushdown=off';

2.引擎条件下推受以下限制

  • 只有NDB存储引擎支持下拉引擎条件。
  • 列只能与常量比较;然而,这包括计算为常数值的表达式。
  • 用于比较的列不能是任何BLOB或TEXT类型。这种排除也扩展到JSON、BIT和ENUM列。
  • 要与列进行比较的字符串值必须使用与列相同的排序规则。
  • 连接不直接支持;涉及多个表的条件将在可能的情况下分别推入。使用扩展的EXPLAIN输出来确定哪些条件实际下推。

6、索引条件下推优化

索引条件下推(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支持对虚拟生成的列进行二级索引
  • 引用子查询的条件不能下推
  • 引用存储函数的条件不能下推。存储引擎无法调用已存储的函数。
  • 触发条件不能下推。(有关触发条件的信息,请参见“使用EXISTS策略优化子查询”。)

要理解这种优化是如何工作的,首先考虑当索引条件下推没有使用时索引扫描是如何进行的:

  • 获取下一行,首先通过读取索引元组,然后使用索引元组来定位和读取整个表行。
  • 测试应用于此表的WHERE条件部分。根据测试结果接受或拒绝行。

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

  • 获取下一行的索引元组(但不是整个表行)。
  • 测试适用于此表的WHERE条件的一部分,该部分只能使用索引列进行检查。如果条件不满足,则继续下一行的索引元组。
  • 如果条件满足,则使用index元组来定位和读取全表行。
  • 测试应用于此表的WHERE条件的其余部分。根据测试结果接受或拒绝行。

EXPLAIN输出显示使用索引条件下推时在Extra列中使用索引条件。它不会显示Using index,因为当必须读取全表行时,Using index不适用。

假设一个表包含有关人员及其地址的信息,并且该表有一个定义为index(邮编、姓氏、名)的索引。如果我们知道一个人的邮政编码值,但不确定他的姓氏,我们可以这样搜索:

SELECT * FROM people
  WHERE zipcode='95054'
  AND lastname LIKE '%etrunia%'
  AND address LIKE '%Main Street%';

MySQL可以使用索引扫描邮政编码为’95054’的人。第二部分(姓氏LIKE ‘%etrunia%’)不能用于限制必须扫描的行数,所以如果没有索引条件下推,这个查询必须检索zipcode='95054’的所有人的全表行。

使用索引条件下推,MySQL在读取全表行之前检查姓氏LIKE '%etrunia%'部分。这避免读取与zipcode条件匹配但不匹配lastname条件的索引元组对应的完整行。
索引条件下推默认启用。可以通过设置index_condition_pushdown标志来控制optimizer_switch系统变量:

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