范围访问方法使用单个索引来检索包含在一个或多个索引值间隔内的表行的子集。 它可用于单部分或多部分索引。 以下部分描述了优化器使用范围访问的条件。
目录
单部分索引的范围访问方法
对于单部分索引,索引值区间可以方便地用 WHERE 子句中的相应条件表示,表示为范围条件而不是“区间”。
单部分索引的范围条件定义如下:
- 对于 BTREE 和 HASH 索引,在使用 =、<=>、IN()、IS NULL 或 IS NOT NULL 运算符时,将关键部分与常量值进行比较是范围条件。
- 此外,对于 BTREE 索引,使用 >、<、>=、<=、BETWEEN、!= 或 <> 运算符时,将关键部分与常量值进行比较是范围条件,或者如果 LIKE 的参数是 LIKE 比较 是一个不以通配符开头的常量字符串。
- 对于所有索引类型,多个范围条件与 OR 或 AND 组合形成一个范围条件。
上述描述中的“常量值”是指以下之一:
- 来自查询字符串的常量
- 来自同一连接的 const 或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');
key1的提取过程如下:
- 从原始 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.始终为真或假的折叠条件:
-
(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')
4.将重叠的间隔组合成一个产生用于范围扫描的最终条件:
(key1 < 'bar')
通常(如前面的示例所示),用于范围扫描的条件没有 WHERE 子句那么严格。 MySQL 执行额外的检查以过滤掉满足范围条件但不满足完整 WHERE 子句的行。
范围条件提取算法可以处理任意深度的嵌套 AND/OR 构造,并且其输出不依赖于条件在 WHERE 子句中出现的顺序。
MySQL 不支持空间索引的范围访问方法合并多个范围。 要解决此限制,我们可以使用具有相同 SELECT 语句的 UNION,但我们将每个空间谓词放在不同的 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,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' 不以通配符开头)。 只要可以确定包含匹配条件的所有行的单个键元组,就可以使用间隔(如果使用 <> 或 !=,则可以使用两个间隔)
只要比较运算符是 =、<=> 或 IS NULL,优化器就会尝试使用其他key parts来确定间隔。 如 果运算符是 >、<、>=、<=、!=、<>、BETWEEN 或 LIKE,则优化器使用它但不再考虑key parts。 对于以下表达式,优化器使用第一次比较中的 =。 它还使用第二个比较中的 >= 但不 考虑其他key parts,并且不使用第三个比较进行区间构造:
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)
在此示例中,第一行的间隔使用一个可以part作为左边界,两个key part作为右边界。 第二行的间隔只使用了一个key part。 EXPLAIN 输出中的 key_len 列指示使用的键前缀的最大长度。
在某些情况下,key_len 可能表示使用了key part,但这可能不是我们所期望的。 假设 key_part1 和 key_part2 可以为 NULL。 然后 key_len 列显示以下条件的两个key part长度:
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 等于多个值中的任何一个,则每个表达式都为真。 这些比较是相等范围比较(其中“范围”是单个值)。 优化器估计读取符合条件的行以进行相等范围比较的成本如下:
- 如果 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 子句。
这些跳水条件仅适用于单表查询。 多表查询(连接)不会跳过索引潜水。
行构造函数表达式的范围优化
优化器能够将范围扫描访问方法应用于这种形式的查询:
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 值):
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 字节。