MySQL5.6 单列、多列索引以及IN语句的优化(翻译)
文章出处:http://dev.mysql.com/doc/refman/5.6/en/range-optimization.html (翻译的有纰漏的地方,请见谅)
单列索引的范围查询
对于单列索引,索引值的区间可以方便的以相应的WHERE从句中的条件来表示,所以我们谈论范围查询而不是“间隔”。
对于单列索引,范围查询条件定义为如下几种情况:
1、对于所有的B+树和哈希索引,使用键列和一个常量通过=、<=>、IN()、IS NULL,或者IS NOT NULL等操作符来比较。
3、对于所有类型的索引,多个范围条件可以由OR或者AND相连的范围查询条件来组合。
注意:在上面描述的“常量值”包括以下几种情况:
1、一个查询字符串
2、一个连接const或者system表的列
3、一个不相关的子查询的结果
4、任何由上述三种类型的子表达式组合而成的表达式
以下是一些在WHERE子句中使用范围查询的例子
View Code
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列则没有索引
View Code
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从句:
View Code
(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和Key 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)总是真
-
(key1 < 'uux' AND key1 > 'z')
总是假使用常量替换掉这些条件,就是(key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (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_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 定义了下面这个区间:
这个区间覆盖了上述数据集中的第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列索引的一个列),举例来说,下面的就是一个3列的HASH索引的范围条件。
对于一个B+树索引,一个区间通过AND关键字连接即可可用,每个条件使用
=
, <=>
, IS NULL
, >
, <
, >=
, <=
, !=
, <>
, BETWEEN
, 或者 LIKE 'pattern
'(
但是模式不能以通配符开始)等符号来比较一个索引列和一个常量值。只要能通过一个单独的列元组能决定一个完整的行记录,而这个行记录匹配这个条件(或者两个区间如果<>或者!=使用),这个区间就会使用。
当比较操作符是
=
, <=>
, 或者 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)
, 但是却不满足原始的条件。如果条件使用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
)
在这个例子中,第一行会使用一个键列来确定区间的左边界,使用两个键列来确定右边界。而第二行仅仅使用一个 键列。在执行计划的输出中,会使用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
多值比较的等值范围优化
考虑这些表达式,col_name是索引列:
col_name
IN(val1
, ..., valN
)
col_name
= val1
OR ... OR col_name
= valN
如果col_name和括号中的任何一个值相等,那么这个表达式就为真,这些都是等值范围比较(“范围”是个单值),优化器使用以下方法来估算读取满足条件的行记录所付出的的代价:1、如果col_name是个唯一索引列,那么每个范围的估算都是1,因为对于给定值,最多只有一个行记录满足条件;
2、否则,优化器会使用索引或者索引统计特征对每个范围的行记录数进行估算
使用index dives,优化器会在范围的末端使用dive,而且使用范围的行记录数估算。
举例来说,表达式
col_name
IN (10, 20, 30)有三个等值的范围,优化器会对每个范围进行两次dives来估算,每一对的dives产生给定值的相应的行记录数的估算。 index dives 提供了精确的行估算,但是当表达式中需要比较的值的数量增加时,优化器会使用更长的时间来估算,而使用索引特征虽然精确度低一些,但是在估算更大值得列表时会更快一些。
当优化器需要从一个估算策略转换到另一个时,可以使用 eq_range_index_dive_limit
这个系统变量进行配置。如果禁用索引特征,总是使用index dives ,可以设置eq_range_index_dive_limit
为0。如果允许使用比较的index dives增长到N 等值范围,可以设置eq_range_index_dive_limit
为N+1;eq_range_index_dive_limit
是MySQL5.6中的变量,在5.6.5之前,优化器一直使用index dives,这等价于eq_range_index_dive_limit
= 0;