7.索引

7.1.索引简介

索引是什么?

MySQL官方对索引的定义为:索引(INDEX)是帮助MySQL高效获取数据的数据结果
从而可以获得索引的本质:**索引是排好序的快速查找

索引的目的在于提高查询效率,可以类比字典的目录。如果要查mysql这个这个单词,我们肯定要先定位到m字母,然后从上往下找y字母,再找剩下的sql。如果没有索引,那么可能需要a---z,这样全字典扫描,如果我想找Java开头的单词呢?如果我想找Oracle开头的单词呢?

重点:索引会影响到MySQL查找(WHERE的查询条件)排序(ORDER BY)两大功能!

除了数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引

一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。

  1. [root@Ringo ~]# df -h # Linux下查看磁盘空间命令 df -h
  2. Filesystem Size Used Avail Use% Mounted on
  3. /dev/vda1 40G 16G 23G 41% /
  4. devtmpfs 911M 0 911M 0% /dev
  5. tmpfs 920M 0 920M 0% /dev/shm
  6. tmpfs 920M 480K 920M 1% /run
  7. tmpfs 920M 0 920M 0% /sys/fs/cgroup
  8. overlay 40G 16G 23G 41%

我们平时所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种数据结构的索引之外,还有哈希索引(Hash Index)等

索引的优势和劣势

优势

  • 查找:类似大学图书馆的书目索引,提高数据检索的效率,降低数据库的IO成本
  • 排序:通过索引対数据进行排序,降低数据排序的成本,降低了CPU的消耗

劣势

  • 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的
  • 虽然索引大大提高了查询速度,但是同时会降低表的更新速度,例如对表频繁的进行INSERTUPDATEDELETE。因为更新表的时候,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加的索引列的字段,都会调整因为更新所带来的键值变化后的索引信息
  • 索引只是提高效率的一个因素,如果MySQL有大数据量的表,就需要花时间研究建立最优秀的索引

    7.2.MySQL索引分类

    索引分类

  • 单值索引:一个索引只包含单个列,一个表可以有多个单列索引

  • 唯一索引:索引列的值必须唯一,但是允许空值
  • 复合索引:一个索引包含多个字段

建议:一张表建的索引最好不要超过5个!

基本语法

  1. -- 1、创建索引 [UNIQUE]可以省略
  2. -- 如果只写一个字段就是单值索引,写多个字段就是复合索引
  3. CREATE [UNIQUE] INDEX indexName ON tabName(columnName(length));
  4. ALTER TABLE tabName ADD [UNIQUE] INDEX indexName ON (columnName(length));
  5. -- 2、删除索引
  6. DROP INDEX [indexName] ON tabName;
  7. -- 3、查看索引
  8. -- 加上\G就可以以列的形式查看了 不加\G就是以表的形式查看
  9. SHOW INDEX FROM tabName \G;

使用**ALTER**命令来为数据表添加索引

  1. -- 1、该语句添加一个主键,这意味着索引值必须是唯一的,并且不能为NULL
  2. ALTER TABLE tabName ADD PRIMARY KEY(column_list);
  3. -- 2、该语句创建索引的键值必须是唯一的(除了NULL之外,NULL可能会出现多次)
  4. ALTER TABLE tabName ADD UNIQUE INDEX indexName(column_list);
  5. -- 3、该语句创建普通索引,索引值可以出现多次
  6. ALTER TABLE tabName ADD INDEX indexName(column_list);
  7. -- 4、该语句指定了索引为FULLTEXT,用于全文检索
  8. ALTER TABLE tabName ADD FULLTEXT indexName(column_list);

7.3MySQL索引数据结构

索引数据结构

  • **B+Tree**索引
  • **Hash**索引
  • **Full-text**全文索引
  • **R-Tree**索引

B+Tree 索引检索原理
MySQL高级 2 索引 - 图1

7.4.哪些情况需要建索引

  1. 主键自动建立主键索引(唯一 + 非空)
  2. 频繁作为查询条件的字段应该创建索引
  3. 查询中与其他表关联的字段,外键关系建立索引
  4. 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度,建的复合索引尽量与order by一致
  5. 查询中统计或者分组字段group by也和索引有关)

    7.5.那些情况不要建索引

  6. 记录太少的表

  7. 经常增删改的表
  8. 频繁更新的字段不适合创建索引
  9. Where条件里用不到的字段不创建索引
  10. 假如一个表有10万行记录,有一个字段A只有true和false两种值,并且每个值的分布概率大约为50%,那么对A字段建索引一般不会提高数据库的查询速度。索引的选择性是指索引列中不同值的数目与表中记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000=0.99。一个索引的选择性越接近于1,这个索引的效率就越高

    8.性能分析

    8.1.MySQL Query Optimizer

    MySQL中专门负责优化SELECT语句的优化器模块
    主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供它认为最优的执行计划(它认为最优的数据检索方式,但不见得是DBA认为最优的,这部分最耗费时间)

当客户端像MySQL请求一条Query,命令解析器模块完成请求分类,去别处是SELECT并转发给MySQL Query OptimizerMySQL Query Optimizer首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对Query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件,结构调整等。然后分析Query中的Hint信息(如果有),看显示Hint信息是否可以完全确定该Query的执行计划。如果没有HintHint信息还不足以完全确定执行计划,则会读区所涉及对象的统计信息,根据Query进行写相应的计算分析,然后再得出最后的执行计划

8.2.MySQL常见瓶颈

**CPU**:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读区数据的时候
**IO**:磁盘IO瓶颈发生再装入数据远大于内存容量的时候
服务器硬件的性能瓶颈top free iostatvmstat来查看系统的性能状态

8.3.EXPLAIN

**EXPLAIN**是什么

  • EXPLAINSQL的执行计划,使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理SQL语句的

**EXPLAIN**怎么使用

  • 语法:**explain + SQL语句**

image.png

  1. mysql> explain select * from pms_category \G;
  2. *************************** 1. row ***************************
  3. id: 1
  4. select_type: SIMPLE
  5. table: pms_category
  6. partitions: NULL
  7. type: ALL
  8. possible_keys: NULL
  9. key: NULL
  10. key_len: NULL
  11. ref: NULL
  12. rows: 1425
  13. filtered: 100.00
  14. Extra: NULL
  15. 1 row in set, 1 warning (0.00 sec)

EXPLAIN能干嘛
可以查看以下信息

  • **id**:表的读取顺序
  • **select_type**:数据读取操作的操作类型
  • **possible_keys**:哪些索引可以使用
  • **key**:哪些索引被实际使用
  • **ref**:表之间的引用
  • **rows**:每张表有多少行被优化器查询

    EXPLAIN字段

    **id**表的读取和加载顺序
    值有以下三种情况

  • id相同,执行顺序由上至下

  • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
  • id相同不同,同时存在。永远是id大的优先级最高,id相等的时候顺序执行

**select_type**数据查询的类型,主要是用于区别,普通查询、联合查询、子查询等的复杂查询。

  • **SIMPLE** 简单的SELECT查询,查询中不包含子查询或者UNION
  • **PRIMARY** 查询中如果包含任何复杂的子部分,最外层查询则被标记为PRIMARY
  • **SUBQUERY**SELECT或者WHERE子句中包含了子查询
  • **DERIVED**FROM子句中包含的子查询被标记为DERIVED(衍生),MySQL会递归执行这些子查询,把结果放在临时表中
  • **UNION** 如果第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为DERIVED
  • **UNION RESULT**UNION表获取结果的SELECT

**type**访问类型排列
从最好到最差依次是:**system**>**const**>**eq_ref**>**ref**>**range**>**index**>**ALL**。除了ALL没有用到索引,其他级别都用到索引了。
一般来说,得保证查询至少达到**range**级别,最好达到**ref**

  • **system**

表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个可以忽略不计

  • **const**

表示通过索引一次就找到了,**const**用于比较**primary key**或者**unique**索引。因为只匹配一行数据,所以很快。如将主键置于where中,MySQL就能将该查询转化为一个常量

  • **eq_ref**

唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描。除了 systemconst 类型之外, 这是最好的联接类型

  • **ref**

非唯一性索引扫描,返回本表和关联表某个值匹配的所有行,查出来有多条记录

  • **range**

只检索给定范围的行,一般就是在WHERE语句中出现了BETWEEN< >in等的查询。这种范围扫描索引比全表扫描要好,因为它只需要开始于索引树的某一点,而结束于另一点,不用扫描全部索引

  • **index**

Full** **Index Scan,全索引扫描,indexALL的区别为index类型只遍历索引树。
也就是说虽然**ALL****index**都是读全表,但是**index**是从索引中读的,**ALL**是从磁盘中读取的

  • **ALL**

Full Table Scan,没有用到索引,全表扫描

**possible_keys**
显示可能应用在这张表中的索引,一个或者多个
查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

**key**
实际使用的索引。如果为NULL,则没建或没有使用索引,即索引失效
查询中如果使用了覆盖索引,则该索引仅仅出现在**key**列表中。与Extra有关

**key_len**
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度
key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。在不损失精度的情况下,长度越短越好。

key_len计算规则 https://blog.csdn.net/qq_34930488/article/details/102931490
— 索引列为字符串类型的情况
1) 列长度:
2) 列是否为空: NULL(+1),NOT NULL(+0)
3) 字符集: 如 utf8mb4=4,utf8=3,gbk=2,latin1=1
4) 列类型为字符: 如 varchar(+2), char(+0)
计算公式:key_len=(表字符集长度) * 列长度 + 1(null) + 2(变长列)
— 数值数据的key_len计算公式:
TINYINT允许NULL = 1 + 1(NULL)
SMALLINT允许为NULL = 2 + 1(NULL)
INT允许为NULL = 4 + 1(NULL)
— 日期时间型的key_len计算:(针对mysql5.5及之前版本)
DATETIME允许为NULL = 8 + 1(NULL)
TIMESTAMP允许为NULL = 4 + 1(NULL)

  1. mysql> desc pms_category;
  2. +---------------+------------+------+-----+---------+----------------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +---------------+------------+------+-----+---------+----------------+
  5. | cat_id | bigint(20) | NO | PRI | NULL | auto_increment |
  6. | name | char(50) | YES | | NULL | |
  7. | parent_cid | bigint(20) | YES | | NULL | |
  8. | cat_level | int(11) | YES | | NULL | |
  9. | show_status | tinyint(4) | YES | | NULL | |
  10. | sort | int(11) | YES | | NULL | |
  11. | icon | char(255) | YES | | NULL | |
  12. | product_unit | char(50) | YES | | NULL | |
  13. | product_count | int(11) | YES | | NULL | |
  14. +---------------+------------+------+-----+---------+----------------+
  15. 9 rows in set (0.00 sec)
  16. mysql> explain select cat_id from pms_category where cat_id between 10 and 20 \G;
  17. *************************** 1. row ***************************
  18. id: 1
  19. select_type: SIMPLE
  20. table: pms_category
  21. partitions: NULL
  22. type: range
  23. possible_keys: PRIMARY
  24. key: PRIMARY # 用到了主键索引,通过查看表结构知道,cat_id是bigint类型,占用8个字节
  25. key_len: 8 # 这里只用到了cat_id主键索引,所以长度就是8!
  26. ref: NULL
  27. rows: 11
  28. filtered: 100.00
  29. Extra: Using where; Using index
  30. 1 row in set, 1 warning (0.00 sec)

**ref**
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值
NULL const 库名.表名.字段

**rows**
根据表统计信息及索引选用情况,大致估算出找到所需的记录需要读取的行数

**Extra**
包含不适合在其他列中显示但十分重要的额外信息

  • **Using filesort**

说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取
**MySQL中无法利用索引完成的排序操作称为”文件内排序

  1. create table user (
  2. id integer primary key auto_increment,
  3. name varchar(20) not null,
  4. age integer not null,
  5. gender tinyint not null
  6. );
  7. create index user_name_gender on user(name, gender);
  8. # 排序没有使用索引
  9. explain select * from user where name ='zhangsan1' order by id \G
  10. *************************** 1. row ***************************
  11. id: 1
  12. select_type: SIMPLE
  13. table: user
  14. partitions: NULL
  15. type: ref
  16. possible_keys: user_name_gender
  17. key: user_name_gender
  18. key_len: 62
  19. ref: const
  20. rows: 1
  21. filtered: 100.00
  22. Extra: Using index; Using filesort
  23. 1 row in set, 1 warning (0.00 sec)
  24. #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
  25. # 排序使用到了索引
  26. explain select * from user where name ='zhangsan1' order by gender \G
  27. *************************** 1. row ***************************
  28. id: 1
  29. select_type: SIMPLE
  30. table: user
  31. partitions: NULL
  32. type: ref
  33. possible_keys: user_name_gender
  34. key: user_name_gender
  35. key_len: 62
  36. ref: const
  37. rows: 1
  38. filtered: 100.00
  39. Extra: Using index
  40. 1 row in set, 1 warning (0.00 sec)
  • **Using temporary**

使用了临时表保存中间结果,MySQL在対查询结果排序时使用了临时表。常见于排序**order by**和分组查询**group by**临时表対系统性能损耗很大

  1. explain select count(*) from user where gender = 18 group by age;
  2. *************************** 1. row ***************************
  3. id: 1
  4. select_type: SIMPLE
  5. table: user
  6. partitions: NULL
  7. type: ALL
  8. possible_keys: NULL
  9. key: NULL
  10. key_len: NULL
  11. ref: NULL
  12. rows: 5
  13. filtered: 20
  14. Extra: Using where; Using temporary; Using filesort
  15. 1 row in set, 1 warning (0.00 sec)
  16. explain select count(*) from user where gender =18 group by name;
  17. *************************** 1. row ***************************
  18. id: 1
  19. select_type: SIMPLE
  20. table: user
  21. partitions: NULL
  22. type: index
  23. possible_keys: user_name_gender
  24. key: user_name_gender
  25. key_len: 63
  26. ref: NULL
  27. rows: 5
  28. filtered: 20
  29. Extra: Using where; Using index
  30. 1 row in set, 1 warning (0.00 sec)
  • **Using index**

表示相应的SELECT操作中使用了覆盖索引,避免访问了表的数据行,效率不错!如果同时出现Using where,表示索引被用来执行索引键值的查找;如果没有同时出现Using where,表明索引用来读取数据而非执行查找动作

  1. # 覆盖索引
  2. # 就是select的数据列只用从索引中就能够取得,不必从数据表中读取,即查询列要被所使用的索引覆盖
  3. # 注意:如果要使用覆盖索引,一定不能写SELECT *,要写出具体的字段。
  4. explain select name, gender from user;
  5. *************************** 1. row ***************************
  6. id: 1
  7. select_type: SIMPLE
  8. table: pms_category
  9. partitions: NULL
  10. type: index
  11. possible_keys: NULL
  12. key: user_name_gender
  13. key_len: 63
  14. ref: NULL
  15. rows: 5
  16. filtered: 100.00
  17. Extra: Using index # select的数据列只用从索引中就能够取得,不必从数据表中读取
  18. 1 row in set, 1 warning (0.00 sec)
  • **Using where**:表明使用了WHERE过滤
  • **Using join buffer**:使用了连接缓存
  • **impossible where**WHERE子句的值总是false,不能用来获取任何元组
    1. mysql> explain select name from user where name = 'zs' and name = 'ls'\G
    2. *************************** 1. row ***************************
    3. id: 1
    4. select_type: SIMPLE
    5. table: NULL
    6. partitions: NULL
    7. type: NULL
    8. possible_keys: NULL
    9. key: NULL
    10. key_len: NULL
    11. ref: NULL
    12. rows: NULL
    13. filtered: NULL
    14. Extra: Impossible WHERE # 不可能字段同时查到两个名字
    15. 1 row in set, 1 warning (0.00 sec)

    9.索引分析

    9.1.单表索引分析

    数据准备 ``sql DROP TABLE IF EXISTSarticle`;

CREATE TABLE IF NOT EXISTS article ( id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT ‘主键’, author_id INT(10) UNSIGNED NOT NULL COMMENT ‘作者id’, category_id INT(10) UNSIGNED NOT NULL COMMENT ‘分类id’, views INT(10) UNSIGNED NOT NULL COMMENT ‘被查看的次数’, comments INT(10) UNSIGNED NOT NULL COMMENT ‘回帖的备注’, title VARCHAR(255) NOT NULL COMMENT ‘标题’, content VARCHAR(255) NOT NULL COMMENT ‘正文内容’ ) COMMENT ‘文章’;

INSERT INTO article(author_id, category_id, views, comments, title, content) VALUES (1, 1, 1, 1, ‘1’, ‘1’), (2, 2, 2, 2, ‘2’, ‘2’), (3, 3, 3, 3, ‘3’, ‘3’), (1, 1, 3, 3, ‘3’, ‘3’), (1, 1, 4, 4, ‘4’, ‘4’);

  1. 案例:查询`category_id`1`comments`大于1的情况下,`views`最多的`article_id`
  2. 1、编写SQL语句并查看SQL执行计划
  3. ```sql
  4. # 1、sql语句
  5. SELECT id,author_id FROM article
  6. WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
  7. # 2、sql执行计划
  8. mysql> EXPLAIN SELECT id,author_id FROM article
  9. WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1\G
  10. *************************** 1. row ***************************
  11. id: 1
  12. select_type: SIMPLE
  13. table: article
  14. partitions: NULL
  15. type: ALL
  16. possible_keys: NULL
  17. key: NULL
  18. key_len: NULL
  19. ref: NULL
  20. rows: 5
  21. filtered: 20.00
  22. Extra: Using where; Using filesort # 产生了文件内排序,需要优化SQL
  23. 1 row in set, 1 warning (0.00 sec)

2、创建索引idx_article_ccv

  1. CREATE INDEX idx_article_ccv ON article(category_id,comments,views);

3、查看当前索引
MySQL高级 2 索引 - 图3
4、查看现在SQL语句的执行计划
MySQL高级 2 索引 - 图4
创建复合索引idx_article_ccv之后,虽然解决了全表扫描的问题,但是在order by排序的时候没有用到索引,MySQL居然还是用的Using filesort,为什么?
5、我们试试修改SQL,看看SQL的执行计划

  1. SELECT id,author_id FROM article
  2. WHERE category_id = 1 AND comments = 1 ORDER BY views DESC LIMIT 1;

MySQL高级 2 索引 - 图5
推论:当comments > 1的时候order by排序views字段索引就用不上,但是当comments = 1的时候order by排序views字段索引就可以用上!!!所以,范围之后的索引会失效

6、知道了范围之后的索引会失效,原来的索引idx_article_ccv最后一个字段views会失效,那么如果删除这个索引,创建idx_article_cv索引呢?

  1. /* 创建索引 idx_article_cv */
  2. CREATE INDEX idx_article_cv ON article(category_id,views);

查看当前的索引
MySQL高级 2 索引 - 图6
7、当前索引是idx_article_cv,来看一下SQL执行计划
MySQL高级 2 索引 - 图7

9.2.两表索引分析

数据准备

  1. DROP TABLE IF EXISTS `class`;
  2. DROP TABLE IF EXISTS `book`;
  3. CREATE TABLE IF NOT EXISTS `class`(
  4. `id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
  5. `card` INT(10) UNSIGNED NOT NULL COMMENT '分类'
  6. ) COMMENT '商品类别';
  7. CREATE TABLE IF NOT EXISTS `book`(
  8. `bookid` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
  9. `card` INT(10) UNSIGNED NOT NULL COMMENT '分类'
  10. ) COMMENT '书籍';

两表连接查询的SQL执行计划

1、不创建索引的情况下,SQL的执行计划。
MySQL高级 2 索引 - 图8
bookclass两张表都是没有使用索引,全表扫描,那么如果进行优化,索引是创建在book表还是创建在class表呢?下面进行大胆的尝试

2、左表(book表)创建索引
创建索引idx_book_card

  1. CREATE INDEX idx_book_card ON book(card);

book表中有idx_book_card索引的情况下,查看SQL执行计划
MySQL高级 2 索引 - 图9
3、删除book表的索引,右表(class表)创建索引
创建索引idx_class_card

  1. CREATE INDEX idx_class_card ON class(card);

class表中有idx_class_card索引的情况下,查看SQL执行计划
MySQL高级 2 索引 - 图10
**由此可见,

9.3.三张表索引分析

数据准备

  1. DROP TABLE IF EXISTS `phone`;
  2. CREATE TABLE IF NOT EXISTS `phone`(
  3. `phone_id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
  4. `card` INT(10) UNSIGNED NOT NULL COMMENT '分类'
  5. ) COMMENT '手机';

三表连接查询SQL优化

1、不加任何索引,查看SQL执行计划。
MySQL高级 2 索引 - 图11
2、根据两表查询优化的经验,左连接需要在右表上添加索引,所以尝试在book表和phone表上添加索引

  1. CREATE INDEX idx_book_card ON book(card); /* 在book表创建索引 */
  2. CREATE INDEX idx_phone_card ON phone(card); /* 在phone表上创建索引 */

再次执行SQL的执行计划
MySQL高级 2 索引 - 图12
后两个都是ref且总row优化很好,效果不错。因此索引最好设置再需要经常查询的字段中

9.4.结论

JOIN语句的优化

  • 尽可能减少JOIN语句中的NestedLoop(嵌套循环)的总次数:永远都是小的结果集驱动大的结果集
  • 优先优化NestedLoop的内层循环
  • 保证JOIN语句中被驱动表上JOIN条件字段已经被索引
  • 当无法保证被驱动表的JOIN条件字段被索引且内存资源充足的前提下,不要太吝惜Join Buffer 的设置

    10.索引失效

    数据准备 ``sql CREATE TABLEstaffs(idINT(10) PRIMARY KEY AUTO_INCREMENT,nameVARCHAR(24) NOT NULL DEFAULT '' COMMENT '姓名',ageINT(10) NOT NULL DEFAULT 0 COMMENT '年龄',posVARCHAR(20) NOT NULL DEFAULT '' COMMENT '职位',add_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘入职时间’ )COMMENT ‘员工记录表’;

INSERT INTO staffs(name,age,pos) VALUES(‘Ringo’, 18, ‘manager’); INSERT INTO staffs(name,age,pos) VALUES(‘张三’, 20, ‘dev’); INSERT INTO staffs(name,age,pos) VALUES(‘李四’, 21, ‘dev’);

/ 创建索引 / CREATE INDEX idx_staffs_name_age_pos ON staffs(name,age,pos);

  1. <a name="c9a7a1cb"></a>
  2. ## 10.1.索引失效的情况
  3. 1. 全值匹配我最爱
  4. 1. 最佳左前缀法则
  5. 1. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
  6. 1. 索引中范围条件右边的字段会全部失效
  7. 1. 尽量使用覆盖索引(只访问索引的查询,索引列和查询列一致),减少`SELECT *`
  8. 1. `MySQL`在使用`!=`或者`<>`的时候无法使用索引会导致全表扫描
  9. 1. `is null`、`is not null`也无法使用索引
  10. 1. `like`以通配符开头`%abc`索引失效会变成全表扫描(使用覆盖索引就不会全表扫描了)
  11. 1. 字符串不加单引号索引失效
  12. 1. 少用`or`,用它来连接时会索引失效
  13. <a name="7af5a8db"></a>
  14. ## 10.2.最佳左前缀法则
  15. > 案例
  16. ```sql
  17. /* 用到了idx_staffs_name_age_pos索引中的name字段 */
  18. EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo';
  19. /* 用到了idx_staffs_name_age_pos索引中的name, age字段 */
  20. EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo' AND `age` = 18;
  21. /* 用到了idx_staffs_name_age_pos索引中的name,age,pos字段 这是属于全值匹配的情况!!!*/
  22. EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo' AND `age` = 18 AND `pos` = 'manager';
  23. /* 索引没用上,ALL全表扫描 */
  24. EXPLAIN SELECT * FROM `staffs` WHERE `age` = 18 AND `pos` = 'manager';
  25. /* 索引没用上,ALL全表扫描 */
  26. EXPLAIN SELECT * FROM `staffs` WHERE `pos` = 'manager';
  27. /* 用到了idx_staffs_name_age_pos索引中的name字段,pos字段索引失效 */
  28. EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo' AND `pos` = 'manager';

概念
**最佳左前缀法则:如果索引是多字段的复合索引,要遵守最佳左前缀法则。指的是

口诀:带头大哥不能死,中间兄弟不能断

10.3.索引列上不计算

案例

  1. # 现在要查询`name` = 'Ringo'的记录下面有两种方式来查询!
  2. # 1、直接使用 字段 = 值的方式来计算
  3. mysql> SELECT * FROM `staffs` WHERE `name` = 'Ringo';
  4. +----+-------+-----+---------+---------------------+
  5. | id | name | age | pos | add_time |
  6. +----+-------+-----+---------+---------------------+
  7. | 1 | Ringo | 18 | manager | 2020-08-03 08:30:39 |
  8. +----+-------+-----+---------+---------------------+
  9. 1 row in set (0.00 sec)
  10. # 2、使用MySQL内置的函数
  11. mysql> SELECT * FROM `staffs` WHERE LEFT(`name`, 5) = 'Ringo';
  12. +----+-------+-----+---------+---------------------+
  13. | id | name | age | pos | add_time |
  14. +----+-------+-----+---------+---------------------+
  15. | 1 | Ringo | 18 | manager | 2020-08-03 08:30:39 |
  16. +----+-------+-----+---------+---------------------+
  17. 1 row in set (0.00 sec)

我们发现以上两条SQL的执行结果都是一样的,但是执行效率有没有差距呢?
通过分析两条SQL的执行计划来分析性能。
MySQL高级 2 索引 - 图13
由此可见,在索引列上进行计算,会使索引失效

口诀:索引列上不计算

10.4.范围之后全失效

案例

  1. /* 用到了idx_staffs_name_age_pos索引中的name,age,pos字段 这是属于全值匹配的情况!!!*/
  2. EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo' AND `age` = 18 AND `pos` = 'manager';
  3. /* 用到了idx_staffs_name_age_pos索引中的name,age字段,pos字段索引失效 */
  4. EXPLAIN SELECT * FROM `staffs` WHERE `name` = '张三' AND `age` > 18 AND `pos` = 'dev';

查看上述SQL的执行计划
MySQL高级 2 索引 - 图14
由此可知,查询范围的字段使用到了索引,但是范围之后的索引字段会失效

口诀:范围之后全失效

10.5.覆盖索引尽量用

在写SQL的不要使用SELECT *,用什么字段就查询什么字段

  1. /* 没有用到覆盖索引 */
  2. EXPLAIN SELECT * FROM `staffs` WHERE `name` = 'Ringo' AND `age` = 18 AND `pos` = 'manager';
  3. /* 用到了覆盖索引 */
  4. EXPLAIN SELECT `name`, `age`, `pos` FROM `staffs`
  5. WHERE `name` = 'Ringo' AND `age` = 18 AND `pos` = 'manager';

MySQL高级 2 索引 - 图15
口诀:查询一定不用*****

10.6.不等有时会失效

  1. /* 会使用到覆盖索引 */
  2. EXPLAIN SELECT `name`, `age`, `pos` FROM `staffs` WHERE `name` != 'Ringo';
  3. typeindex
  4. /* 索引失效 全表扫描 */
  5. EXPLAIN SELECT * FROM `staffs` WHERE `name` != 'Ringo';
  6. typeALL

10.7.like百分加右边

  1. -- 索引失效 全表扫描
  2. EXPLAIN SELECT * FROM `staffs` WHERE `name` LIKE '%ing%';
  3. typeALL
  4. -- 索引失效 全表扫描
  5. EXPLAIN SELECT * FROM `staffs` WHERE `name` LIKE '%ing';
  6. typeALL
  7. -- 使用索引范围查询
  8. EXPLAIN SELECT * FROM `staffs` WHERE `name` LIKE 'Rin%';
  9. typerange

口诀:**like**百分加右边

如果一定要使用%like,而且还要保证索引不失效,那么使用覆盖索引来编写SQL

  1. /* 使用到了覆盖索引 */
  2. EXPLAIN SELECT `id` FROM `staffs` WHERE `name` LIKE '%in%';
  3. typeindex
  4. /* 使用到了覆盖索引 */
  5. EXPLAIN SELECT `name` FROM `staffs` WHERE `name` LIKE '%in%';
  6. typeindex
  7. /* 使用到了覆盖索引 */
  8. EXPLAIN SELECT `age` FROM `staffs` WHERE `name` LIKE '%in%';
  9. typeindex
  10. /* 使用到了覆盖索引 */
  11. EXPLAIN SELECT `pos` FROM `staffs` WHERE `name` LIKE '%in%';
  12. typeindex
  13. /* 使用到了覆盖索引 */
  14. EXPLAIN SELECT `id`, `name` FROM `staffs` WHERE `name` LIKE '%in%';
  15. typeindex
  16. /* 使用到了覆盖索引 */
  17. EXPLAIN SELECT `id`, `age` FROM `staffs` WHERE `name` LIKE '%in%';
  18. typeindex
  19. /* 使用到了覆盖索引 */
  20. EXPLAIN SELECT `id`,`name`, `age`, `pos` FROM `staffs` WHERE `name` LIKE '%in';
  21. typeindex
  22. /* 使用到了覆盖索引 */
  23. EXPLAIN SELECT `id`, `name` FROM `staffs` WHERE `pos` LIKE '%na';
  24. typeindex
  25. /* 索引失效 全表扫描 */
  26. EXPLAIN SELECT `name`, `age`, `pos`, `add_time` FROM `staffs` WHERE `name` LIKE '%in';
  27. typeALL

MySQL高级 2 索引 - 图16
口诀:覆盖索引保两边

10.8.字符要加单引号

  1. -- 使用到了覆盖索引
  2. EXPLAIN SELECT `id`, `name` FROM `staffs` WHERE `name` = 'Ringo';
  3. -- 使用到了覆盖索引
  4. EXPLAIN SELECT `id`, `name` FROM `staffs` WHERE `name` = 2000;
  5. -- 索引失效 全表扫描
  6. EXPLAIN SELECT * FROM `staffs` WHERE `name` = 2000;

这里name = 2000在MySQL中会发生强制类型转换,将数字转成字符串

口诀:字符要加单引号

10.9.索引相关题目

假设index(a,b,c)

Where语句 索引是否被使用
where a = 3 Y,使用到a
where a = 3 and b = 5 Y,使用到a,b
where a = 3 and b = 5 and c = 4 Y,使用到a,b,c
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 N,没有用到a字段
where a = 3 and c = 5 使用到a,但是没有用到c,因为b断了
where a = 3 and b > 4 and c = 5 使用到a,b,但是没有用到c,因为c在范围之后
where a = 3 and b like ‘kk%’ and c = 4 Y,a,b,c都用到
where a = 3 and b like ‘%kk’ and c = 4 只用到a
where a = 3 and b like ‘%kk%’ and c = 4 只用到a
where a = 3 and b like ‘k%kk%’ and c = 4 Y,a,b,c都用到

10.10.面试题分析

数据准备

  1. /* 创建表 */
  2. CREATE TABLE `test03`(
  3. `id` INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
  4. `c1` CHAR(10),
  5. `c2` CHAR(10),
  6. `c3` CHAR(10),
  7. `c4` CHAR(10),
  8. `c5` CHAR(10)
  9. );
  10. /* 插入数据 */
  11. INSERT INTO `test03`(`c1`,`c2`,`c3`,`c4`,`c5`) VALUES('a1','a2','a3','a4','a5');
  12. INSERT INTO `test03`(`c1`,`c2`,`c3`,`c4`,`c5`) VALUES('b1','b22','b3','b4','b5');
  13. INSERT INTO `test03`(`c1`,`c2`,`c3`,`c4`,`c5`) VALUES('c1','c2','c3','c4','c5');
  14. INSERT INTO `test03`(`c1`,`c2`,`c3`,`c4`,`c5`) VALUES('d1','d2','d3','d4','d5');
  15. INSERT INTO `test03`(`c1`,`c2`,`c3`,`c4`,`c5`) VALUES('e1','e2','e3','e4','e5');
  16. /* 创建复合索引 */
  17. CREATE INDEX idx_test03_c1234 ON `test03`(`c1`,`c2`,`c3`,`c4`);

题目

最好索引怎么创建的,就怎么用,按照顺序使用,避免让MySQL再自己去翻译一次

  1. /* 1.全值匹配 用到索引c1 c2 c3 c4全字段 */
  2. EXPLAIN SELECT * FROM `test03`
  3. WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c3` = 'a3' AND `c4` = 'a4';
  4. /* 2.用到索引c1 c2 c3 c4全字段 MySQL的查询优化器会优化SQL语句的顺序*/
  5. EXPLAIN SELECT * FROM `test03`
  6. WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c4` = 'a4' AND `c3` = 'a3';
  7. /* 3.用到索引c1 c2 c3 c4全字段 MySQL的查询优化器会优化SQL语句的顺序*/
  8. EXPLAIN SELECT * FROM `test03`
  9. WHERE `c4` = 'a4' AND `c3` = 'a3' AND `c2` = 'a2' AND `c1` = 'a1';
  10. /* 4.用到索引c1 c2 c3字段,c4字段失效,范围之后全失效 type:range*/
  11. EXPLAIN SELECT * FROM `test03`
  12. WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c3` > 'a3' AND `c4` = 'a4';
  13. /* 5.用到索引c1 c2 c3 c4全字段 MySQL的查询优化器会优化SQL语句的顺序 type:range*/
  14. EXPLAIN SELECT * FROM `test03`
  15. WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c4` > 'a4' AND `c3` = 'a3';
  16. /* 6.用到了索引c1 c2 c3三个字段, c1和c2两个字段用于查找,
  17. c3字段用于排序了但是没有统计到key_len中,c4字段失效*/
  18. EXPLAIN SELECT * FROM `test03`
  19. WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c4` = 'a4' ORDER BY `c3`;
  20. /* 7.用到了索引c1 c2 c3三个字段,c1和c2两个字段用于查找,
  21. c3字段用于排序了但是没有统计到key_len中*/
  22. EXPLAIN SELECT * FROM `test03`
  23. WHERE `c1` = 'a1' AND `c2` = 'a2' ORDER BY `c3`;
  24. /* 8.用到了索引c1 c2两个字段,c4失效,c1和c2两个字段用于查找,
  25. c4字段排序产生了 Using filesort 说明排序没有用到c4字段 */
  26. EXPLAIN SELECT * FROM `test03`
  27. WHERE `c1` = 'a1' AND `c2` = 'a2' ORDER BY `c4`;
  28. /* 9.用到了索引c1 c2 c3三个字段,c1用于查找,c2和c3用于排序 */
  29. EXPLAIN SELECT * FROM `test03`
  30. WHERE `c1` = 'a1' AND `c5` = 'a5' ORDER BY `c2`, `c3`;
  31. /* 10.用到了c1一个字段,c1用于查找,c3和c2两个字段索引失效,产生了Using filesort */
  32. EXPLAIN SELECT * FROM `test03`
  33. WHERE `c1` = 'a1' AND `c5` = 'a5' ORDER BY `c3`, `c2`;
  34. /* 11.用到了c1 c2 c3三个字段,c1 c2用于查找,c2 c3用于排序 */
  35. EXPLAIN SELECT * FROM `test03`
  36. WHERE `c1` = 'a1' AND `c2` = 'a2' ORDER BY c2, c3;
  37. /* 12.用到了c1 c2 c3三个字段,c1 c2用于查找,c2 c3用于排序 */
  38. EXPLAIN SELECT * FROM `test03`
  39. WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c5` = 'a5' ORDER BY c2, c3;
  40. /* 13.用到了c1 c2 c3三个字段,c1 c2用于查找,c2 c3用于排序 没有产生Using filesort
  41. 因为之前c2这个字段已经确定了是'a2'了,这是一个常量,
  42. 再去ORDER BY c3,c2 这时候c2已经不用排序了!
  43. 所以没有产生Using filesort 和(10)进行对比学习!*/
  44. EXPLAIN SELECT * FROM `test03`
  45. WHERE `c1` = 'a1' AND `c2` = 'a2' AND `c5` = 'a5' ORDER BY c3, c2;
  46. /* GROUP BY 表面上是叫做分组,但是分组之前必定排序。 */
  47. /* 14.用到c1 c2 c3三个字段,c1用于查找,c2 c3用于排序,c4失效 */
  48. EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c4` = 'a4' GROUP BY `c2`,`c3`;
  49. /* 15.用到c1这一个字段,c4失效,c2和c3排序失效产生了Using filesort Using temporary
  50. 5.7之后这种不是分组函数和GROUP BY出现的字段不能select*/
  51. EXPLAIN SELECT * FROM `test03` WHERE `c1` = 'a1' AND `c4` = 'a4' GROUP BY `c3`,`c2`;

**GROUP BY**基本上都需要进行排序,索引优化几乎和**ORDER BY**一致,但是**GROUP BY**会有临时表的产生

10.11.总结

索引优化的一般性建议

  • 对于单值索引,尽量选择针对当前query过滤性更好的索引
  • 在选择复合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好
  • 在选择复合索引的时候,尽量选择可以能够包含当前query中的where子句中更多字段的索引
  • 尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的

口诀
带头大哥不能死,中间兄弟不能断,
索引列上不计算,范围之后全失效,
覆盖索引尽量用,不等有时会失效,
like百分加右边,字符要加单引号,
一般SQL少用or。