Bootstrap

mysql 索引 底层刨析

转载自: https://blog.csdn.net/weixin_39724194/article/details/107515342

1.索引概述:

索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。

索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。

更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。、、

例如,数据库里面有20000条记录,现在要执行这么一个查询:SELECT * FROM table where num = 10000。如果没有索引,必须遍历整个表,直到num等于10000的这一行被找到为止;如果在num列上创建索引,MySQL不需要任何扫描,直接在索引中找10000,就可以得知值这一行的位置。可见,索引的建立可以提高数据库的查询速度。

2.优缺点:

2.1 优点:

  1. 通过创建唯一索引,可以保证数据库表中每一行数据的唯一性
  2. 可以大大加快数据的查询速度,这也是创建索引最主要的原因
  3. 在实现数据的参考完整性方面,可以加速表和表之间的连接
  4. 在使用分组和排序子句进行数据查询时,也可以显著减少查询中分组和排序的时间

2.2 缺点:

  • 时间上:
    • 创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率
  • 空间上:
    • 索引需要占物理空间

3.索引分类:

可分为:

  • 单列索引
    • 普通索引
    • 唯一索引
    • 主键索引
  • 组合索引
  • 全文索引
  • 空间索引

主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。

唯一索引: 数据列不允许重复,允许为NULL值(允许多个null值),一个表允许多个列创建唯一索引。

  • 可以通过 ALTER TABLE table_name ADD UNIQUE (column); 创建唯一索引
  • 可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2); 创建唯一组合索引

普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。

  • 可以通过ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引
  • 可以通过ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引

全文索引: 是目前搜索引擎使用的一种关键技术。
全文索引类型为FULLTEXT,在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。全文索引可以在CHAR、VARCHAR或者TEXT类型的列上创建。在5.6之前,MySQL中只有MyISAM存储引擎支持全文索引,5.6之后MyISAM和InnoDB支持

  • 可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引

4.索引的设计原则:

索引设计不合理或者缺少索引都会对数据库和应用程序的性能造成障碍,高效的索引对于获得良好的性能非常重要,设计索引时,应该考虑一下:

  1. 索引并非越多越好,一个表中如有大量的索引,不仅占用磁盘空间,而且会影响INSERT、DELETE、UPDATE等语句的性能,因为当表中的数据更改的同时,索引也会进行调整和更新
    Mysql8中原话:
// 二级索引:非聚集索引
// 一个表最多可以包含64个二级索引。
//多列索引最多允许有16列。
// 超过限制将返回错误。
//错误1070(42000):指定的关键部件太多;最多允许16个零件
A table can contain a maximum of 64 secondary indexes.
A maximum of 16 columns is permitted for multicolumn indexes. 
Exceeding the limit returns an error.
ERROR 1070 (42000): Too many key parts specified; max 16 parts allowed

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  1. 避免对经常更新的表设计过多的索引,并且索引中的列尽可能要少,而对经常用于查询的字段应该创建索引,但要避免添加不必要的字段

  2. 频繁更新的列不要设计索引

  3. 数据量小的表最好不要使用索引,由于数据较少,查询花费的时间可能比遍历索引时间还要短,索引可能不会产生优化效果

  4. 若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)

  5. 当唯一性是某种数据本身的特征时,指定唯一索引。使用唯一索引需能确保定义的列的数据完整性,以提高查询速度

  6. 频繁排序或分组(即group by或order by操作)的列上建立索引,如果待排序的列有多个,可以在这些列上建立组合索引

  7. 定义有外键的数据列一定要建立索引

  8. 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

  9. 对于定义为text、image和bit的数据类型的列不要建立索引。

  10. 查询的内容尽量匹配索引,避免回表问题

  11. 当没有给表创建索引的时候

    • 如果有主键会创建聚簇索引
    • 如果没有主键会生成rowid作为隐式主键

5.索引的数据结构(b+树,hash)

索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引,B+树索引等,而我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。

5.1 索引实现原理(为什么使用B+树作为索引的存储结构):

5.1.1 目录到索引的演变过程:

假设有一个表index_demo,表中有2个INT类型的列,1个CHAR(1)类型的列,c1列为主键:

CREATE TABLE index_demo(c1 INT,c2 INT,c3 CHAR(1),PRIMARY KEY(c1)) ;

  
  
  • 1

index_demo表的简化的行格式示意图如下:

在这里插入图片描述

我们只在示意图里展示记录的这几个部分:

  • record_type:表示记录的类型, 0是普通记录、 2是最小记录、 3 是最大记录、1是B+树非叶子节点记录。
  • next_record:表示下一条记录的相对位置,我们用箭头来表明下一条记录。
  • 各个列的值:这里只记录在 index_demo 表中的三个列,分别是 c1 、 c2 和 c3 。
  • 其他信息:除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息。

其他信息项暂时去掉并把它竖起来的效果就是这样:
在这里插入图片描述

把一些记录放到页里的示意图就是(这里一页就是一个磁盘块,代表一次IO):
在这里插入图片描述

MySQL InnoDB的默认的页大小是16KB,因此数据存储在磁盘中,可能会占用多个数据页。如果各个页中的记录没有规律,我们就不得不依次遍历所有的数据页。如果我们想快速的定位到需要查找的记录在哪些数据页中,我们可以这样做 :

  • 下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值
  • 给所有的页建立目录项

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wUUDFdX7-1681456048320)(image-20220709073749310.png)]

页28为例,它对应目录项2 ,这个目录项中包含着该页的页号28以及该页中用户记录的最小主键值 5。我们只需要把几个目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键值快速查找某条记录的功能了。比如:查找主键值为 20 的记录,具体查找过程分两步:

  1. 先从目录项中根据二分法快速确定出主键值为20的记录在目录项3中(因为 12 ≤ 20 < 209 ),对应页9
  2. 再到页9中根据二分法快速定位到主键值为 20 的用户记录。

至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为索引

5.1.2 目录结构到B+树结构的演变过程:

我们新分配一个编号为30的页来专门存储目录项记录,页10、28、9、20专门存储用户记录
在这里插入图片描述
在这里插入图片描述

目录项记录和普通的用户记录的不同点:

  • 目录项记录 的 record_type 值是1,而 普通用户记录 的 record_type 值是0。
  • 目录项记录只有主键值和页的编号两个列,而普通的用户记录的列是用户自己定义的,包含很多列,另外还有InnoDB自己添加的隐藏列。

现在查找主键值为 20 的记录,具体查找过程分两步:

  1. 先到页30中通过二分法快速定位到对应目录项,因为 12 ≤ 20 < 209 ,就是页9。
  2. 再到页9中根据二分法快速定位到主键值为 20 的用户记录。

更复杂的情况如下:

我们生成了一个存储更高级目录项的 页33 ,这个页中的两条记录分别代表页30和页32,如果用户记录的主键值在 [1, 320) 之间,则到页30中查找更详细的目录项记录,如果主键值 不小于320 的话,就到页32中查找更详细的目录项记录。这个数据结构,它的名称是 B+树 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YkSiMnbC-1681456620381)(image-20220709080648851.png)]

5.2 B+树实现索引:

关于B树和B+树详细可以看数据结构-B树和B+树

  1. 非叶节点只存放索引,不存放数据,n棵子tree的节点包含n个关键字
  2. 所有的叶子节点通过一个有序链表构成了全部数据及指向这些数据的指针,可以按照关键码排序的次序遍历全部数据

5.3 Hash表实现索引:

简要说下,类似于数据结构中简单实现的HASH表(散列表)一样,当我们在mysql中用哈希索引时,主要就是通过Hash算法(常见的Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的Hash值,与这条数据的行指针一并存入Hash表的对应位置;如果发生Hash碰撞(两个不同关键字的Hash值相同),则在对应Hash键下以链表形式存储。当然这只是简略模拟图。
在这里插入图片描述

5.4 Hash表相对于B+树的优劣:

hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树。对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。

5.3.1 Hash表的优点:
  • 正常情况下,Hash表的等值查询更快。如果存在大量重复键值,就存在Hash碰撞的问题。就要先找到键所在位置,然后再根据链表往后扫描,直到找到相应的数据
5.3.2 缺点:

因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围。

  • hash索引不支持使用索引进行排序,原理同上。
  • hash索引不支持模糊查询以及多列索引的最左前缀匹配。原理也是因为hash函数的不可预测。AAAA和AAAAB的索引没有相关性。
  • hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询。
  • hash索引虽然在等值查询上较快,但是不稳定。性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差。而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低。

5.4 为什么使用B+树而不用B树:

B和B+树的区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历。

5.4.1 B+ 树的优点在于:
  • 读写代价低(和磁盘IO效率有关);一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素
  • 查询效率稳定
  • 由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。
  • B+树支持顺序查询:B+树的叶子结点都是相链的,因此对整棵树的遍历只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
  • 增删效率好
5.4.2 B树优点:
  • 由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。

下面是B 树和B+树的区别图:
在这里插入图片描述

5.5 B+树的存储量级:

  • 真实环境中一个页存放的记录数量是非常大的(默认16KB),假设指针与键值忽略不计(或看做10个字节),数据占 1 kb 的空间:
  • 如果B+树只有1层,也就是只有1个用于存放用户记录的节点,最多能存放 16 条记录。
  • 如果B+树有2层,最多能存放 1600×16=25600 条记录。
  • 如果B+树有3层,最多能存放 1600×1600×16=40960000 条记录。
  • 如果存储千万级别的数据,只需要三层就够了

B+树的非叶子节点不存储用户记录,只存储目录记录,相对B树每个节点可以存储更多的记录,树的高度会更矮胖,IO次数也会更少。

6. 聚簇索引和非聚簇索引

6.1 聚簇索引:

  • innodb可以把主键索引理解成聚簇索引。
  • 一张表中,如果没有指定某列是聚簇索引,那么该表的第一个唯一非空索引被作为聚集索引,如果也没有索引,那么系统会自动创建一个隐含列作为表的聚集索引, 这个字段长度为6个字节,类型为长整型
  • 一个表只能有一个聚集索引,因为聚集索引把表的数据格式转换成索引树(平衡树)的格式放置。所以一个表只能有一个主键。树中的节点(除底部节点)的数据是由主键及数据地址构成。时间复杂度O(log n)(n代表记录数);
  • 页内的记录是按照主键的大小顺序排成一个单向链表
  • 页和页之间也是根据页中记录的主键的大小顺序排成一个双向链表
  • 非叶子节点存储的是记录的主键+页号
  • 叶子节点存储的是完整的用户记录
  • 查找的时候先通过主键找到所在的叶节点,而这个叶节点叶包含了此行的所有数据信息(innodb直接存储数据信息,myisam存储着数据的指向)(来源通过DBCC PAGE查看页信息验证聚集索引和非聚集索引节点信息
    在这里插入图片描述
6.1.1 优点
  • 数据访问更快 ,因为索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快。
  • 聚簇索引对于主键的排序查找范围查找速度非常快。
  • 按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库可以从更少的数据块中提取数据,节省了大量的IO操作
6.1.2 缺点:
  • 插入速度严重依赖于插入顺序 ,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
  • 更新主键的代价很高 ,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新
6.1.3 限制:
  • 只有InnoDB引擎支持聚簇索引,MyISAM不支持聚簇索引
  • 由于数据的物理存储排序方式只能有一种,所以每个MySQL的表只能有一个聚簇索引
  • 如果没有为表定义主键,InnoDB会选择非空的唯一索引列代替。如果没有这样的列,InnoDB会隐式的定义一个主键作为聚簇索引。
  • 为了充分利用聚簇索引的聚簇特性,InnoDB中表的主键应选择有序的id,不建议使用无序的id,比如UUID、MD5、HASH、字符串作为主键,无法保证数据的顺序增长。

6.2 非聚集索引(二级索引、辅助索引)

聚簇索引,只能在搜索条件是主键值时才发挥作用,因为B+树中的数据都是按照主键进行排序的,如果我们想以别的列作为搜索条件,那么需要创建非聚簇索引

例如,以c2列作为搜索条件,那么需要使用c2列创建一棵B+树,如下所示:
其中蓝色的是非聚簇索引值即C2列,黄色是聚簇索引值即C1列,粉色是页号
在这里插入图片描述

6.2.1 这个B+树与聚簇索引有几处不同:
  • 页内的记录是按照从c2列的大小顺序排成一个单向链表

  • 页和页之间也是根据页中记录的c2列的大小顺序排成一个双向链表

  • 非叶子节点存储的是记录的c2列+页号

  • 叶子节点存储的并不是完整的用户记录,而只是c2列+主键这两个列的值。

  • 一张表可以有多个非聚簇索引:

在这里插入图片描述

  • 每次给字段建一个新索引, 字段中的数据就会被复制一份出来, 用于生成索引。 因此, 给表添加索引,会增加表的体积, 占用磁盘存储空间。
6.2.2 非聚簇索引的查找逻辑

根据c2列的值查找c2=4的记录,查找过程如下:

  1. 根据根页面44定位到页42(因为2 ≤ 4 < 9
  2. 由于c2列没有唯一性约束,所以c2=4的记录可能分布在多个数据页中,又因为 2 ≤ 4 ≤ 4,所以确定实际存储用户记录的页在页34和页35中。
  3. 在页34和35中定位到具体的记录
  4. 但是这个B+树的叶子节点只存储了c2和c1(主键)两个列,所以我们必须再根据主键值去聚簇索引中再查找一遍完整的用户记录。
    在这里插入图片描述
6.2.3 非聚簇索引存在的问题:

非聚集索引叶节点仍然是索引节点(包括普通索引列和主键索引列),只是有一个指针指向对应的数据块,此如果使用非聚集索引查询,而查询列中包含了其他该索引没有覆盖的列,那么他还要进行第二次的查询,查询节点上对应的数据行的数据。

我们可以通过使用覆盖(联合)索引,来避免回表问题。

6.3 覆盖(联合)索引(避免回表):

  • 如果为一个索引指定两个字段, 那么这个两个字段的内容都会被同步至索引之中。
  • 比如对a,b,c三个字段创建了索引,那么就相当于创建了三个索引:
    • a
    • a,b
    • a,b,c
  • 所以能不用select *,能用到覆盖索引的时候,就用覆盖索引查询结果
  • 例://查询生日在1991年11月1日出生用户的用户名select user_name from user_info where birthday = '1991-11-1'
    • 非聚集索引:
      create index index_birthday on user_info(birthday);
      查询过程:先通过索引找主键,再通过主键找到数据(回表)
    • 覆盖索引:
      create index index_birthday_and_user_name on user_info(birthday, user_name);
      查询过程:通过非聚集索引index_birthday_and_user_name查找birthday等于1991-11-1的叶节点的内容,然而, 叶节点中除了有user_name表主键ID的值以外, user_name字段的值也在里面, 因此不需要通过主键ID值的查找数据行的真实所在, 直接取得叶节点中user_name的值返回即可。 通过这种覆盖索引直接查找的方式, 可以省略不使用覆盖索引查找的后面两个步骤, 大大的提高了查询性能,
      在这里插入图片描述

7. InnoDB索引与MyISAM索引实现的区别

  • MyISAM的索引方式都是非聚簇的,InnoDB只包含1个聚簇索引

  • 在InnoDB存储引擎中,我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录,而在MyISAM中却需要进行一次回表操作,意味着MyISAM中建立的索引相当于全部都是二级索引 。

  • InnoDB的索引文件本身就是数据文件,而MyISAM索引文件和数据文件是分离的 ,索引文件仅保存数据记录的地址。

    • MyISAM的表在磁盘上存储在以下文件中: *.sdi(描述表结构)*.MYD(数据)*.MYI(索引)
    • InnoDB的表在磁盘上存储在以下文件中: .ibd(表结构、索引和数据都存在一起)
  • InnoDB的非聚簇索引data域存储相应记录主键的值 ,而MyISAM索引记录的是地址 。换句话说,InnoDB的所有非聚簇索引都引用主键作为data域。

  • MyISAM的回表操作是十分快速的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。

  • InnoDB要求表必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型

7.1 innodb的B+树存储结构

叶子节点直接存储着数据:(聚簇索引)
在这里插入图片描述

7.2 Myisam的B+树存储结构

叶子节点存储的是数据的地址
在这里插入图片描述

8. 索引下推:Index Condition Pushdown

MySQL 5.6引入了索引下推优化,默认开启,使用SET optimizer_switch = 'index_condition_pushdown=off';可以将其关闭。官方文档中给的例子和解释如下:

people表中(zipcode,lastname,firstname)构成一个索引

SELECT * FROM people WHERE zipcode=‘95054’ AND lastname LIKE ‘%etrunia%’ AND address LIKE ‘%Main Street%’;

  • 1
  • 2
  • 3

如果没有使用索引下推技术,则MySQL会通过zipcode='95054'从存储引擎中查询对应的数据,返回到MySQL服务端,然后MySQL服务端基于lastname LIKE '%etrunia%'和address LIKE '%Main Street%'来判断数据是否符合条件。

如果使用了索引下推技术,则MYSQL首先会返回符合zipcode='95054'的索引,然后根据lastname LIKE '%etrunia%'和address LIKE '%Main Street%'来判断索引是否符合条件。如果符合条件,则根据该索引来定位对应的数据,如果不符合,则直接reject掉。有了索引下推优化,可以在有like条件查询的情况下,减少回表次数。

总结:

  • 未开启索引下推:

    • 根据筛选条件在索引树中筛选第一个条件
    • 获得结果集后回表操作
    • 进行其他条件筛选
    • 再次回表查询
  • 开启索引下推:在条件查询时,当前索引树如果满足全部筛选条件,可以在当前树中完成全部筛选过滤,得到比较小的结果集再进行回表操作

9. 最左匹配原则:

顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。

  • 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,只有abc可以用到索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
  • =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
  • 两个字段(name,age)建立联合索引,如果where age=12这样的话,是没有利用到索引的,这里我们可以简单的理解为先是对name字段的值排序,然后对age的数据排序,如果直接查age的话,这时就没有利用到索引了,查询条件where name=’xxx’ and age=xx 这时的话,就利用到索引了,再来思考下where age=xx and name=’xxx‘ 这个sql会利用索引吗,按照正常的原则来讲是不会利用到的,但是查询优化器会进行优化,把位置交换下。这个sql也能利用到索引了
  • 三个字段加上联合索引(a,b,c),如果查询a = 1 and c = 2;这种情况只有a会用到索引,c不会
  • 如果是数字类型字符串,没有加单引号,也可以查询结果,但是会造成索引失效

9.1 查询优化器优化联合索引实测:

表字段中有exam_id,paper_id,user_id,三个字段;

对他们创建联合索引:

在这里插入图片描述
查询测试:

  1. 正常排序:abc
explain select * from e_user_exam where exam_id = 372 and paper_id = 372 and user_id = 184524124736049152;

 
 
  • 1

在这里插入图片描述
2. 正常排序ab

explain select * from e_user_exam where exam_id = 372 and paper_id = 372
 
 
  • 1

在这里插入图片描述
3. 正常排序b:

explain select * from e_user_exam where paper_id = 372

 
 
  • 1

在这里插入图片描述

  1. 异常排序ba:
explain select * from e_user_exam where paper_id = 372 and exam_id = 372;

 
 
  • 1

在这里插入图片描述

  1. 异常排序bac:
explain select * from e_user_exam where paper_id = 372 and exam_id = 372 and user_id = 184524124736049152 ;

 
 
  • 1

在这里插入图片描述

  1. 异常排序ac:
explain select * from e_user_exam where exam_id = 372 and user_id = 184524124736049152 ; 

 
 
  • 1

在这里插入图片描述
7. 异常排序ca:

explain select * from e_user_exam where user_id = 184524124736049152 and exam_id = 372

 
 
  • 1

在这里插入图片描述
8. 异常排序bc:

explain select * from e_user_exam where  paper_id = 372 and user_id = 184524124736049152;

 
 
  • 1

在这里插入图片描述

  1. 异常排序cba:
explain select * from e_user_exam where  user_id = 184524124736049152 and paper_id = 372 and exam_id = 372 ; 

 
 
  • 1

在这里插入图片描述

9.2 结果分析:

  • 创建abc联合索引,等于创建了abc、ab、a三个索引
  • 通过结果分析,只要用到了这三个索引,索引前后顺序并不会对索引是否使用造成影响
  • 只有没有用到这三个索引中的任何一个,才会造成索引失效,比如ac,只会用到索引a
  • ba用到了索引ab,所以可以使用索引

10. 索引失效原因:

  1. 如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因),即where a = 1 or b = 1如果a加了索引,b没有加索引,这里a的索引还是不会生效的
    注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引,就是说如果要索引生效,必须要单独使用
  2. like查询是以%开头。(以%结尾还是会生效的,如"wl%")
    解决:使用覆盖索引:
--id,name,age;对name 创建索引
select *  from user where  name like '%明'
--type=all
select name,id  from user where  name like '%明'
--type=index

– 没有高效使用索引是因为字符串索引会逐个转换成accii码,
– 生成b+树时按首个字符串顺序排序,类似复合索引未用左列字段失效一样,
– 跳过开始部分也就无法使用生成的b+树了

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  1. 对于多列索引,不是使用的第一部分,则不会使用索引
  2. 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
EXPLAIN SELECT * FROM emp WHERE name='123'; 
EXPLAIN SELECT * FROM emp WHERE name= 123; --索引失效

 
 
  • 1
  • 2
  1. 普通使用not in不会使用索引,in可以使用索引;如果是主键索引无论是in还是not in都会走索引
  2. 不符合最左匹配原则
  3. 数据类型出现隐式转化(如varchar不加单引号的话可能会自动转换为int型)
  4. mysql判断全表扫描比使用索引更快的时候,比如重复情况较多的字段并且正标数据不多的时候
  5. 在order by时,select的字段出现了非索引字段
  6. ‘=’号两边的字符集或者排序规则不一致(重要,我就是因为这个问题找了好久失效原因,不光数据库的字符集要一致、表的和字段的字符集也要一致
  7. 计算、函数导致索引失效
-- 显示查询分析
EXPLAIN SELECT * FROM emp WHERE emp.name  LIKE 'abc%';
EXPLAIN SELECT * FROM emp WHERE LEFT(emp.name,3) = 'abc'; --索引失效

 
 
  • 1
  • 2
  • 3
  1. 不等于(!= 或者<>)索引失效
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name = 'abc' ;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name <> 'abc' ; --索引失效

 
 
  • 1
  • 2
  1. IS NOT NULL 失效 和 IS NULL
EXPLAIN SELECT * FROM emp WHERE emp.name IS NULL;
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL; --索引失效

 
 
  • 1
  • 2

注意:当数据库中的数据的索引列的NULL值达到比较高的比例的时候,即使在IS NOT NULL 的情况下 MySQL的查询优化器会选择使用索引,此时type的值是range(范围查询)

-- 将 id>20000 的数据的 name 值改为 NULL
UPDATE emp SET `name` = NULL WHERE `id` > 20000;

– 执行查询分析,可以发现 IS NOT NULL 使用了索引
– 具体多少条记录的值为NULL可以使索引在IS NOT NULL的情况下生效,由查询优化器的算法决定
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

11. 索引字段包含null值问题:

11.1 如果表中有字段为null,又被经常查询该不该给这个字段创建索引?

应该创建索引,使用的时候尽量使用is null判断。

  • IS NOT NULL 失效 和 IS NULL
EXPLAIN SELECT * FROM emp WHERE emp.name IS NULL;
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL; --索引失效

 
 
  • 1
  • 2

注意:当数据库中的数据的索引列的NULL值达到比较高的比例的时候,即使在IS NOT NULL 的情况下 MySQL的查询优化器会选择使用索引,此时type的值是range(范围查询)

-- 将 id>20000 的数据的 name 值改为 NULL
UPDATE emp SET `name` = NULL WHERE `id` > 20000;

– 执行查询分析,可以发现 IS NOT NULL 使用了索引
– 具体多少条记录的值为NULL可以使索引在IS NOT NULL的情况下生效,由查询优化器的算法决定
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

11.2 有字段为null索引是否会失效?

不一定会失效,每一条sql具体有没有使用索引 可以通过trace追踪一下

最好还是给上默认值

数字类型的给0,字符串给个空串“”,

12. group by 分组和order by在索引使用上有什么区别?

group by 使用索引的原则几乎跟order by一致 ,唯一区别:

  • group by 先排序再分组,遵照索引建的最佳左前缀法则
  • group by没有过滤条件,也可以用上索引。Order By 必须有过滤条件才能使用上索引。
;