MySQL索引背后的数据结构及算法原理(4)
MySQL索引背后的数据结构及算法原理(4)
情况三:查询条件用到了索引中列的精确匹配,但是中间某个条件未提供。
view sourceprint?EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26'; EXPLAIN SELECT*FROM employees.titles WHERE emp_no='10001'AND from_date='1986-06-26';
+----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
| id | select_type |table| type | possible_keys |key| key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
|1| SIMPLE | titles | ref |PRIMARY|PRIMARY|4| const |1| Using where|
+----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
此时索引使用情况和情况二相同,因为title未提供,所以查询只用到了索引的第一列,而后面的from_date虽然也在索引中,但是由于title不存在而无法和左前缀连接,因此需要对结果进行扫描过滤from_date(这里由于emp_no唯一,所以不存在扫描)。如果想让from_date也使用索引而不是where过滤,可以增加一个辅助索引,此时上面的查询会使用这个索引。除此之外,还可以使用一种称之为“隔离列”的优化方法,将emp_no与from_date之间的“坑”填上。
首先我们看下title一共有几种不同的值: SELECTDISTINCT(title) FROM employees.titles;
+--------------------+
| title |
+--------------------+
| Senior Engineer |
| Staff |
| Engineer |
| Senior Staff |
| Assistant Engineer |
| Technique Leader |
| Manager |
+--------------------+
只有7种。在这种成为“坑”的列值比较少的情况下,可以考虑用“IN”来填补这个“坑”从而形成最左前缀: EXPLAIN SELECT*FROM employees.titles
WHERE emp_no='10001'
AND title IN ('Senior Engineer', 'Staff', 'Engineer', 'Senior Staff', 'Assistant Engineer', 'Technique Leader', 'Manager')
AND from_date='1986-06-26';
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type |table| type | possible_keys |key| key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
|1| SIMPLE | titles | range |PRIMARY|PRIMARY|59|NULL|7| Using where|
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
这次key_len为59,说明索引被用全了,但是从type和rows看出IN实际上执行了一个range查询,这里检查了7个key。看下两种查询的性能比较:
“填坑”后性能提升了一点。如果经过emp_no筛选后余下很多数据,则后者性能优势会更加明显。当然,如果title的值很多,用填坑就不合适了,必须建立辅助索引。
情况四:查询条件没有指定索引第一列。 Query_ID | Duration | Query +----------+------------+-------------------------------------------------------------------------------+
|10|0.00058000|SELECT*FROM employees.titles WHERE emp_no='10001'AND from_date='1986-06-26'|
|11|0.00052500|SELECT*FROM employees.titles WHERE emp_no='10001'AND title IN ... |
+----------+------------+-------------------------------------------------------------------------------+
由于不是最左前缀,索引这样的查询显然用不到索引。
情况五:匹配某列的前缀字符串。 EXPLAIN SELECT*FROM employees.titles WHERE emp_no='10001'AND title LIKE'Senior%';
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type |table| type | possible_keys |key| key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
|1| SIMPLE | titles | range |PRIMARY|PRIMARY|56|NULL|1| Using where|
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
此时可以用到索引,但是如果通配符不是只出现在末尾,则无法使用索引。
情况六:范围查询。
view sourceprint?EXPLAIN SELECT * FROM employees.titles WHERE emp_no
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type |table| type | possible_keys |key| key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
|1| SIMPLE | titles | range |PRIMARY|PRIMARY|4|NULL|16| Using where|
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引。同时,索引最多用于一个范围列,因此如果查询条件中有两个范围列则无法全用到索引。
可以看到索引对第二个范围索引无能为力。这里特别要说明MySQL一个有意思的地方,那就是仅用explain可能无法区分范围索引和多值匹配,因为在type中这两者都显示为range。同时,用了“between”并不意味着就是范围查询,例如下面的查询: EXPLAIN SELECT*FROM employees.titles
WHERE emp_no
AND title='Senior Engineer'
AND from_date BETWEEN'1986-01-01'AND'1986-12-31';
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type |table| type | possible_keys |key| key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
|1| SIMPLE | titles | range |PRIMARY|PRIMARY|4|NULL|16| Using where|
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
看起来是用了两个范围查询,但作用于emp_no上的“BETWEEN”实际上相当于“IN”,也就是说emp_no实际是多值精确匹配。可以看到这个查询用到了索引全部三个列。因此在MySQL中要谨慎地区分多值匹配和范围匹配,否则会对MySQL的行为产生困惑。