Mysql——1.索引
Posted 折叠的饼干
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql——1.索引相关的知识,希望对你有一定的参考价值。
1.索引
是什么?
提到索引必须提到存储引擎(需要了解mysql逻辑架构 1.Server层 2.存储引擎层)
索引的具体实现与存储引擎相关,存储引擎负责将数据持久化在磁盘中,以及提供数据读写接口
值得注意的是,记录是按行进行存储的,但存储引擎则是以数据页为单位进行读取(读取磁盘非常耗时,所以我们尽量一次读取尽可能多的数据)InnoDB数据页的默认大小为16KB(一次最少从磁盘中读取16K的内容到内存中,一次最少把内存中的16K内容刷新到磁盘中)
学过操作系统,就会知道磁盘中数据页的结构和组织
在文件头中存在前向指针和后向指针,将数据页串成一个双向链表,使得数据页之间不需要物理上的连续,而是逻辑上的连续
而用户记录(User Records)的组织则是按照主键顺序组成单向链表,单向链表插入删除非常方便,但是检索效率不高
所以通过数据页中页目录存储用户记录中每组最后一个记录的偏移量(槽),对用户记录起到索引作用,以便于快速找到记录(注意这种方法是查找一个数据页内的方法)
由于数据页中用户记录是有限的,且主键值是有序的,所以我们可以通过二分查找slot查找对应记录
if(check(slot))r=mid;
l=mid+1;
check函数通过将目标值与该槽对应的组的记录的最大值(最后一条记录)进行比较
页目录的创建过程:
1. 将所有的记录划分成几个组,这些记录包括最小纪录和最大记录,但不包括标记为已删除的记录
2. 每个记录组的最后一条记录就是组内最大的记录,并且最后一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned 字段(下图中粉红色字段)
3. 页目录用来存储每组最后一条记录的地址偏移量,这些地址偏移量会按照先后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每个槽相当于指针指向了不同组的最后一个记录。
InnoDB 对每个分组中的记录条数都是有规定的:
- 第一个分组中的记录只能有 1 条记录;
- 最后一个分组中的记录条数范围只能在 1-8 条之间;
- 剩下的分组中记录条数范围只能在 4-8 条之间。
- 对于一个数据页内数据页中的用户记录,可以通过数据页中的页目录中的槽进行查找行记录对应的组从而找到对应的行记录(单个数据页内部索引,用于找到行记录)
- 而数据库中存储大量的数据页,对于多个数据页中的记录,就需要考虑建立新型的索引(多个数据页之间索引,用于找到对应的数据页)
不同的存储引擎的索引采用的数据结构不同
Mysql常用的存储引擎:
- (默认) InnoDB存储引擎 采用 B+Tree 作为索引的数据结构
- MyISAM 存储引擎 支持多种索引数据结构 eg.B+ 树索引、R 树索引、Full-Text 索引。创建表时,创建的主键索引默认使用的是 B+ 树索引。
二者都支持B+树索引,但二者对数据的存储方式不同
B+树非叶子节点存放索引,叶子结点存放数据页
- InnoDB 存储引擎:B+ 树索引的叶子节点保存数据本身;
- MyISAM 存储引擎:B+ 树索引的叶子节点保存数据的物理地址;
- 定位记录在哪一个页也是通过二分法快速定位到包含该记录的页
- 定位到该页后,又会在该页内进行二分快速定位记录所在的分组(slot),最后在分组内遍历查找
为什么要用B+树作为数据库索引?
2. 主键索引,聚簇索引,非聚簇索引,二级索引,索引覆盖,联合索引
是什么?
索引按叶节点存放数据内容可以分为聚簇索引和非聚簇索引
聚簇索引:叶节点存放完整用户数据
- 一定存在且唯一 因为表的数据都是存放在聚簇索引的叶子节点里,所以 InnoDB 存储引擎一定会为表创建一个聚簇索引,且由于数据在物理上只会保存一份,所以聚簇索引只能有一个。
- 如果有主键,默认使用主键作为聚簇索引的索引键
- 如果没有主键,选择第一个不包含NULL值的列作为聚簇索引的索引键
- 如果都没有,会自动生成一个隐式自增id列作为聚簇索引的索引键
非聚簇索引(二级索引):只存放主键值,用于实现非主键的快速搜索
- 可以有多个
- 回表 如果查询的数据包含非主键数据,就需要到聚簇索引中获取数据行(一共查询了两个B+树)
- 索引覆盖 如果查询的数据是主键值,只用在二级索引查,不用回表(一共查询了两个B+树)
联合索引(组合索引) 多个普通字段组合在一起创建的索引
遵循最左匹配原则 按照最左优先的原则进行匹配
比如,如果创建了一个
(a, b, c)
联合索引,如果查询条件是以下这几种,就可以匹配上联合索引:
- where a=1;
- where a=1 and b=2 and c=3;
- where a=1 and b=2;
其他情况都会失效
为什么要用?
怎么用?
索引作用是进行数据查找,但查询条件用到了索隐列并不意味着查询过程一定用到了索引
要使用到索引进行查找,需要避免索引失效的情况,否则会进行全盘扫描
什么情况下索引会失效
1. 对索引使用左、或者左右模糊匹配,且查询内容包含非索引字段
即 like %xx 或like %xx%时
type=ALL代表走了全盘扫描,没有走索引扫描
type=ALL说明利用索引进行了范围查询,走了索引扫描 ,key=index_name即为使用的索引
为什么 like 关键字左或者左右模糊匹配无法走索引呢?
B+树按照索引值排列有序存储,只能根据前缀进行比较
如果使用
name like '%林'
方式来查询,因为查询的结果可能是「陈林、张林、周林」等之类的,所以不知道从哪个索引值开始比较,于是就只能通过全表扫描的方式来查询。
2.对索引使用函数
为什么对索引使用函数,就无法走索引了呢?
因为经过函数计算后的值不是索引原始值,比如说length(name),只能把索引字段的值都抽出来,然后依次进行表达式计算来进行条件判断,因此采用全盘扫描
不过,从 MySQL 8.0 开始,索引特性增加了函数索引,即可以针对函数计算后的值建立一个索引,也就是说该索引的值是函数计算后的值,所以就可以通过扫描索引来查询数据。
alter table t_user add key idx_name_length ((length(name)));
再进行查询就会走索引了
3. 对索引进行表达式计算
为什么对索引进行表达式计算,就无法走索引了呢?
与对索引进行表达式计算失效的原因类似
4. 对索引进行隐式类型转换
结果为1,说明Mysql在遇到字符串和数字比较时,会自动把字符串转为数字,然后再进行比较
(“10”<"9" 10>9)
type=ALL 走全盘扫描
字符串和数字进行比较,将字符串转换为数字,相当于
select * from t_user where CAST(phone AS signed int) = 1300000001;
相当于对索引使用了函数,所以索引失效
type=const key=PRIMARY 走主键索引
id 是int类型,和字符串进行比较,将字符串‘1’转换为1,相当于
select * from t_user where id = CAST("1" AS signed int);
没有对索引使用函数,所以可以走索引扫描
5.联合索引不遵循最左匹配,且查询内容包含非索引字段
特殊查询条件:where a = 1 and c = 3 索引截断
不同版本处理方式不同
- Mysql5.5 a走联合索引,联合索引找到主键值后,开始回表,到主键索引读取数据行,再对比c的值
- Mysql5.6 索引下推 在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数
原理:截断的字段会被下推到存储引擎层进行条件判断(c字段的值是在(a,b,c)联合索引里的),然后过滤出符合条件的数据后再返回给Server层,由于在引擎层过滤掉大量的数据,无需再回表读取数据来进行判断,减少回表次数,提高了性能
为什么联合索引不遵循最左匹配原则就会失效?
原因是,在联合索引的情况下,数据是先按索引第一列排序,第一列数据相同时才会按照第二列排序。
也就是说,如果我们想使用联合索引中尽可能多的列,查询条件中的各个列必须是联合索引中从最左边开始连续的列。如果我们仅仅按照第二列搜索,肯定无法走索引。
6.where子句中的OR
在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。
id 是主键,age 是普通列
解决方法:将age字段设置为索引
思考题:
题目1:
第一条和第二条都会走索引扫描,而且都是选择扫描二级索引(index_name)
第三和第四条会发生索引失效,执行计划的结果 type= ALL,代表了全表扫描。
题目2:
第一条和第二条模糊查询语句也是一样可以走索引扫描
第二条查询语句的执行计划如下,Extra 里的 Using index 说明用上了覆盖索引
而第一和第二条查询语句的执行计划中 type 是range,表示对索引列进行范围查询,也就是利用了索引树的有序性的特点,通过查询比较的方式,快速定位到了数据行。
所以,type=range 的查询效率会比 type=index 的高一些。
第三条查询语句的执行计划(第四条也是一样的结果)
可以看到 key=index_name,也就是说用上了二级索引,而且从 Extra 里的 Using index 说明用上了覆盖索引。
------------------------------------------------------------------------------------------------------------------------
为什么选择全扫描二级索引树,而不扫描全表(聚簇索引)呢?
1. 因为二级索引树的记录东西很少,就只有「索引列+主键值」
聚簇索引记录的东西会更多,比如聚簇索引中的叶子节点则记录了主键值、事务 id(用于事务)和 MVCC 的回流指针以及所有的剩余列。
2. 这个 select * 不用执行回表操作。
所以, MySQL 优化器认为直接遍历二级索引树要比遍历聚簇索引树的成本要小的多,因此 MySQL 选择了「全扫描二级索引树」的方式查询数据。
----------------------------------------------------------------------------------------------------------------------
为什么这个数据表加了非索引字段,执行同样的查询语句后,怎么变成走的是全表扫描呢?
加了其他字段后,
select * from t_user where name like "%xx";
要查询的数据就不能只在二级索引树里找了,得需要回表操作才能完成查询的工作,再加上是左模糊匹配,无法利用索引树的有序性来快速定位数据,所以得在二级索引树逐一遍历,获取主键值后,再到聚簇索引树检索到对应的数据行,这样实在太累了。所以,优化器认为上面这样的查询过程的成本实在太高了,所以直接选择全表扫描的方式来查询数据。
3.count(*) 和 count(1) 有什么区别?哪个性能最好?
count()是聚合函数,函数参数可以为字段名,也可以是其他任何表达式
作用:统计符合查询条件的记录中,参数不为NULL的记录有多少
count(1)
select count(1) from tb;
相当于统计tb表中有多少记录
过程:如果没有二级索引,InnoDB循环遍历聚簇索引,将读到记录返回给server层,但不读取记录中任何字段的值(1!=NULL),所以比count(主键)执行效率高
如果有二级索引,循环遍历二级索引
count(*)
count(\\*)相当于count(0),Mysql会将参数*转化为参数0,所以count(*)和count(1)执行过程基本一样,性能无差异
count(字段)
如果没有二级索引,进行全盘扫描,如果有,采用二级索引进行扫描
所以如过要使用,建议给这个字段建立索引
为什么要通过遍历的方式来计数
MyISAM每张 MyISAM 的数据表都有一个 meta 信息有存储了row_count值,由于表级锁一致性,可以直接读取该值,单count查询复杂度O(1)
InnoDB支持事务,同一时刻的多个查询,由于多版本并发控制(MVCC),InnoDB应该返回的行数不确定
如何优化count(*)
1.近似值
show table status;
explain select count(*) from tb;
--并不真的查询,只会估算
2.额外表保存计数值
新增或删除时,需要维护这个表
以上是关于Mysql——1.索引的主要内容,如果未能解决你的问题,请参考以下文章