索引底层原理

create table t1 (
    a int primary key,
    b int,
    c int,
    d int,
    e varchar(20)
) engine = innodb;

insert into t1 value(4, 3, 1, 1, 'd');
insert into t1 value(1, 1, 1, 1, 'a');
insert into t1 value(8, 8, 8, 8, 'h');
insert into t1 value(2, 2, 2, 2, 'b');

insert into t1 value(5, 2, 3, 5, 'e');
insert into t1 value(3, 3, 2, 2, 'c');
insert into t1 value(7, 4, 5, 5, 'g');
insert into t1 value(6, 6, 4, 4, 'f');

1、页

页是InnoBD存储引挚管理数据库的最小磁盘单位(逻辑单位),遵循计算机的局部性原理

1、局部性原理

局部性原理又表现为:时间局部性和空间局部性。

  • 时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。
  • 空间局部性是指一旦程序访问了某个存储单元,则不久之后,其附近的存储单元也将被访问。
2、磁盘IO与预读

考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。(在InnoDB一页的大小是16KB)

3、InnoDB数据页由以下7个部分组成
  • FIle Header(文件头)
  • Page Header(页头)
  • Infimun 和 Supremum Records
  • User Records(用户记录,即行记录)
  • Free Space(空闲空间)
  • Page Directory(页目录)
  • File Trailer(文件结尾信息)
image.png

特别说明:
①:主键索引,b+树中会存储完整记录,即每一个页的每个数据项记录的是某一个行的完整信息
②:二级索引,b+数中不会存在完整记录,即每一页的每个数据项记录的是某一行的主键索引(注意,这里记录的是一个索引,还需要回表到主键索引对应的B+树去查)
③:页目录的作用:通过槽对用户数据区域的数据项进行划分,以多少个数据项为一组,而槽记录的是该组第一个数据项的地址偏移量。当查找一个数据时,具体的执行流程在下面
④:用户数据区域的数据项是通过某个值进行排序的,主键索引通过主键值排序,其他二级索引通过特定的索引值排序

4、在某一页中的查找数据的执行流程

通过某个索引值去查找数据时,找到该页时,通过二分法在页目录中找到对应的槽,在槽维护的区间中逐个遍历寻找,找到后返回

例如:

select * from t1 where a = 3
image.png

插入操作
在图中,加入用户数据区域只能存4条数据,当插入(5,2,3,5,'e')时,会把(5,2,3,5,'e')插入到该页中,而将(8,8,8,8,'h')调整到下一页

image.png

两页数据的存储情况

image.png

2、InnoDB 存储引擎使用B+树结构

image.png

如果数据中数据极少,只有一两条记录,完全没有必要用B+树,B+树是需要去维护的,需要一定的成本,占用一定的内存,B+树是随着数据的增加慢慢生成出来的,当数据进行插入的时候,是根据B+树的特性进行插入,慢慢呈现出一棵B+树

其中,每个大框框都是代表一个页,指针指向的是一个页

1、索引的本质是什么

索引是mysql高效去获取数据并排好序的数据结构,数据结构指的是组织数据的方式。例如,主键值数据是以B+树进行组织的,因此这个B+树就是一个主键索引。

每一个索引在 InnoDB 里面对应一棵 B+ 树。

**,该B+树的叶结点是存放具体数据的,

2、聚簇索引 与 非聚簇索引
(1)聚簇索引

1、以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐式的主键。这是因为 InnoDB 是把数据存放在 B+ 树中的,而 B+ 树的键值就是主键,在 B+ 树的叶子节点中,存储了表中所有的数据。

2、即每个表都会有一棵通过主键值建立出来的B+树(主键索引),主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引

(2)非聚簇索引

以主键以外的列值作为键值构建的 B+ 树索引,且非主键索引的叶子节点内容是主键的值,在 InnoDB 里,我们称之为非聚集索引。也称 非主键索引 或者 二级索引

3、当某个表中如果没有主键怎么办?

如果没有主键索引,就把唯一索引作为主键索引;如果没有唯一索引,则会对每一行生成一个隐式字段row_id,通过他们作为主键索引

注意:必须需要一个唯一确定该表的主键索引,整棵B+树是通过主键索引的排序规律进行插入,寻找的

4、聚簇索引(主键索引)例子

(1)精确查询

explain select * from t1 where a = 1;

分析:由于a是主键,因此可以通过主键索引进行查询

(2)范围查询

explain select * from t1 where a > 1;

分析:也是通过主键索引进行查询,查到a = 1的位置对应的叶结点,通过叶结点继续往后找就可以了

5、非聚簇索引(主键索引)例子

(1)在只有主键索引的情况下,查找如下

explain select * from t1 where b = 2 and c = 3 and d = 5;

分析:会通过全表扫描的方式查询,即通过主键索引搜到最小的页,从左往右扫描

(2)如果上面的查询语句是我们经常使用的查询,我们可以添加一个索引,再通过该索引idx_t1_bcd进行查询,该索引idx_t1_bcd也会生成一棵B+树,存的数据是该行的主键。同时数据的排序规则是,先对b排序,相同再对c排序,相同再对d排序。如图所示

create index idx_t1_bcd on t1(b, c, d);//建立一个bcd索引

explain select * from t1 where b = 2 and c = 3 and d = 5;
image.png

分析:通过索引idx_t1_bcd找到b = 2,c = 3,d = 5对应是数据是主键值是5,即a = 5,再通过该主键值继续通过主键索引去寻找具体的行

注意:非聚簇索引存储的是索引的数据(b,c,d)以及主键是数据(a),要寻找其他的数据需要继续通过聚簇索引(主键索引)查找,这样的操作叫做回表(特别重要)

覆盖索引

InnoDB存储引擎支持覆盖索引(covering index,或称索引覆盖),即从辅助索引中就可以得到查询记录,而不需要查询聚集索引中的记录。

使用覆盖索引的一个好处是:辅助索引不包含整行记录的所有信息,故其大小要远小于聚集索引,因此可以减少大量的IO操作

(3)如果条件也是b,c,d,只不过顺序不同,会怎么样查询呢?

explain select * from t1 where c = 1 and d = 1 and b = 1;

分析:也是会通过索引idx_t1_bcd进行查询,因为查询的时候在Server层会经过一个优化器,该优化器会对条件c,d,b进行调优再决定使用哪个索引去查询,最终选择索引idx_t1_bcd进行查询(例如,可以通过条件顺序调整成固定顺序b,c,d的形式)

(4)如果条件只是c和d,会怎么样查询呢??

explain select * from t1 where c = 1 and d = 1 ;

分析:会通过全表扫描进行查询,原因是因为b不知道,导致如果用索引idx_t1_bcd去查询的时候,不知道是向左边找还是向右边找,因此只能全表扫描

这就是最左前缀原则

B+ 树这种索引结构,可以利用索引的“最左前缀”,来定位记录。如果想使用哪个索引,一定要提供这个字段的最左边的字段,因为是按照最左边的字段顺序排序的

(5)如果条件只是b和d,会怎么样查询呢??

explain select * from t1 where b = 1 and d = 1 ;

分析:先通过索引idx_t1_bcd寻找到b=1的第一个页,因为哪些符合还不知道,因此在b=1的第一个页中开始往后遍历,当每一次都满足d也等于1(例如111),则拿到该数据项的主键值去回表查询,直到b不等于1为止就停止

这就是索引下推

在找到最左前缀对应的ID时,在非聚簇索引中开始往后找,满足条件的则进行回表(并不是每个都回表,只有满足条件才回表查询)

在 MySQL 5.6 之前,只能从 ID3 开始一个个回表。到主键索引上找出数据行,再对比字段值。
而 MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。

(6)如果条件只是b和e,会怎么样查询呢??

explain select * from t1 where b = 1 and e = 1 ;

分析:先通过索引idx_t1_bcd寻找到 b=1 的第一个页的第一个主键值,然后再在索引idx_t1_bcd中往后扫,每当满足b = 1的就回表查询,回表查询后发现e=1则返回数据,

下面的分析是错误的,由于主键索引是以a为键值,b = 1并不是在主键索引中是连续的,因此是错误的
先通过索引idx_t1_bcd寻找到 b=1 的第一个页的第一个主键值,再进行回表,找到主键索引中第一个满足条件的页的第一项,从左往右扫描,直到b不等于1为止

(7)如果条件只是b > 1,会怎么样查询呢??

explain select * from t1 where b > 1;

分析:优化器会分析情况一和情况二的性能,再做出决策
情况一:
先通过索引idx_t1_bcd寻找到b=1的第一个页的第一个主键值,再在索引idx_t1_bcd中往后扫,每当满足b > 1的就回表查询,(和6一样)

情况二:
可以通过主键索引进行全表查询

(8)如果条件如下

explain select c from t1 where b = 1;

分析:直接在索引idx_t1_bcd找到第一个b = 1的数据项,从左往右扫描,然后直接返回即可(覆盖索引)

(9)如果条件如下

create index idx_t1_e on t1(e);//建立一个e索引

explain select * from t1 where a = 1; (1)
explain select * from t1 where a = '1'; (2)
explain select * from t1 where e = 1;  (3)
explain select * from t1 where e = '1'; (4)

可以很明确指定(1)会走a的索引,(4)会走e的索引
引理:字符串可以自动转数字,而数字不能自动转字符串
因此a = '1'会自动转换成a = 1,因此(2)也是走a的索引
然而e = 1不会自动转成‘1’,因此(4)只能e = 1去全表扫描,将查到的字符串e先将它转成数字,再和1比较

字符串作为索引去建一棵B+树,是通过某一个特定的规律进行字符串排序的,也就是说e = 1不能走idx_t1_e索引去搜,可能idx_t1_e索引的顺序是1,3,2,并非按照1,2,3的顺序排列

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容