MySQL查询优化之范围优化

原文地址:https://dev.mysql.com/doc/refman/5.7/en/range-optimization.html

译文:

8.2.1.2 范围优化

范围访问方法使用单个索引检索包含在一个或多个索引值区间内的表行子集。它可以用于单列或复合索引。下面各部分描述的是优化器在不同条件下使用rang访问方法的情况。

用于单列索引的范围访问方法

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

单列索引的范围条件的定义如下所示:

    1)对于B-tree和散列索引,当使用=,<=>,in(),is null 或is not null这些操作符时,索引键与常量值的比较是一个范围条件。

    2)此外,对于B-tree索引,当使用>、<、>=、<=、between、!=或<>这些操作符或者参数是常量字符串且不以通配符开头的like关键字时,索引键与常量的比较是一个范围条件。

    3)对于所有索引类型,多范围条件与or或and形成一个范围条件。

前面所描述的常量值指的是如下所示情况:

    1)查询字符串中的常量

    2)同一连接里常量表或系统表中的一列

    3)不关联子查询的结果

    4)完全由上述类型的子表达式组成的任何表达式

下面是一些在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子句中为每个可能的索引提取范围条件。在提取过程中,删除不能用于构造范围条件的条件和产生空范围的条件,合并产生重叠范围的条件。

考虑下面的语句,其中key2是一个索引列,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')

    2)移除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')

3)Collapse conditions总是为真或假:

(key1 LIKE 'abcde%' OR TRUE) is always true

(key1 < 'uux' AND key1 > 'z') is always false

用常量替换这些条件后如下所示:

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

移除不是必须的True或False值后:

(key1 < 'abc') OR (key1 < 'bar')

4)将重叠区间合并为一个区间,得到用于范围扫描的最终条件:

(key1 < 'bar')

一般来说(正如前面的示例所演示的那样),用于范围扫描的条件没有where子句那么严格。MySQL通过执行一个额外的检查,来过滤出满足范围条件但不满足完整where子句的行。

范围条件提取算法可以处理任意深度的嵌套and/or,其输出不依赖于条件出现在where子句中的顺序。

MySQL不支持为空间索引的范围访问方法合并多个范围。为了克服这个限制,可以使用具有相同select语句的union,但是需要将每个空间谓词放在不同的select中。

用于复合索引的范围访问方法

复合索引的范围条件是单列索引范围条件的扩展。复合索引上的范围条件限制索引行位于一个或多个索引键元组区间。索引键元组区间是在一组索引键元组上定义的,使用索引中的顺序。

例如,考虑定义为key1(key_part1key_part2key_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' 这个条件没有定义一个区间,因此也就无法用于范围访问方法。

下文的描述详细表明了范围访问方法如何作用于复合索引:

    1)对于散列索引来说,可以使用每个包含相同值的区间。这意味着只有在以下条件下才能产生区间:

key_part1 cmp const1
AND key_part2 cmp const2
AND ...
AND key_partN cmp constN;

这里的const1const2, … 都是常量,,cmp 是如=,<=>,或is null这样的比较操作符,条件覆盖了所有的索引 (也就是说,有N个条件,每个条件都对应着复合索引N-part index中的一个 ) 例如,下面是一个对应于三列复合哈希索引的范围条件:

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

关于常量的定义,可以参考前文用于单列索引的范围访问方法。

2)对于一个B-tree索引来说,区间可以用于与and结合的条件,其中每个条件用=,<=>,is null,>,<,>=,<=,!=,<>,between或者like '匹配模式'(匹配模式中不以通配符开头)这样的操作符把每个条件与一个常量值进行比较。只要能决定单列索引键元组包含所有与条件匹配的行,单一区间就可以使用(或者是使用了<>/!=操作符的两个区间)。

只要是使用了=,<=>,或者is null操作符,MySQL优化器就尝试使用额外的索引键来确定区间。如果操作符是>,<,>=,<=,!=,<>,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),这并不符合原始条件。

    3)如果覆盖区间内包含行集的条件与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)

在这个例子中,第一行的区间使用复合索引中的单列作为左边边界,使用复合索引中的两列作为右边边界。第二行的区间只使用了复合索引中的一列。expalin输出中的key_len列表示所使用的索引键前缀的最大长度。

在一些情况下,key_len列表明了有一个索引键被使用,但这可能不是你期望的。假设key_part1和key_part2可以为空。然后key_len列在以下条件下显示这两个索引键的长度:

key_part1 >= 1 AND key_part2 < 2

但是,实际上,条件被转化成如下所示:

key_part1 >= 1 AND key_part2 IS NOT NULL

有关如何执行优化以组合或消除单列索引的范围条件区间的描述,可以参考前文的用于单列索引的范围访问方法。复合索引的范围条件,也执行了类似的步骤。

多值比较的等值范围优化

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

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

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

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

    2)否则,如果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。

要更新表索引统计信息以获得最佳估计,可以使用使用分析表。

在索引潜水可能被使用的条件下,他们跳过满足所有这些条件的查询:

    1)存在强制使用单列索引的索引暗示。 其思想是,如果索引使用是强制的,那么执行索引潜水的额外开销不会带来任何好处。

    2)索引是非唯一索引并且不是全文搜索索引

    3)不存在子查询

    4)不存在distinct,group by或order by子句

这些潜水跳跃式条件仅适用于单表查询。对于多表查询(joins),索引潜水不会被跳过。

行构造器表达式的范围优化

优化器能够将范围扫描访问方法应用到如下所示查询:

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' );

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

    1)只有in()谓词被用到,而不是not in()

    2)在in()谓词的左侧,行构造器只包含列引用

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

    4)在in()谓词的右侧,有多个行构造器

有关优化器和行构造函器的更多信息,可以参考:Section 8.2.1.19, “Row Constructor Expression Optimization”

范围优化的内存使用限制

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

    1)0值意味着没有限制

    2)如果值大于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.

    3)对于update和delete语句,如果优化器退回到全表扫描,并且启用了系统变量sql_safe_updates,则会发生错误而不是警告。因为,实际上,没有索引键用于确定要修改哪些行。有关更多信息,可以参考Section 4.5.1.6.4, “Using Safe-Updates Mode (--safe-updates)”

对于超出可用范围优化内存且优化器退回到不太优化的计划的单个查询,增加变量range_optimizer_max_mem_size的值可能会提高性能。

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

    1)对于如下所示简单查询,其中有一个范围访问方法的候选键,每个与or结合的谓词大约使用230个字节:

SELECT COUNT(*) FROM t
WHERE a=1 OR a=2 OR a=3 OR .. . a=N;

    2)类似的,对于如下所示查询,每个与and结合的谓词大约使用125个字节:

SELECT COUNT(*) FROM t
WHERE a=1 AND b=1 AND c=1 ... N;

    3)至于使用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个字节。

PS:由于水平有限,译文中难免存在谬误,欢迎批评指正。

猜你喜欢

转载自blog.csdn.net/qq_41080850/article/details/85224373