0
点赞
收藏
分享

微信扫一扫

mysql8.0 之 sql 优化《三B》 之 优化范围查询 总览 总结


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

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

  • 对于这两种​​BTREE​​​和​​HASH​​索引,使用时具有恒定值的关键部分的比较是一个范围条件 = <=>,in(),is null or,is not null运营商。
  • 另外,对于​​BTREE​​索引,使用时具有恒定值的关键部分的比较是一个范围条件 >,<,>=,<=,between,!=,<>运营商,或者like比较,如果该参数 like是一个常数字符串不与通配符开始。
  • 对于所有索引类型,多个范围条件与范围条件组合 or 或​​AND​​形成范围条件。

 

以下是​​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');

 

密钥的提取过程​​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')​​ 总是假的

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

(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​​子句中出现的顺序 。

 

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

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

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

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

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

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

例子:

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

单个间隔是:

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

例子:

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

 

例子:

key_part1 >= 1 AND key_part2 < 2
但是,事实上,条件转换为:


key_part1 >= 1 AND key_part2 IS NOT NULL

 

多值比较的等价范围优化

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

在MySQL 8.0中,对于满足所有这些条件的查询,可以进行索引潜水跳过:

  • 查询用于单个表,而不是多个表的连接。
  • 存在单索引​​FORCE INDEX​​索引提示。我们的想法是,如果强制使用索引,那么从潜在的索引中获取额外开销就无法获得任何好处。
  • 该索引不是唯一的而不是​​FULLTEXT​​索引。
  • 没有子查询。
  • 不​​DISTINCT​​​,​​GROUP BY​​​或​​ORDER BY​​条款存在。

 

跳过扫描范围访问方法

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;

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

  1. 获取第一个关键部分(​​f1 = 1​​)的第一个不同值。
  2. 根据第一个和第二个关键部分构造范围(​​f1 = 1 AND f2 > 40​​)。
  3. 执行范围扫描。
  4. 获取第一个关键部分(​​f1 = 2​​)的下一个不同值。
  5. 根据第一个和第二个关键部分构造范围(​​f1 = 2 AND f2 > 40​​)。
  6. 执行范围扫描。

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

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

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()​谓词的右侧 ,有多个行构造函数。

 

限制内存使用范围优化

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

  • 值为0表示“ 无限制。”
  • 值大于0时,优化程序会在考虑范围访问方法时跟踪消耗的内存。如果要超过指定的限制,则放弃范围访问方法,并考虑其他方法,包括全表扫描。这可能不太理想。如果发生这种情况,会发生以下警告(​N​当前 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​。

 

 

文章持续更新,转发表明出处,方便更新!

 

 

 

 

举报

相关推荐

0 条评论