对码当歌,猿生几何?

MySQL索引优化(四)—— 范围查询优化

范围查询可以通过一个索引的单个或者多个索引值的区间来检索数据记录,也适用于符合索引。下面就区间是怎么样从where子句中被提取出来的作出详细解释。

使用单值索引的范围查询

对于单值索引来说索引值的区间能够很好地通过where子句中的相应条件很好的表示出来。

单值索引的范围查询被定义为:

  1. 对于BTree索引和Hash索引来讲,包含索引键和 =, <=>, IN(), IS NULL 或者 IS NOT NULL 这些操作符的比较

  2. 此外对于BTree索引来讲还包含索引键和 >, <, >=, <=, BETWEEN, !=, 或<>, 或 LIKE不以通配符开头的 这些操作符的比较

  3. 对于所有的索引类型,多个索引条件用 or 或者 and 组合的单个范围查询

下面这些是范围查询

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会在提取范围查询条件的时候会为每个索引提取。在提取的过程中,不能构成范围查询条件的将会被丢弃,重叠的范围查询条件将会被合并,构成空的范围查询的将会被移除。

参考下面的语句,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')

2、移除 nonkey = 4 和 key1 LIKE '%b' , 因为他们不能构成范围查询。正确的替代方式是移除并使用true替代它们,这样可以保证不会有被错过的记录,替代之后如下:

(key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR
(key1 < 'bar' AND TRUE) OR
(key1 < 'uux' AND key1 > 'z')

3、合并总是true或者false的条件

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

从上述分析可知

  1. 通常使用的范围查询条件是没有where子句严格的,基于此,MySQL还会额外做一些过滤以滤掉那些符合范围查询条件而不符合where子句的所有条件的记录。

  2. MySQL范围查询条件提取算法能够处理人体深度用and 或者 or组合的查询条件,最后的范围查询条件不依赖出现在where语句中顺序。

使用复合索引的范围查询

复合索引的范围查询是单值索引的扩展。基于复合索引的范围查询条件将索引限制在一个或多个键元组区间内,键元组区间定义在键元组的集合上,并使用索引排序。

如下,复合索引定义为 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' 不能定义一个单独的区间,不能被范围查询使用。

下面将详细地描述复合索引的范围查询是如何工作的。

1、对于Hash索引,只有包含唯一元组的索引才能够使用,也就是说只有下列形式的条件能够产出范围查询条件

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

这里const1, const2, … 常量, cmp是 =, <=>, or IS NULL 其中之一的操作符,并且查询条件覆盖了索引的所有部分。例如下面的查询是一个范围查询对于复合Hash索引

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

2、对于BTree索引来讲,一个区间可以是使用and连接起来的查询条件,并且每个条件可以是索引的一部分和一个常量的比较(操作符是 =, <=>, IS NULL, >, <, >=, <=, !=, <>, BETWEEN, or LIKE 'pattern')。一个区间只要是能决定一个单独的键元组并且包含了满足所有条件的所有记录。

当操作符是 =, <=>, or IS NULL时,优化器会使用键的其他部分来决定一个区间,如果操作符是 >, <, >=, <=, !=, <>, BETWEEN, or 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)

在第一行的区间会使用键的左右两个部分,而第二行则只使用键的一部分。