不会吧不会吧,难道还有人不了解MySQL索引底层原理?

MySQL索引底层原理

什么是索引?

之前我们也讲过了(我的上面一篇博文中提到的关于索引数据结构的选择),索引就是一种帮助mysql提高查询效率的数据结构,而且重点是排好序数据结构

索引的优点

  • 大大加快数据查询速度

索引的缺点

  • 维护索引需要耗费数据库资源
  • 索引需要占用磁盘空间
  • 当对表的数据进行增删改查的时候,需要维护索引,因此速度会受到影响

索引分类

  • 主键索引

    设定为主键后数据库会自动建立索引,innodb为聚簇索引(这个后面会讲到)

-- 主键索引 自动创建
create table t_user
(
    id varchar(20) primary key ,
    name varchar(20)
);
-- 查看索引
show index from t_user;

在这里插入图片描述

  • 单值索引

    即一个索引只包含单个列,一个表可以有多个单列索引

-- 建表时候创建
create table t_user
(
    id varchar(20) primary key ,
    name varchar(20),
    key (name) -- 注意:随表一起建立的索引,索引名与列名一致
);

show index from t_user;

-- 建表后创建
create index name_index on t_user(name);

-- 删除索引
drop index name_index on t_user;

在这里插入图片描述

  • 唯一索引

    索引列的值必须唯一,但是允许有空值

-- 建表时候创建
create table t_user
(
    id varchar(20) primary key ,
    name varchar(20),
    unique (name) -- 注意:随表一起建立的索引,索引名与列名一致
);

-- 建表后创建
create unique index name_index on t_user(name);

在这里插入图片描述

注意唯一索引是可以存入NULL值的,并且可以存在多个NULL值(官网是这么描述的:A UNIQUE index permits multiple NULL values for columns that can contain NULL.)

在这里插入图片描述

  • 复合索引

    即一个索引包含多个列

-- 建表时候创建
create table t_user
(
    id varchar(20) primary key ,
    name varchar(20),
    age int,
    key (name,age) -- 注意:随表一起建立的索引,索引名与列名一致
);

-- 建表后创建
create  index name_age_index on t_user(name,age);

在这里插入图片描述

  • 全文索引

    主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配。fulltext索引配合match against操作使用,而不是一般的where语句加like。

可以参考一下官方文档:https://dev.mysql.com/doc/refman/5.7/en/create-index.html

索引的底层原理

上面的都是我们工作中,做项目中常常在应用层面中接触到的索引相关的知识啦,基本我们对于优化SQL的时候大多数时候都是加索引,下面就来从底层上来讲一下索引的原理。

我们还是使用上面使用到的表

create table t_user
(
    id varchar(20) primary key ,
    name varchar(20)
);

然后我们顺序执行以下的sql

insert into t_user(id,name) values('2','a');
insert into t_user(id,name) values('4','b');
insert into t_user(id,name) values('3','c');
insert into t_user(id,name) values('5','d');
insert into t_user(id,name) values('1','e');
insert into t_user(id,name) values('7','f');
insert into t_user(id,name) values('6','g');

然后我们通过查询语句来进行查询

select * from t_user;

然后我们可以发现我们的结果,是根据id排好序的,为什么插入是顺序的,而查询是排好序的呢?

在这里插入图片描述

然后我们如果不设置主键的话,我们可以看到顺序是按照插入的顺序的,这是为什么呢?

create table t_user
(
    id varchar(20),
    name varchar(20)
);


insert into t_user(id,name) values('2','a');
insert into t_user(id,name) values('4','b');
insert into t_user(id,name) values('3','c');
insert into t_user(id,name) values('5','d');
insert into t_user(id,name) values('1','e');
insert into t_user(id,name) values('7','f');
insert into t_user(id,name) values('6','g');

在这里插入图片描述

其实我们从上面两个结果我们就可以知道,mysql底层其实是会为主键自动创建索引。

在这里插入图片描述

然后索引他是排好序的数据结构,所以能够帮助我们排好序。为啥要排好序呢,因为排好序之后查找相对就快了。比如我查找id为3的,我只需要按顺序找到3就行了。

那我们接下来就要讲一下mysql索引底层使用的数据结构,也就是B+树(大家应该都早有耳闻了),是怎么让我们查询的时候是有序的。

注意:在数据库中,索引是分很多种类的(千万不要狭隘的认为索引只有 B+ 树,那是因为我们平时使用的基本都是 MySQL)。而不同的种类很显然是为了应付不同的场合,但我们主要讨论的都是B+树,因为我们平时更多使用的都是B+树

我们可以看官网的描述,对于InnoDB, 除了空间索引之外,其他的索引的数据结构是B-Tree(其实就是B+ Tree)

The Physical Structure of an InnoDB Index

With the exception of spatial indexes, InnoDB indexes are B-tree data structures. Spatial indexes use R-trees, which are specialized data structures for indexing multi-dimensional data. Index records are stored in the leaf pages of their B-tree or R-tree data structure. The default size of an index page is 16KB. The page size is determined by the innodb_page_size setting when when the MySQL instance is initialized.

参考:https://dev.mysql.com/doc/refman/5.7/en/innodb-physical-structure.html

在讲B+树之后,我们还是快速来回顾一下,为啥B+树比较适合作为索引的数据结构的。

索引数据结构选择

之前我写了一篇关于MySQL索引的数据结构,《MySQL底层为什么要选用B+树作为索引的数据结构呢?》,里面提到了关于为什么当我们需要快速查找数据的时候,我们需要建立索引,那么索引本质上就是一种数据结构,帮助我们更好的去定位数据,那么MySQL中最终为什么选择了B+树这种数据结构作为索引的数据结构呢,感兴趣的小伙伴可以看看,当然本篇文章也会快速的带过一下索引相关的几种数据结构选择(Hash,二叉树查找树,AVL树,红黑树,B树,B+树),只是不会这么详细的去介绍一些数据结构,不懂的小伙伴最好去复习一下数据结构了。

我们这里再简单快速回顾一下

  • Hash表
  • 二叉查找树
  • AVL树
  • 红黑树
  • B-Tree
  1. Hash表

首先讲一下Hash表,Hash表的好处就是可以根据一个key快速定位到具体的位置,但是缺点是不支持范围查询,如果我要查询id>2的数据,通过Hash表就很难查到。

在这里插入图片描述

  1. 二叉查找树

二叉查找树,在插入递增的数据的时候,会退化成链表,比如MySQL的主键用自增Id的话,
建立起的二叉查找树如下,退化成链表之后查询效率变得很差了

在这里插入图片描述

  1. AVL树

AVL树就是为了解决插入递增数据的时候会退化成链表的问题,下面是插入递增数据的时候构造出来的AVL树

请添加图片描述

在这里插入图片描述

  1. 红黑树

与AVL树不同的是,红黑树在插入、删除等操作,不会像AVL树那样,频繁着破坏红黑树的规则,所以不需要频繁着调整,但是如果你要说,单单在查找方面的效率的话,AVL树比红黑树快。所以,红黑树也可以说是一个折中发方案,查询效率不算差,插入删除操作也不需要频繁调整。

请添加图片描述

在这里插入图片描述

  1. B-树

看到红黑树我们可以考虑一个问题,如果我们有500万的数据,那我们建立出来的红黑树其实是很高的,树一旦高起来,查询的效率其实也是很差的,而且数据库查询数据的瓶颈在于磁盘 I/O,如果使用的是 AVL树或者红黑树,我们每一个树结点只存储了一个数据,我们一次磁盘 I/O 只能取出来一个节点上的数据加载到内存里。500万的数据可以想象到二叉树的节点将会非常多,高度也会极其高,我们查找数据时也会进行很多次磁盘 I/O,我们查找数据的效率将会极低。

所以我们想一想,如果我们把树的高度限制的越小,高度在小于4的范围内,还能存储上千万的数据,那么就还能保持较快的查询次数,也能够降低磁盘I/O次数。

那么我们如何降低树的高度呢,想一想那就是让我们每个节点存储更多的数据,所以就出现了B-树。

请添加图片描述

在这里插入图片描述

那么B-树有什么特点呢?

  • 叶结点具有相同的深度,叶结点的指针为空
  • 所有索引数据不重复
  • 节点中的数据索引从左到右递增排序

B树有一个特点就是,它的每个节点都会存一份数据,如下图:

在这里插入图片描述

这里借用网上的一个图来给大家更好的展示一下B树(下图为3阶B树):

图中的p节点为指向子节点的指针,二叉查找树和平衡二叉树其实也有,因为图的美观性,被省略了。 图中的每个节点称为页,页就是我们上面说的磁盘块,在mysql中数据读取的基本单位都是页,所以我们这里叫做页更符合mysql中索引的底层数据结构。

这是官网对页的介绍:
InnoDB stores all records inside a fixed-size unit which is commonly called a “page” (though InnoDB sometimes calls it a “block” instead). Currently all pages are the same size, 16KB.

可以用如下SQL来查询:

SHOW GLOBAL STATUS like 'Innodb_page_size'; -- 1页 16384

select 16384/1024; -- 16Kb

参考链接:https://dev.mysql.com/doc/internals/en/innodb-page-structure.html

在这里插入图片描述

假如我们要查找id=28的用户信息,那么我们在上图B树中查找的流程如下:

  1. 先找到根节点也就是页1,判断28在键值17和35之间,我们那么我们根据页1中的指针p2找到页3。

  2. 将28和页3中的键值相比较,28在26和30之间,我们根据页3中的指针p2找到页8。

  3. 将28和页8中的键值相比较,发现有匹配的键值28,键值28对应的用户信息为(28,bv)。

参考:再有人问你为什么MySQL用B+树做索引,就把这篇文章发给她

  1. B+树

那么我们最后要讲一下B+树了,有人问为啥有了B-树,还要搞一个B+树。那是因为我们其实会发现我们的B-树的每个结点上都存储了一份数据,而每个结点也就是1页的数据,1页的数据在mysql中是16KB,所以一页(一个结点)能存储的数据个数就不多,想要树的高度低,那肯定要一个结点存尽可能多的数据了,所以就有了B+树,我们来看看B+树是怎样的吧

请添加图片描述

但是MySQL中的B+树跟上面的还不太一样(这点我也有点怀疑,网上很多B+树图都长得不一样,可能是Mysql为了更好满足自身需求做的一些优化吧,仅个人假设),我们可以参考官网给出的B+树的定义

         --------
         - root -
         --------
            |
  ----------------------
  |                    |
  |                    |
  --------          --------
  - leaf -  <-->    - leaf -
  --------          --------

Everyone has seen a B-tree and knows that the entries in the root page point to the leaf pages. (I indicate those pointers with vertical ‘|’ bars in the drawing.) But sometimes people miss the detail that leaf pages can also point to each other (I indicate those pointers with a horizontal two-way pointer ‘<–>’ in the drawing). This feature allows InnoDB to navigate from leaf to leaf without having to back up to the root level. This is a sophistication which you won’t find in the classic B-tree, which is why InnoDB should perhaps be called a B±tree instead.

参考:https://dev.mysql.com/doc/internals/en/innodb-fil-header.html

所以其实在叶结点中其实是有双向指针的,下面我借用别人画的图来介绍一下B+树。

参考: 再有人问你为什么MySQL用B+树做索引,就把这篇文章发给她

PS: 但是有一点我不确认的是,到底在MySQL的B+树中非根节点和非叶子结点的那些结点之间到底是不是也有指针进行关联的。因为在MySQL官网中,只提到了关于叶子结点上存在双向指针,非叶子节点(除了根)之间到底有没有指针指向,这个我不太确定,而且我个人认为非叶子节点(除了根)的指针起不到什么作用,不知道有没有大佬可以给出MySQL B+树的正确图形结构,为了保证严谨的态度,我暂时将每个非叶子节点(除了根)之间的指针关系去掉了。

在这里插入图片描述

下面来讲一下Mysql的一些特点。

  1. B+树非叶子节点上是不存储数据的,仅存储键值(跟B树的区别),而B树节点中不仅存储键值,也会存储数据。之所以这么做是因为在数据库中页的大小是固定的,innodb中页的默认大小是16KB。如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快。另外,B+树的阶数是等于键值的数量的,如果我们的B+树一个节点可以存储1000个键值,那么3层B+树可以存储1000×1000×1000=10亿个数据。一般根节点是常驻内存的,所以一般我们查找10亿数据,只需要2次磁盘IO。

  2. 因为B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而B树因为数据分散在各个节点,要实现这一点是很不容易的。

  3. B+树中叶子结点之间是通过双向链表连接的,叶子节点里面的那些的数据是通过单向链表连接的。上图中的B+树索引就是innodb中B+树索引真正的实现方式,准确的说应该是聚集索引(聚集索引和非聚集索引下面会讲到)。

  4. 在innodb中,我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。
    MyISAM中的B+树索引实现与innodb中的略有不同。在MyISAM中,B+树索引的叶子节点并不存储数据,而是存储数据的文件地址。

再来讲一下相比起B-树,B+树有什么优点吧。

  1. 在 B+ 树中,所有数据记录节点都是按照键值的大小存放在同一层的叶子节点上,而非叶子结点只存储key的信息,这样可以大大减少每个节点的存储的key的数量,降低B+ 树的高度
  2. B+ 树叶子节点的关键字从小到大有序排列,左边结尾数据都会保存右边节点开始数据的指针。
  3. B+ 树的层级更少:相较于 B 树, B+ 每个非叶子节点存储的关键字数更多,树的层级更少所以查询数据更快
  4. B+ 树查询速度更稳定:B+ 所有关键字数据地址都存在叶子节点上,所以每次查找的次数都相同所以查询速度要比B树更稳定;
  5. B+ 树天然具备排序功能:B+ 树所有的叶子节点数据构成了一个有序链表,在查询大小区间的数据时候更方便,数据紧密性很高,缓存的命中率也会比B树高。
  6. B+ 树全节点遍历更快:B+ 树遍历整棵树只需要遍历所有的叶子节点即可,而不需要像 B 树一样需要对每一层进行遍历,这有利于数据库做全表扫描

MyISAM存储引擎和InnoDB存储引擎索引B+树的区别

在MyISAM中,聚集索引和非聚集索引(后面会讲到)的叶子节点都会存储数据的文件地址。如下图

在这里插入图片描述

我们先来创建两个表,然后分别指定以下不同的存储引擎,我们来看看底层的文件存储结构是怎样的

create table t_user_myisam
(
    id varchar(20) primary key ,
    name varchar(20)
)engine=MyISAM;

create table t_user_innodb
(
    id varchar(20) primary key ,
    name varchar(20)
)engine=InnoDB;


#查看某个库下所有表使用的存储引擎
show table status from test_db;

SHOW CREATE TABLE t_user_innodb;
SHOW CREATE TABLE t_user_myisam;
#查看某个库下指定表使用的存储引擎
show table status from test_db where name='t_user_innodb';
show table status from test_db where name='t_user_myisam';

我这里使用的是docker中的mysql:5.7.25,然后我们进入容器。

docker exec -it mysql /bin/bash

然后我们来查看一下底层存储的data文件,首先我们进入文件目录

cd var/lib/mysql

在这里插入图片描述

然后找到我们的database,进入这个目录,我们可以看到我们

在这里插入图片描述

在这里插入图片描述

所以在B+树上的体现就是,对于InnoDB来说,B+树的叶子节点是会存储数据和索引,他们都是存储在一个文件里。而对于MyISAM来说,B+树的叶子节点是存储索引已经具体数据的地址,通过这个地址去找到具体的数据,所以索引和数据是分开存储的。

InnoDB索引B+树详解

其实B+树的这种查询方式,跟我们平时查询书本目录其实是很类似的。

在这里插入图片描述

比如我们要找InnoDB存储引擎这一章的内容的某一句话,那我们先找到MySQL的存储引擎,在下面找到InnoDB存储引擎,他会告诉我们在这一章节的其实页数是那一页,然后我们找到那一页,往后开始翻知道找到我们需要的内容。 这种查询方式其实跟我们B+树中的查询方式是十分相似的。

我们都知道 MySQL 在存储数据的时候是以数据页为最小单位的(这就好像书一样都是一页里面存了很多的文字内容),且数据在数据页中的存储是连续的,数据页中的数据是按照主键排序的(没有主键是由 MySQL自己维护的 ROW_ID 来排序的),数据页和数据页之间是通过双向链表来关联的,数据与数据之间是通过单向链表来关联的。

在这里插入图片描述

也就是说有一个在每个数据页中,他必然就有一个最小的主键,然后每个数据页的页号和最小的主键会组成一个主键目录(就像上图中的左边部分),这里就有点像书的目录,把标

在这里插入图片描述

假设现在要查找主键为 2 的数据,通过二分查找法最后确定下主键为 2 的记录在数据页 1 中,此时就会定位到数据页 1 接着再去定位主键为 2 的记录(这就有点跟我们翻目录查到页码之后,再从)

在这里插入图片描述

但是现在假设有很多很多的是数据页,那是不是对应的主键目录会很大很大呢?
那假设有1000万条记录、5000万条记录呢?是不是就算是二分法查找,其效率也依旧是很低的,所以为了解决这种问题 MySQL 又设计出了一种新的存储结构—索引页。(就相当于如果你的书的内容超级超级多,导致你的书目录也是超级多,可能目录有几百页的内容,那么我们也可以对书的目录向上抽取一层目录,没错看起来就是在套娃了。)

所以我们不断的对索引目录向上抽取一层目录索引,直到内容能够存储在一页里面,那就不需要继续抽取目录了,那么最终就会变成下面这个图这样。

在这里插入图片描述

如果有小伙伴对这方面还是有疑惑的话,可以看看再有人问你MySQL索引原理,就把这篇文章甩给他! ,我也是参考这篇博文,我觉得写的很好,可以更好帮助我们理解B+树索引。

聚簇索引和非聚簇索引

前面介绍 B+ 树索引的时候,我们提到了图中的主键索引其实是聚簇(聚集)索引的实现方式,也就是数据和索引是放一起的,叶子结点保存了全部的行数据。

那我们平时开发中除了主键索引其他的索引也用的不少,这时候该怎么办?假设你现在 对其他字段建立索引。现在回顾下主键索引,是不是在插入数据的时候基于主键的顺序去维护一个 B+ 树的?,那在叶子结点是不是也冗余了一份相同的行数据。如果我们对每个字段都建立了索引,那是不是全部的叶子结点都冗余了一份行数据。那岂不是很浪费空间了。 所以这里就引出了非聚簇(非聚集)索引

在 MySQL 中,B+ 树索引按照存储方式的不同分为聚簇(聚集)索引和非聚簇(非聚集)索引。

这里我们开始介绍 InnoDB 中的聚簇(聚集)索引和非聚簇(非聚集)索引

  • 聚簇索引:将数据存储和索引放到一块,索引结构的叶子结点保存了行数据
  • 非聚簇索引:将数据与索引分开存储,索引结构的叶子结点指向了数据对应的位置。

非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。

注意:在innodb中,在聚簇索引之上创建的索引称之为辅助索引,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引。辅助索引叶子节点存储的不再是行的物理位置,而是主键值,辅助索引访问数据总是需要二次查找。

在这里插入图片描述

  1. 在InnoDB中
  • InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id=14"这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。
  • 若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树种再执行一次B+树检索操作(这个过程也称为回表),最终到达叶子节点即可获取整行数据。(重点在于通过其他键需要建立辅助索引)
  • 聚簇索引默认是主键,如果表中没有定义主键,InnoDB会选择一个唯一且非空的索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键(类似oracle中的Rowld)来作为聚簇索引。如果已经设置了主键为聚簇索引又希望再单独设置聚簇索引,必须先删除主键,然后添加我们想要的聚簇索引,最后恢复设置主键即可。
  1. 在MyISAM中

之前我们也讲到了MyISAM存储引擎的索引存储结构,叶子结点存储的是记录的地址,所以很明显MyISAM中的索引是非聚簇索引。

  • MyISAM使用的是非聚簇索引,非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。(所以没有回表这一操作)

在这里插入图片描述

聚簇索引需要注意什么?
  • 当使用主键为聚簇索引时,主键最好不要使用uuid,因为uuid的值太过离散,不适合排序且可能出现新增加记录的uuid,会插入在索引树中间的位置,导致索引树调整复杂度变大,消耗更多的时间和资源。
  • 建议使用int类型的自增,方便排序并且默认会在索引树的末尾增加主键值,对素引树的结构影响最小。而且,主键值占用的存储空间越大,辅助素引中保存的主键值也会跟着变大,占用存储空间,也会影响到IO操作读取到的数据量。
为什么不推荐主键使用UUID?

聚簇索引的数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的。如果主键不是自增id,比如像UUID这样的,那么可以想象,它会干些什么,不断地调整数据的物理地址、分页,当然也有其他一些措施来减少这些操作,但却无法彻底避免。但如果是自增的,那就简单了,它只需要一页一页地写,索引结构相对紧凑,磁盘碎片少,效率也高。

可以参考一下:MySQL索引背后的数据结构及算法原理 中的 【InnoDB的主键选择与插入优化】 这一个章节

最左前缀原理

高效使用索引的首要条件是知道什么样的查询会使用到索引,这个问题和B+Tree中的“最左前缀原理”有关,下面通过例子说明最左前缀原理。

我现在创建了下面一张表

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');

然后我们现在对表t1创建一个复合索引

create index idx_t1_bcd on t1(b,c,d);

现在我们来看以下的查询条件,哪些条件是能走索引的。

explain select * from t1 where b = 1 and c = 1 and d = 1; -- 走 idx_t1_bcd 索引

# mysql引擎在查询为了更好利用索引,在查询过程中会动态调整查询字段的顺序以便利用索引
explain select * from t1 where  d = 1 and  b = 1 and c = 1; -- 走 idx_t1_bcd 索引
explain select * from t1 where  c = 1 and  d = 1 and c = 1; -- 走 idx_t1_bcd 索引
explain select * from t1 where  c = 1 and  d = 1 and c = 1 and a = 1; -- 如果有主键的查询条件,会走主键索引,因为主键索引可以直接获取到数据,而走 idx_t1_bcd 索引需要回表

# 对b,c,d创建了复合索引,相当于创建了(b),(b,c),(b,d),(b,c,d)索引
explain select * from t1 where  b = 1; -- 走 idx_t1_bcd 索引
explain select * from t1 where  b = 1 and c = 1; -- 走 idx_t1_bcd 索引
explain select * from t1 where  b = 1 and d = 1; -- 走 idx_t1_bcd 索引 //Using index condition
explain select * from t1 where  b = 1 and e = 'a'; -- 走 idx_t1_bcd 索引 //Using where
explain select * from t1 where  c = 1 and d = 1; -- 不走idx_t1_bcd 索引
explain select * from t1 where  c = 1; -- 不走idx_t1_bcd 索引
explain select * from t1 where  d = 1; -- 不走idx_t1_bcd 索引
explain select * from t1 where  e = 'a'; -- 不走idx_t1_bcd 索引

我们现在从底层原理来分析一下为啥会有这个最左前缀原理。

首先这个表有一个主键a字段,主键索引是聚簇索引,我们可以画出它的结构

在这里插入图片描述

然后有一个复合索引(b,c,d); 复合索引一个非聚簇索引,我们可以画出它的结构。

在这里插入图片描述

当需要回表查询的时候,首先从复合索引的B+树中找到对应数据的主键ID,然后在去主键索引的B+树中找到具体的数据,如下图。

在这里插入图片描述

首先我们来讲一下复合索引是如何查询的,比如我们现在要查询一个是b=2,c=3,d=5 这个查询条件,那我们是
如何查询的呢?

首先复合索引的键做比较的时候,是一个个比较的,先比较b,如果b相等再看c,如果c还是相等那就是d,其实就跟字符串的比较是一样的。

当条件是b=2,c=3,d=5的时候,匹配的路径如下,通过b=2这个条件其实就可以筛选到最后的叶子结点了,最后在从叶子结点的数据中找到b=2,c=3,d=5这一个数据。

在这里插入图片描述

那现在我们来看看为什么c = 1 and d = 1这个条件不能走索引了。

在这里插入图片描述

因为我们需要从左到右判断每个字段,从而知道怎么往下去找结点,但是如果复合索引的第一个b字段没有给出来的话,我们没法比较,所以不知道下面该如何走,假如b=1那就会走左边,如果b=4那就会走右边,所以mysql通过这个查询条件是没法索引的,所以最终会走全表扫描。

所以我们一旦理解了索引的底层数据结构是B+树,我们自然就知道为什么会有最左前缀原则了。

MySQL是如何选择最优索引的?

如果我们的查询条件满足了最左前缀原则,MySQL一定会走索引呢?其实不一定,MySQL是会自己判断选择最优的索引方式,是走全表扫描还是走索引。

比如我们还是用上面的那个表来举例子了。

explain select * from t1 where b > 1; 

比如上面这个sql,其实它满足了最左前缀了,但是走索引吗? 答案是不走的,我们可以看一下。

在这里插入图片描述

可以看到是走全表扫描的,为什么有索引但是MySQL偏偏要走索引呢,这个时候我们又要拿出之前画的索引B+树了。

在这里插入图片描述

通过上面的图,我们可以知道,如果走索引的话,我们还需要回表8次,才能拿出全部的数据,而如果是全表扫描的话就不需要回表操作了,我们直接从主键索引B+树中叶子结点进行遍历就快多了。

在这里插入图片描述

那我们现在再来看看这个sql

explain select * from t1 where b > 6;

这个sql走不走索引呢,答案是走的。

在这里插入图片描述

为什么这个就走索引了呢,其实从B+树来分析,我们可以很简单的看出来,因为回表的次数少了,所以mysql选择了走索引。

在这里插入图片描述

覆盖索引

覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。

当一条查询语句符合覆盖索引条件时,MySQL只需要通过索引就可以返回查询所需要的数据,这样避免了查到索引后再返回表操作,减少I/O提高效率。

这里就用之前的例子来说明了。

explain select * from t1 where b > 1; -- 全表扫

之前我们说到这个sql是走全表扫描的,因为需要获取全部数据,所以需要回表查询具体的数据,而且回表次数比较多,所以MySQL选择了全表扫描。

那我们来看下面这个sql。

explain select a,b,c,d from t1 where b > 1; 

那么这个sql,是走全表扫描还是走索引呢?答案是走索引,因为这里就是我们提到的覆盖索引了。

在这里插入图片描述

因为我们的查询的数据是a,b,c,d这4个字段,而这4个字段都能在(b,c,d)这个索引的B+树的叶子结点中找到。所以根本不需要回表去查询全部的数据了。所以这个时候MySQL会选择走索引而不走全表扫描。

在这里插入图片描述

所以一旦我们理解了索引的B+树结构,我们自然而然的就能够理解为什么会有覆盖索引了。

总结

所以本篇博文主要是讲解了mysql用B+树作为数据的索引,以及在innodb中数据库如何通过B+树索引来存储数据以及查找数据。我们一定要记住这句话:数据即索引,索引即数据

参考

MySQL索引类型

再有人问你MySQL索引原理,就把这篇文章甩给他!

B站讲的最好的MySQL数据库教程全集(2020最新版)

【编程不良人】这一次彻底搞定MySQL索引、从此不在恐惧面试

Data Structure Visualizations

图解 MySQL 索引:B-树、B+树,终于搞清楚了!

再有人问你为什么MySQL用B+树做索引,就把这篇文章发给她

MySQL索引背后的数据结构及算法原理

MySQL哈希索引

超级干货|为什么MySQL能够支撑千万数据规模的快速查询?

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值