MySql学习(三)索引

索引是什么

官方介绍索引是帮助MySQL高效获取数据的数据结构。更通俗的说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。

索引的优势和劣势

优势

可以提高数据检索的效率,降低数据库的IO成本,类似于书的目录。 – 检索
通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。 --排序
被索引的列会自动进行排序,包括【单列索引】和【组合索引】,只是组合索引的排序要复杂一些。(可以参考后续讲的B+TREE的结构,帮助理解)
如果按照索引列的顺序进行排序,对应order by语句来说,效率就会提高很多。
如果where条件列符合索引的最左前缀原则的话,在存储引擎层会有一层index filter处理。(索引下推 ICP)
如果检索的字段都包含在索引当中可以减少回表查询从而提高效率(索引覆盖

劣势

索引会占据磁盘空间。
索引虽然会提高查询效率,但是会降低更新表的效率。比如每次对表进行增删改操作,MySQL不仅要保存数据,还有保存或者更新对应的索引文件。

索引原理分析

索引的存储结构

  1. 索引是在存储引擎中实现的,也就是说不同的存储引擎,索引会使用不同的数据结构
  2. MyISAM和InnoDB存储引擎:只支持B+ TREE索引, 也就是说默认使用B+TREE,不能够更换
  3. MEMORY/HEAP存储引擎:支持HASH和BTREE索引

B-TREE和B+TREE

在这里插入图片描述

  1. B树是为了磁盘或其它存储设备而设计的一种多叉平衡查找树。
  2. B树的高度一般都是在2-4这个高度,树的高度直接影响IO读写的次数。
  3. 如果是三层树结构—支撑的数据可以达到20G,如果是四层树结构—支撑的数据可以达到几十T B和B+的区别
  4. B树和B+树的最大区别在于非叶子节点是否存储数据的问题。
  5. B树是非叶子节点和叶子节点都会存储数据。
  6. B+树只有叶子节点才会存储数据,而且存储的数据都是在一行上,而且这些数据都是有指针指向
    的,也就是有顺序的

注:所以上文的留下的问题,因为B+树叶子节点是有顺序的且类似于链表的结构,所以会大大的缩短排序的时间,而且是只有包含在索引中的列的排序才能享受到这个待遇。

索引节点与内存还有磁盘存储之间的关系

首先我们先引入页,块,扇区

  1. 内存以页为单位进行IO读取,默认为4k,
  2. 而MySql的Innodb存储引擎也有页的概念,默认为16K,可以设置innodb_page_size来改变这个大小,SSD、PCIE 硬盘在使用时厂商或者优化建议需要把innodb_page_size 更改为4k。(注意,改为4k的话主键最大长度为768 bytes,而且改小的话检索性能增加但是CPU压力也随着增加)
  3. 操作系统以块这个逻辑单位来操作磁盘
  4. 磁盘以扇区为这个做小的物理存储单位去存储数据。

从上图可知,索引在磁盘中是以一个个索引节点也就是磁盘块来存储的。那么一个索引的大小直接关系到了抓取索引节点时的IO次数。比如指针的默认长度是6bit,如果索引是bigint类型,为8bit,那么一个索引是14bit。相当于一次IO可以抓取9362个索引key进行折半查找。那如果你以UUID当主键呢,一个32位char类型的UUID主键是246bit,相当于一次IO可以抓取532个索引key进行折半查找。相当于UUID主键类型检索的话是bigint类型的IO次数的17倍,可想而知检索效率要差的多。

索引字段的设计

结合B+树的特点还有索引的存储结构,我们可得出下面结论

  1. :where条件以及外链接的ON条件尽量加上索引。

因为可以大大的优化检索的效率。

  1. :该项目的值可以很细化的区分数据。

越能区分出数据,索引的效果越好。例如,存放出⽣⽇期的列具有不同值,很容易区分各⾏。⽽⽤来记录性别的列,只含有“0”和“1”,则对此列进⾏索引没有多⼤⽤处,因为不管 搜索哪个值,都会得出⼤约⼀半的⾏。

  1. :尽量使用短的列作为索引。

如果对字符串列进⾏索引,应该指定⼀个前缀长度,只要有可能就应该这样做。例如,有⼀个 CHAR(200)列,如果在前10个或20个字符内,多数值是唯⼀的,那么就不要对整个列进⾏索引。对前10个或20个字 符进⾏索引能够节省⼤量索引空间,也可能会使查询更快。较⼩的索引涉及的磁盘 IO 较少,较短的值⽐较起来更 快。更为重要的是,对于较短的键值,索引⾼速缓存中的块能容纳更多的键值,因此,MySQL 也可以在内存中容 纳更多的值。这样就增加了找到⾏⽽不⽤读取索引中较多块的可能性。

  1. :不要过度索引。

索引不宜过多。因为每个额外的索引都要占⽤额外的磁盘空间,并降低写操作的性能。在修改表的内容时,索引必须进⾏更新,有时可能需要重构,因此,索引越多,所花的时间越长。如果有⼀个索引很少利⽤或从不使⽤,那么会不必要地减缓表的修改速度。此外,MySQL 在⽣成⼀个执⾏计划时,要考虑各个索引,这也要花费时间。创建多余的索引给查询优化带来了更多的⼯作。索引太多,也可能会使MySQL选择不到所要使⽤的最好索引。只保持所需的索引有利于查询优化。

  1. :建议设定自增的int类型的主键。

对于InnoDB存储引擎的表,记录默认会按照⼀定的顺序保存,如果有明确定义的主键,则按照主键顺序保存。如果没有主键,但是有唯⼀索引,那么就是按照唯⼀索引的顺序保存。如果既没有主键又没有唯⼀索引,那么表中会⾃动⽣成⼀个内部列,按照这个列的顺序保存。

因为B+树中所有节点都是排过序的,新插入进来的索引KEY如果不是自增的话,有可能插入到中间的叶子节点中,导致后边的叶子节点的需要更新(相当于链表的插入操作),如果使用自增key就可以避免这个问题。

非聚集索引(MyISAM)

MyISAM索引(.myi)和数据(,myd)是分开存放的,MyISAM的B+树种叶子节点存放的是数据在数据文件(.myd)的指针位置

在物理结构篇中讲述了MyISAM中的存储结构和上述结构也有一个对照与论证

.frm文件:主要存放与表相关的数据信息,主要包括表结构的定义信息
.myd文件:主要用来存储表数据信息。
.myi文件:主要用来存储表数据文件中任何索引的数据树。

在这里插入图片描述
其中test表使用的是MyISAM引擎,结构如上图所示。

主键索引

![在这里插入图片描述](https://img-blog.csdnimg.cn/2020030118593943.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzI5NjA5OTYx,size_16,color_FFFFFF,t_70

辅助索引(非主键索引)

MyISAM的辅助索引与主键索引是相同的。存放的都是索引值与数据地址的键值对。

聚集索引(InnoDB)

主键索引

在这里插入图片描述

辅助索引

在这里插入图片描述
InnoDB索引数据是存放在一起的,InnoDB的B+树种叶子节点中主键索引存放的是完整的数据,而辅助索引中存放的是索引值与主键值。

在物理结构篇中讲述了InnoDB中的存储结构和上述结构也有一个对照与论证

1 .frm文件:主要存放与表相关的数据信息,主要包括表结构的定义信息.
2. .ibd:使用独享表空间存储表数据和索引信息,一张表对应一个ibd文件。

在这里插入图片描述
其中user表使用的是InnoDB引擎,结构如上图所示。

一些名词解释

回表

结合InnoDB的索引数据结构,由于非主键查询,第一次仅能得到索引的值以及对应主键的值,需要回到主键的索引树种查询对应的行记录,这种行为就叫做回表

索引覆盖

索引覆盖是针对回表来说的,检索所需的项目都包含在索引中,那么就不需要回表查询,这样的效率相对于回表查询要高。是否用到了索引覆盖可以根据查询计划中 extra的值来判断。
根据上面的InnoDB索引文件结构,索引中存放的是主键值,或者辅助索引的值,如果你查询只需要键值,那么直接就可以得到就不需要再根据索引中存放的主键再去检索主键索引了。
举个例子:

CREATE TABLE `tuser`  (
  `id` int(11) NOT NULL,
  `NAME` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  `sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `address` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `idx_name_age`(`NAME`, `age`) USING BTREE,
  INDEX `idx_sex`(`sex`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

表结构如上述所示
执行查询,结果如下(不同数据库版本可能有所不同,我用的版本是5.7.24
在这里插入图片描述
这里就是用到了索引覆盖,如果我把select项目改成*,或者不符合最做前缀原则那么,就需要回表进行查询了
在这里插入图片描述
在这里插入图片描述
重点: 索引覆盖是针对一个索引来说的,比如tuser表,单独使用主键索引,或者辅助索引用到了索引覆盖都是可以的,但是要根据主键值查询name则不行。
在这里插入图片描述
在这里插入图片描述
可以看到这两个查询都用到了所以覆盖,一个是索引idx_name_age的索引覆盖,一个是主键的索引覆盖。
但是我如果根据主键值查询name则无法进行索引覆盖。
在这里插入图片描述
再注意: 索引覆盖不要理解有误区,只要是能从当前索引树上取得所有的信息不需要再去查询主键索引树就是索引覆盖。就如下图所示,idx_name_age树中节点中存放的键值对为(name+age : id)所以检索id即使不符合idx_name_age的最左前缀原则,也是属于索引覆盖的范畴。
在这里插入图片描述

索引下推

在MySql5.6之前检索分为两个步骤

  1. 根据最左前缀原则在存储引擎层确定index key的检索范围,然后将回表读取这些数据
  2. 返回给SQL SERVER层再根据剩下的where条件进行过滤

而在MySql5.7之后,把第二步一部分操作下推到存储引擎层,即可以根据索引上的值过滤一些不符合条件的数据(称为 index filter),再返回到SQL SERVER层进行过滤(称为 table filter)过程如下

  1. 根据最左前缀原则在存储引擎层确定index key的检索范围
  2. 根据索引过滤掉一些数据,再回表查询读取
  3. 返回给SQL SERVER层再根据剩下的where条件进行过滤
    这一改动就称之为索引下推

直观体验就是执行计划中 extra 提示Using index condition
在这里插入图片描述
脑筋急转弯(大雾)
如果把上面的*改成name,age进行索引覆盖还会出现索引下推么?

答案是:
不会出现,看原理我们得出索引下推是出现回表查询的时候才会用到的技术,那么索引覆盖不会回表,那么就不会出现索引下推。

在这里插入图片描述

索引下推的好处

直接在存储引擎层过滤,省去了这些记录回表查询以及返回到SQL SERVER曾的开销。

索引的选择

  1. 索引优先选择包含所有信息的列,如果有多个索引都包含全部信息,存储引擎自动选择键值最短的索引,这个跟存储引擎的工作机制有关,存储引擎一下子读取多条数据的键值进行比对,每次读取到内存中的大小是一定的,那么键值越短,每次读取的条数越多,IO越少,效率越高。
CREATE TABLE `tuser` (
  `id` int(11) NOT NULL,
  `NAME` varchar(100) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `sex` char(1) DEFAULT NULL,
  `address` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_name_age` (`NAME`,`age`),
  KEY `idx_sex` (`sex`),
  KEY `idx_age_address` (`age`,`address`),
  KEY `idx_sex_address` (`sex`,`address`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

如果我要单一查找address,该信息在idx_age_address和idx_sex_address中都是存在的且都不需要回表查询,而idx_age_address键长为308,idx_sex_address为307所以会选择idx_sex_address
在这里插入图片描述
2. SQL优化器会自动帮你优化条件顺序,以最左前缀原则选择合适的索引
在这里插入图片描述

索引失效

讲索引失效之前,你要牢记索引的存储结构,就是

  1. 主键索引是主键值与整个行记录信息。
  2. 辅助索引存放的是辅助索引所包含的列的信息与主键值。

结合上边的例子,我们插入几条数据

INSERT INTO `tuser` VALUES (1, 'zhangsan', 20, '0', '致真 大厦');
INSERT INTO `tuser` VALUES (2, 'lisi', 23, '0', '致真 大厦');
INSERT INTO `tuser` VALUES (3, 'wangwu', 22, '0', '致真 大厦');
INSERT INTO `tuser` VALUES (4, 'zhuliu', 21, '0', '致真 大厦');

那么索引PRIMARY(主键索引)中存放的结构大体如下:
在这里插入图片描述
索引idx_name_age中存放的结构大体如下:
在这里插入图片描述
(一定都是排过序的)

不符合最左前缀原则

1. 想要利用索引,索引的第一项必须在检索条件中。
2. 如果断开只能利用到断开前的那一项。
3. SQL条件与你写的位置无关,SQL优化器会自动帮你优化条件顺序
口诀就是
带头索引不能死,中间索引不能断

其实根据键值很好理解比如,我想查询name=lisi的值,虽然索引idx_name_age键值是name和age拼接起来的,但是我依然可以根据键值是否以lisi开头来快速过滤出来。相反的我只检索age=20即使idx_name_age中里面有age字段但是在键值中间所以就无法使用该索引快速过滤只能全文检索了。
下面举几个例子(以name和age项组合索引为例),多图预警

  1. 利用到全部索引
    在这里插入图片描述
  2. 索引从中间断开只用到了name(注意看key与key_len项的值)
    在这里插入图片描述
  3. 索引从开头就断开了
    在这里插入图片描述
  4. SQL优化排序过后符合的例子
    在这里插入图片描述

在索引上做运算

在索引上进行下列操作都会使索引失效

  1. 计算
  2. 函数
  3. 手动或自动类型转换

下面举几个例子(还是以name和age项组合索引为例)

  1. 在索引上做计算
    在这里插入图片描述
  2. 函数
    在这里插入图片描述
  3. 自动或手动类型转换
    在这里插入图片描述

使用范围条件

适用范围条件都会使索引失效包括 > ,< , <>, bettween,in等
在这里插入图片描述

在这里插入图片描述
不等号连索引下推都不行了
不等号连索引下推都不行了

在所以上判断null

主键索引上判断null会导致索引失效从而全表检索
辅助索引上判断is null 会使用索引下推。
辅助索引上判断is not null 会导致索引失效从而全表检索

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

like 字段使用同配符开头

在这里插入图片描述

在这里插入图片描述

使用or

使用or作为条件会是索引失效进行全文检索
在这里插入图片描述

字符集不一致(mysql)

在MySQL中如果left join的字段字符集不一致也会导致索引失效

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加班狂魔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值