表结构和索引列

假设数据库中表是这样的: 

我们只考虑一张表employees.titles: 
索引是(emp_no,title,from_date)

  1. SHOW INDEX FROM employees.titles;
  2. +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+
  3. | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Null | Index_type |
  4. +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+
  5. | titles | 0 | PRIMARY | 1 | emp_no | A | NULL | | BTREE |
  6. | titles | 0 | PRIMARY | 2 | title | A | NULL | | BTREE |
  7. | titles | 0 | PRIMARY | 3 | from_date | A | 443308 | | BTREE |
  8. | titles | 1 | emp_no | 1 | emp_no | A | 443308 | | BTREE |
  9. +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+

titles表的主索引为<emp_no, title, from_date>,还有一个辅助索引<emp_no>

为了避免多个索引使事情变复杂(MySQL的SQL优化器在多索引时行为比较复杂),这里我们将辅助索引drop掉:

  1. ALTER TABLE employees.titles DROP INDEX emp_no;

下面分情况讨论,注意每个查询结果的key_lenkey_len 越长,索引用到的越多

全列匹配

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title='Senior Engineer' AND from_date='1986-06-26';
  2. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  5. | 1 | SIMPLE | titles | const | PRIMARY | PRIMARY | 59 | const,const,const | 1 | |
  6. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+

当按照索引中所有列进行精确匹配(这里精确匹指“=”或“IN”匹配)时,索引可以被用到。

这里有一点需要注意,理论上索引对顺序是敏感的,但是由于MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引,例如将where中的条件顺序颠倒:

  1. EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26' AND emp_no='10001' AND title='Senior Engineer';
  2. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  5. | 1 | SIMPLE | titles | const | PRIMARY | PRIMARY | 59 | const,const,const | 1 | |
  6. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+

依然能触发索引

最左前缀匹配

比如(emp_no,title)查询是可以触发索引的

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001';
  2. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
  5. | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | |
  6. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+

查询条件用到了索引中列的精确匹配,但是中间某个条件未提供

比如查询(emp_no,from_date)

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26';
  2. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  5. | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | Using where |
  6. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+

这样只会触发emp_no索引,而后面的from_date虽然也在索引中,但是由于title不存在而无法和左前缀连接,因此需要对结果进行扫描过滤from_date

如果想让from_date也使用索引而不是where过滤,可以增加一个辅助索引<emp_no, from_date>,此时上面的查询会使用这个索引。

除此之外,还可以使用一种称之为“隔离列”的优化方法,将emp_nofrom_date之间的“坑”填上。

首先我们看下title一共有几种不同的值:

  1. SELECT DISTINCT(title) FROM employees.titles;
  2. +--------------------+
  3. | title |
  4. +--------------------+
  5. | Senior Engineer |
  6. | Staff |
  7. | Engineer |
  8. | Senior Staff |
  9. | Assistant Engineer |
  10. | Technique Leader |
  11. | Manager |
  12. +--------------------+

只有7种。在这种成为“坑”的列值比较少的情况下,可以考虑用“IN”来填补这个“坑”从而形成最左前缀:

  1. EXPLAIN SELECT * FROM employees.titles
  2. WHERE emp_no='10001'
  3. AND title IN ('Senior Engineer', 'Staff', 'Engineer', 'Senior Staff', 'Assistant Engineer', 'Technique Leader', 'Manager')
  4. AND from_date='1986-06-26';
  1. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  2. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  3. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  4. | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 59 | NULL | 7 | Using where |
  5. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

这次key_len为59,说明索引被用全了,但是从type和rows看出IN实际上执行了一个range查询,这里检查了7个key。

如果title的值很多,用填坑就不合适了,必须建立辅助索引。

查询条件没有指定索引第一列

  1. EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26';
  2. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
  5. | 1 | SIMPLE | titles | ALL | NULL | NULL | NULL | NULL | 443308 | Using where |
  6. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+

不是最左前缀不会触发

匹配某列的前缀字符串

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title LIKE 'Senior%';
  2. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  5. | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 56 | NULL | 1 | Using where |
  6. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

此时可以用到索引,但是如果通配符不是只出现在末尾,则无法使用索引。(原文表述有误,如果通配符%不出现在开头,则可以用到索引,但根据具体情况不同可能只会用其中一个前缀)

范围查询

范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引。同时,索引最多用于一个范围列,因此如果查询条件中有两个范围列则无法全用到索引。 
比如:

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no < '10010' and title='Senior Engineer';
  2. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  5. | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 4 | NULL | 16 | Using where |
  6. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

可以触发(emp_no ,title) 索引

  1. EXPLAIN SELECT * FROM employees.titles
  2. WHERE emp_no < 10010'
  3. AND title='Senior Engineer'
  4. AND from_date BETWEEN '1986-01-01' AND '1986-12-31';
  5. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  6. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  7. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  8. | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 4 | NULL | 16 | Using where |
  9. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

也是只能触发(emp_no ,title) 索引

用了“between”并不意味着就是范围查询,例如下面的查询:

  1. EXPLAIN SELECT * FROM employees.titles
  2. WHERE emp_no BETWEEN '10001' AND '10010'
  3. AND title='Senior Engineer'
  4. AND from_date BETWEEN '1986-01-01' AND '1986-12-31';
  5. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  6. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  7. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  8. | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 59 | NULL | 16 | Using where |
  9. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

看起来是用了两个范围查询,但作用于emp_no上的“BETWEEN”实际上相当于“IN”,也就是说emp_no实际是多值精确匹配。可以看到这个查询用到了索引全部三个列。因此在MySQL中要谨慎地区分多值匹配和范围匹配,否则会对MySQL的行为产生困惑。

查询条件中含有函数或表达式

MySQL不会为这列使用索引,比如:

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND left(title, 6)='Senior';
  2. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  5. | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | Using where |
  6. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+

虽然这个查询和情况五中功能相同,但是由于使用了函数left,则无法为title列应用索引,而情况五中用LIKE则可以。

再如:

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no - 1='10000';
  2. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
  5. | 1 | SIMPLE | titles | ALL | NULL | NULL | NULL | NULL | 443308 | Using where |
  6. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+

显然这个查询等价于查询emp_no为10001的函数,但是由于查询条件是一个表达式,MySQL无法为其使用索引。看来MySQL还没有智能到自动优化常量表达式的程度,因此在写查询语句时尽量避免表达式出现在查询中,而是先手工私下代数运算,转换为无表达式的查询语句。

最左前缀原理

假设联合索引是state/city/zipCode

那么state就是第一关,city是第二关,zipCode就是第三关

必须匹配了第一关,才能匹配第二关,匹配了第一关和第二关,才能匹配第三关

你不能直接到第二关的

索引的格式就是第一层是state,第二层才是city

多列索引是先按照第一列进行排序,然后在第一列排好序的基础上再对第二列排序,如果没有第一列的话,直接访问第二列,那第二列肯定是无序的,直接访问后面的列就用不到索引了

举例说明 
假设数据 表T (a,b,c) rowid 为物理位置

  1. rowid a b c
  2. (1) 1 1 1
  3. (2) 2 1 13
  4. (3) 2 2 14
  5. (4) 1 3 3
  6. (5) 2 3 12
  7. (6) 1 2 5
  8. (7) 2 3 9
  9. (8) 1 2 2
  10. (9) 1 3 6
  11. (10) 2 2 11
  12. (11) 2 2 8
  13. (12) 1 1 7
  14. (13) 2 3 15
  15. (14) 1 1 4
  16. (15) 2 1 10

当你创建一个索引 create index xxx on t(a,b), 则索引文件逻辑上等同于如下

  1. a b rowid
  2. 1 1 1
  3. 1 1 12
  4. 1 1 14
  5. 1 2 6
  6. 1 2 8
  7. 1 3 4
  8. 1 3 9
  9. 2 1 2
  10. 2 1 15
  11. 2 2 3
  12. 2 2 10
  13. 2 2 11
  14. 2 3 5
  15. 2 3 7
  16. 2 3 13

当select * from T where a=1 and b=3 的时候, 数据库系统可以直接从索引文件中直接二分法找到A=1的记录,然后再B=3的记录。 
但如果你 where b=3 则需要遍历这个索引表的全部!

转:SQL 索引最左前缀原理的更多相关文章

  1. mysql索引之四:复合索引之最左前缀原理,索引选择性,索引优化策略之前缀索引

    高效使用索引的首要条件是知道什么样的查询会使用到索引,这个问题和B+Tree中的“最左前缀原理”有关,下面通过例子说明最左前缀原理. 一.最左前缀索引 这里先说一下联合索引的概念.MySQL中的索引可 ...

  2. 性能测试四十二:sql案例之联合索引最左前缀

    联合索引:一个索引同时作用于多个字段 联合索引的最左前缀: A.B.C3个字段--联合索引 这个时候,可以使用的查询条件有:A.A+B.A+C.A+B+C,唯独不能使用B+C,即最左侧那个字段必须匹配 ...

  3. 【转】MYSQL数据库四种索引类型的简单使用--MYSQL组合索引“最左前缀”原则

    MYSQL数据库索引类型包括普通索引,唯一索引,主键索引与组合索引,这里对这些索引的做一些简单描述: (1)普通索引 这是最基本的MySQL数据库索引,它没有任何限制.它有以下几种创建方式: 创建索引 ...

  4. mysql优化-----多列索引的左前缀规则

    索引优化策略 :索引类型 .1B-tree索引 关注的是:Btree索引的左前缀匹配规则,索引在排序和分组上发挥的作用. 注:名叫btree索引,大的方面看都用的二叉树.平衡树.但具体的实现上,各引擎 ...

  5. MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)

    本节内容: 1)索引基础 2)索引类型(Hash索引.有序数组.B+树) 3)索引的几个常见问题 1)联合索引 2)最左前缀原则 3)覆盖索引 4)索引下推 1. 索引基础 索引对查询的速度有着至关重 ...

  6. sql索引碎片产生的原理 解决碎片的办法(sql碎片整理)(转)

    本文讲述了SQL SERVER中碎片产生的原理,内部碎片和外部碎片的概念.以及解决碎片的办法和填充因子.在数据库中,往往每一个对于某一方面性能增加的功能也会伴随着另一方面性能的减弱.系统的学习数据库知 ...

  7. mysql 索引最左原则原理

    索引本质是一棵B+Tree,联合索引(col1, col2,col3)也是. 其非叶子节点存储的是第一个关键字的索引,而叶节点存储的则是三个关键字col1.col2.col3三个关键字的数据,且按照c ...

  8. sql索引碎片产生的原理 解决碎片的办法(sql碎片整理)

    本文讲述了SQL SERVER中碎片产生的原理,内部碎片和外部碎片的概念.以及解决碎片的办法和填充因子.在数据库中,往往每一个对于某一方面性能增加的功能也会伴随着另一方面性能的减弱.系统的学习数据库知 ...

  9. mysql 索引 大于等于 走不走索引 最左前缀

    你可以认为联合索引是闯关游戏的设计 例如你这个联合索引是state/city/zipCode 那么state就是第一关 city是第二关, zipCode就是第三关 你必须匹配了第一关,才能匹配第二关 ...

随机推荐

  1. 基于Web的Kafka管理器工具之Kafka-manager启动时出现Exception in thread "main" java.lang.UnsupportedClassVersionError错误解决办法(图文详解)

    不多说,直接上干货! 前期博客 基于Web的Kafka管理器工具之Kafka-manager的编译部署详细安装 (支持kafka0.8.0.9和0.10以后版本)(图文详解)   问题详情 我在Kaf ...

  2. 树莓派 关闭屏保 / RaspberryPi turn off ScreenSaver / RaspberryPi disable screen off

    安装xscreensaver并配置 见:https://www.raspberrypi.org/forums/viewtopic.php?t=57552

  3. DatePickerDialog日期对话框以及回调函数的用法

    DatePickerDialog类的实例化需要用到回调接口,如下定义: android.app.DatePickerDialog.DatePickerDialog(Context context, O ...

  4. window下编写python脚本在linux下运行出错 usr/bin/python^M: bad interpreter: No such file or directory

    今天在windows下使用notepad++写了个python脚本,传到linux服务器执行后提示:-bash: ./logger.py: usr/bin/python^M: bad interpre ...

  5. The user specified as a definer ('root'@'%') does not exist 解决方法

    mysql> grant all privileges on *.* to root@"%" identified by "."; Query OK, r ...

  6. Win10 “此环境变量太大。此对话框允许将值设置为最长2047个字符。" 解决方法。

    打开注册表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment 双击右边的 Path (RE ...

  7. Xamarin View获取属性的绑定信息

    public static Binding GetBinding( BindableObject self, BindableProperty property) { var methodInfo = ...

  8. TWaver3D特效之高光反射

    前篇我们介绍了TWaver 3D的环境映射特效,下面我们接着给大家分享高光反射特效.高光反射定义了物体上的某一区域比其他地方更反光.在高光反射的贴图中,黑色区域的反射率为0(完全不反光),白色区域的反 ...

  9. Luogu P2176 [USACO14FEB]路障Roadblock

    解题思路 这是一道最短路题目,不知道大家有没有做过玛丽卡这道题目,如果没做,在做完这道题之后可以去拿个双倍经验哦 先求出一张图中的最短路径,并将其记录下来,我们首先思考:要有增量的前提是新的最短路径比 ...

  10. UVA - 820 Internet Bandwidth(最大流模板题)

    题目: 思路: 直接套最大流的模板就OK了,注意一下输出的格式. 代码: #include <bits/stdc++.h> #define inf 0x3f3f3f3f #define M ...