MySQL高阶知识点(三):吃透索引【转】
接着之前总结的:索引概述入门,今天全面讲述一下索引相关知识点
1.数据库索引是什么?有什么优缺点?
索引是一种能提高数据库查询效率的有序的数据结构。它可以比作一本字典的目录,可以帮你快速找到对应的记录。索引一般存储在磁盘的文件中,它是占用物理空间的。其优缺点如下:
优点:
- 加快数据查询速度,这也是解决慢SQL,创建索引的主要原因。你可以想象查找一个字没有目录,一页一页去查找和根据目录快速定位到该字在哪一页两者的查询效率对比。
- 创建唯一索引保证数据的唯一性,从而达到业务逻辑层的幂等性。
缺点:
- 创建索引和维护索引要耗费时间,对表中的数据进行增、删、改时都需要动态维护索引。比如说你在字典最后一页新增了一个汉字,你还需要同时维护目录。
- 索引需要占物理空间,除了数据表占用数据空间之外,每一个索引还要占用一定的物理空间,所以不能乱建过多索引,会导致数据库空间占用过大
2.什么是聚簇索引与非聚簇索引?区别是什么?
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。它表示索引结构和数据一起存放的索引。非聚簇索引是索引结构和数据分开存放的索引。
在MySQL
的InnoDB
存储引擎中, 聚簇索引与非聚簇索引最大的区别,在于叶节点是否存放一整行记录。聚簇索引叶子节点存储了一整行记录,而非聚簇索引叶子节点存储的是主键信息,因此,一般非聚簇索引还需要回表查询,覆盖索引不需要。覆盖索引是select
的数据列只用从索引中就能够取得,不必回表,换句话说,查询列要被所建的索引覆盖
- 一个表中只能拥有一个聚簇索引(因为一般聚簇索引就是主键索引),而非聚簇索引一个表则可以存在多个。
- 一般来说,相对于非聚簇索引,聚簇索引查询效率更高,因为不用回表。
而在MyISM
存储引擎中,它的索引都是非聚簇索引,因为数据和索引是分开的,叶子节点data存放是数据记录的指针地址
3.InnoDB索引与MyISAM索引实现的区别是什么?
MyISAM的索引方式都是非聚簇的,与InnoDB包含1个聚簇索引是不同的。
-
在InnoDB存储引擎中,我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录,而在MyISAM中却需要进行一次回表操作,意味着MyISAM中建立的索引相当于全部都是二级索引 。
-
InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是分离的 ,索引文件仅保存数据记录的地址。
- MyISAM的表在磁盘上存储在以下文件中:
*.sdi(描述表结构)
、*.MYD(数据)
,*.MYI(索引)
- InnoDB的表在磁盘上存储在以下文件中:
.ibd(表结构、索引和数据都存在一起)
-
InnoDB的非聚簇索引data域存储相应记录主键的值 ,而MyISAM索引记录的是地址 。换句话说,InnoDB的所有非聚簇索引都引用主键作为data域。
-
MyISAM的回表操作是十分快速的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,相对来说比不MyISAM直接使用地址访问快。
-
InnoDB要求表必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。
4.B+树索引实现原理
MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构
假设有一个表index_demo,表中有2个INT类型的列,1个CHAR(1)类型的列,c1列为主键:
CREATE TABLE index_demo(c1 INT,c2 INT,c3 CHAR(1),PRIMARY KEY(c1)) ;
index_demo表的简化的行格式示意图如下:
我们只在示意图里展示记录的这几个部分:
record_type:
表示记录的类型, 0是普通记录、 2是最小记录、 3 是最大记录、1是B+树非叶子节点记录。next_record:
表示下一条记录的相对位置,我们用箭头来表明下一条记录。各个列的值:
这里只记录在 index_demo 表中的三个列,分别是 c1 、 c2 和 c3 。其他信息:
除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息。
将其他信息
项暂时去掉并把它竖起来的效果就是这样:
把一些记录放到页里的示意图就是(这里一页就是一个磁盘块,代表一次IO):
MySQL InnoDB的默认的页大小是16KB
,因此数据存储在磁盘中,可能会占用多个数据页。如果各个页中的记录没有规律,我们就不得不依次遍历所有的数据页。如果我们想快速的定位到需要查找的记录在哪些数据页中
,我们可以这样做 :
- 下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值
- 给所有的页建立目录项
以页28
为例,它对应目录项2
,这个目录项中包含着该页的页号28
以及该页中用户记录的最小主键值 5
。我们只需要把几个目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键值快速查找某条记录的功能了。比如:查找主键值为 20 的记录,具体查找过程分两步:
- 先从目录项中根据二分法快速确定出
主键值为20的记录在目录项3中
(因为 12 ≤ 20 < 209 ),对应页9
。 - 再到页9中根据二分法快速定位到主键值为 20 的用户记录。
至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为索引
。
我们新分配一个编号为30的页来专门存储目录项记录
,页10、28、9、20专门存储用户记录
:
目录项记录和普通的用户记录的不同点:
- 目录项记录 的 record_type 值是1,而 普通用户记录 的 record_type 值是0。
- 目录项记录只有主键值和页的编号两个列,而普通的用户记录的列是用户自己定义的,包含很多列,另外还有InnoDB自己添加的隐藏列。
现在查找主键值为 20 的记录,具体查找过程分两步:
- 先到页30中通过二分法快速定位到对应目录项,因为 12 ≤ 20 < 209 ,就是页9。
- 再到页9中根据二分法快速定位到主键值为 20 的用户记录。
更复杂的情况如下:
我们生成了一个存储更高级目录项的页33 ,这个页中的两条记录分别代表页30和页32,如果用户记录的主键值在 [1, 320)
之间,则到页30中查找更详细的目录项记录,如果主键值 不小于320 的话,就到页32中查找更详细的目录项记录。**这个数据结构,它的名称是 B+树 **
5.聚簇索引与非聚簇索引b+树实现有什么区别?
5.1聚簇索引
特点:
-
索引和数据保存在同一个B+树中
-
页内的记录
是按照主键
的大小顺序排成一个单向链表
。 -
页和页之间
也是根据页中记录的主键
的大小顺序排成一个双向链表
。 -
非叶子节点存储的是记录的
主键+页号
。 -
叶子节点存储的是
完整的用户记录
。
优点:
- 数据访问更快 ,因为
索引和数据保存在同一个B+树中
,因此从聚簇索引中获取数据比非聚簇索引更快。 - 聚簇索引对于主键的
排序查找
和范围查找
速度非常快。 - 按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于
数据都是紧密相连
,数据库可以从更少的数据块中提取数据,节省了大量的IO操作
。
缺点:
- 插入速度严重依赖于插入顺序 ,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个
自增的ID列为主键
。 - 更新主键的代价很高 ,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义
主键为不可更新
。
限制:
- 只有InnoDB引擎支持聚簇索引,
MyISAM不支持聚簇索引
。 - 由于数据的物理存储排序方式只能有一种,所以
每个MySQL的表只能有一个聚簇索引
。 - 如果没有为表定义主键,InnoDB会选择
非空的唯一索引列代替
。如果没有这样的列,InnoDB会隐式的定义一个主键
作为聚簇索引。 - 为了充分利用聚簇索引的聚簇特性,InnoDB中表的
主键应选择有序的id
,不建议使用无序的id,比如UUID、MD5、HASH、字符串作为主键,无法保证数据的顺序增长。
5.2 非聚簇索引(二级索引、辅助索引)
聚簇索引
,只能在搜索条件是主键值
时才发挥作用,因为B+树中的数据都是按照主键进行排序的,如果我们想以别的列作为搜索条件,那么需要创建非聚簇索引
。
例如,以c2列作为搜索条件
,那么需要使用c2列创建一棵B+树
,如下所示:
这个B+树与聚簇索引有几处不同:
-
页内的记录
是按照从c2列
的大小顺序排成一个单向链表
。 -
页和页之间
也是根据页中记录的c2列
的大小顺序排成一个双向链表
。 -
非叶子节点存储的是记录的
c2列+页号
。 -
叶子节点存储的并不是完整的用户记录,而只是
c2列+主键
这两个列的值。
6.一个b+树中大概能存放多少条索引记录?
MySQL中一个页存放的记录数量是非常大的(默认16KB),假设指针与键值忽略不计(或看做10个字节),数据占 1 kb 的空间:
- 如果B+树只有1层,也就是只有1个用于存放用户记录的节点,最多能存放 16 条记录。
- 如果B+树有2层,最多能存放
1600×16=25600
条记录。 - 如果B+树有3层,最多能存放
1600×1600×16=40960000
条记录。 - 如果存储千万级别的数据,只需要三层就够了
B+树的非叶子节点不存储用户记录,只存储目录记录,相对B树每个节点可以存储更多的记录,树的高度会更矮胖,IO次数也会更少。
7.什么是自适应哈希索引和索引下推
自适应哈希索引
自适应哈希索引是Innodb引擎的一个特殊功能,当MySQL注意到某些索引值被使用的非常频繁时,会在内存中基于B-Tree所有之上再创建一个哈希索引,这就让B-Tree索引也具有哈希索引的一些优点,比如快速哈希查找。这是一个完全自动的内部行为,用户无法控制或配置。
索引下推
select * from user where name like '小%' and age=18;
其中,name
和age
为联合索引(idx_name_age
)
如果是MySQL5.6之前,在idx_name_age
索引树,找出满足条件名字第一个字是“小”
的人,然后依次拿着主键id回表找出数据记录,再去对比年龄是否满足条件,这时候你会age不也在索引中吗,为啥不先判断age是否满足,再回表查询对应的数据记录呢?这样可以减少回表次数提升性能速度。所以MySQL 5.6
就引入了索引下推优化,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
8.如何知道语句是否走索引查询?
explain
查看SQL的执行计划,这样就知道是否命中索引了。
当explain
与SQL
一起使用时,MySQL将显示来自优化器的有关语句执行计划的信息。
一般来说,我们需要重点关注type、rows、filtered、extra、key
。
8.1 type
type表示连接类型,查看索引执行情况的一个重要指标。以下性能从好到坏依次:system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
- system:这种类型要求数据库表中只有一条数据,是
const
类型的一个特例,一般情况下是不会出现的。 - const:通过一次索引就能找到数据,一般用于主键或唯一索引作为条件,这类扫描效率极高,,速度非常快。
- eq_ref:常用于主键或唯一索引扫描,一般指使用主键的关联查询
- ref : 常用于非主键和唯一索引扫描。
- ref_or_null:这种连接类型类似于
ref
,区别在于MySQL
会额外搜索包含NULL
值的行 - index_merge:使用了索引合并优化方法,查询使用了两个以上的索引。
- unique_subquery:类似于
eq_ref
,条件用了in
子查询 - index_subquery:区别于
unique_subquery
,用于非唯一索引,可以返回重复值。 - range:常用于范围查询,比如:between ... and 或 In 等操作
- index:全索引扫描
- ALL:全表扫描
8.2 rows
该列表示MySQL估算要找到我们所需的记录,需要读取的行数。对于InnoDB表,此数字是估计值,并非一定是个准确值。
8.3 filtered
该列是一个百分比的值,表里符合条件的记录数的百分比。简单点说,这个字段表示存储引擎返回的数据在经过过滤后,剩下满足条件的记录数量的比例。
8.4 extra
该字段包含有关MySQL如何解析查询的其他信息,它一般会出现这几个值:
- Using filesort:表示按文件排序,一般是在指定的排序和索引排序不一致的情况才会出现。一般见于order by语句
- Using index :表示是否用了覆盖索引。
- Using temporary: 表示是否使用了临时表,性能特别差,需要重点优化。一般多见于group by语句,或者union语句。
- Using where : 表示使用了where条件过滤.
- Using index condition:MySQL5.6之后新增的索引下推。在存储引擎层进行数据过滤,而不是在服务层过滤,利用索引现有的数据减少回表的数据。
8.5 key
该列表示实际用到的索引。一般配合possible_keys
列一起看。
转自
MySQL高阶知识点(三):吃透索引
https://mp.weixin.qq.com/s?__biz=Mzg5MDY1NzI0MQ==&mid=2247485707&idx=1&sn=fa8abc12e7705ec950dc4d34e51442db&chksm=cfd809bdf8af80ab5afc84361257bfd5bd892bdc4838b63d1120bc4b2436d6d04704596126cd&scene=178&cur_album_id=2577655039234932737#rd