一文拿捏MySQL索引(篇幅略长,建议收藏)

一、基本概念:

索引:索引是帮助MySQL高效获取数据的有序的数据结构,在数据之外,数据库系统害维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

优点:索引可以提高数据检索效率 降低数据库的io成本,通过索引对数据进行排序,降低数据排序成本,降低CPU的消耗。

缺点:索引列会占用空间,索引大大提高了查询效率但是也降低了更更新表的速度,如对表进行INSERT,UPDATE,DELETE时,效率降低。

二、索引结构

MySQL的索引是在存储引擎层实现,不同的存储引擎会有不同的索引结构,主要包含以下几种:

B+Tree索引:最常见的索引类型,大部分引擎都支持B+Tree索引

Hash索引:底层的数据结构使用哈希表实现的,只支持等值匹配,不支持范围查询

R-Tree(空间索引):空间索引是MylSAM引擎的一个特殊引擎类型,主要用于地理空间类型数据

Full-text(全文索引):通过建立倒排索引,快速匹配文档

本文我们主要探讨B+Tree索引和Hash索引

1.B+树的索引结构

在探讨B+Tree索引之前 我们先来简单看一下B-Tree的结构

B-Tree树(多路平衡查找树)

以一颗最大度数为5阶的为例(每个节点最多存储4个key,5个指针)如图:

B-Tree

我们可以看到B树的每个节点下面都有数据

B-Tree树的演变过程:

如图,假如现在有五阶的B树,现在里面有四条数据

如果我再加入一条数据 1200 那么B树就会变成如下所示:

我们再依次加入 1234,1500 会变成如下:

此时我们再插入 1000 B数变成如下:

再添加多条数据

由此可见,B树是中间数向上分裂,并且数据是挂在每个ID下面

这就是B树的基本结构

下面我们再来看一下B+树的结构

我们以一颗最大度数为4的的B+树为例,如下图:

B+Tree

我们可以看到B+树上面的分叶节点是没有数据的,数据都是挂在最下面的叶子节点,并且在叶子节点中形成了一个单向链表

下面我们看一下B+树的演变过程:

如图,假如现在有五阶的B+树,现在里面有四条数据:

如果我再加入一条数据 890 那么B树就会变成如下所示:

此时我们可以看到B+树也是向上分裂,但向上分裂的同时数据还保留在叶子节点

我们再依次加入 1234,2345 会变成如下:

B+树相对于B树的区别:B+树 叶子节点会形成单项链表,非叶子节点只是起到索引的作用

在MySQL索引中的B+树对上面所展示的B+树的基础上 增加了一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+树,提高了区间访问的性能。

最终如图所示:

2.Hash索引结构

Hash索引就是采用Hash算法将键值换算成新的Hash值,映射到对应的槽位上,然后存储在Hash表中

如图所示:

如果数据量过大,通过Hash算法 算出的多个结果都指向同一个存储位置,那么会产生哈希冲突,我们可以通过链表来解决,如图:

Hash索引的特点,查询效率高,无法按照范围查询。

在mysql中,支持Hash索引的是Memory引擎,而innoDB中具有自适应hash功能,hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的。

为什么mysql会使用B+树索引结构:

1.相对于二叉树,层级更少,搜索效率更高

2.相对于B树,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,同样保存大量数据,只能增加树的高度,导致性能降低

3.相对于Hash,虽然Hash查询效率通常比B+树高,但是B+树通用性更强,hash只适合做等值查询

三、索引的分类

MySQL索引类型分为以下几种:

在innoDB存储引擎中,根据索引的形式可以分为以下两种:

聚集索引的选取规则:

1.如果存在主键,主键索引就是聚集索引

2.如果不存在主键,将会选择第一个唯一索引作为聚集索引

3.如果不存在主键以及合适的唯一索引,那么innoDB会自动生成一个rowid作为隐藏的聚集索引

聚集索引的结构:

假如有如下表:id是主键

stu

那么innoDB会生成如下聚集索引:

row是一行的数据

而如果根据name创建二级索引 如下图所示:

此时name下面挂载的是各个ID

假如说我们现在写一条SQL:

select id,name from stu where name =Lee

那么mysql会走name创建的二级索引,从里面查找名字是Lee的,找到之后 拿到Lee下面的ID并返回结果

如果我们的SQL语句改成:

select * from stu where name =Lee

那么mysql会走name创建的二级索引,从里面查找名字是Lee的,找到之后 拿到Lee下面的ID去聚集索引下查找这个ID并取得这一整行的数据

上面这条SQL就涉及到了回表查询 效率是没有第一条SQL的效率高的 所以我们写SQL尽量少用select *

四、MySQL性能分析工具

1.SQL执行频率查询

查询数据库增删改查的操作次数

语法:show  [session|global] status like 'Com_______';

2.慢查询日志 

慢查询日志就是定位到运行时间过长的SQL语句,针对个别语句进行建立索引等优化方式

查看是否开启慢查询日志:show variables like 'slow_query_log'

如果没有开启 开mysql配置文件 etc/my.cnf 中加上如下配置信息:

#开启mysql慢查询日志

slow_query_log=1

#设置慢查询日志的时间为5秒,SQL语句执行时间超过5秒就会视为慢查询,记录慢查询日志

long_query_time=5

然后查询日志文件锁定执行时间过长的SQL

3.profile查询

profile用来查询SQL语句在各个阶段所消耗的时间

使用前先查询 当前mysql 是否支持profile

语法:select @@have_profiling

用法:先执行一条查询语句 然后show profile 找到查询语句的Query_id,

show profile for query query_id;

4.Explain查询(最常用)

explain 最常用来查看表的执行顺序,以及当前查询语句是否使用索引

语法:explain  查询语句

结果如下:

首先我们来了解一下explain的字段名称

1,id:  

 id列的编号是 select 的序列号,有几个 select 就有几个id,并且id的顺序是按 select 出现的顺序增长的。 id列越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。

2,select_type 查询类型:

可以为一下任何一种类型

simple  简单select(不使用union或子查询)

primary   最外面的select

union    union中的第二个或后面的select语句

dependent union  union中的第二个或后面的select语句,取决于外面的查询

union result  union的结果。

subquery 子查询中的第一个select

dependent subquery  子查询中的第一个select,取决于外面的查询

derived    导出表的select(from子句的子查询)

3,table  输出的行所引用的表。

当 from 子句中有子查询时,table列是 格式,表示当前查询依赖 id=N 的查询,于是先执行 id=N 的查 询。

当有 union 时,UNION RESULT 的 table 列的值为,1和2表示参与 union 的 select 行id。

4,type 查找数据行记录的大概范围。

依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL,一般来说,得保证查询达到range级别,最好达到ref

system:表仅有一行(=系统表)。这是const联接类型的一个特例。

const:  表最多有一个匹配行,它将在查询开始时被读取。因为仅有一行,在这行的列值可被优化器剩余部分认为是常数。const表很快,因为它们只读取一次!

eq_ref:对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型。它用在一个索引的所有部分被联接使用并且索引是unique或primary key

ref:  对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取。如果联接只使用键的最左边的前缀,或如果键不是unique或primary key(换句话说,如果联接不能基于关键字选择单个行的话),则使用ref。如果使用的键仅仅匹配少量行,该联接类型是不错的。

range:范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行

index:扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接 对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般为使用覆盖索引,二级索引一般比较小,所以这 种通常比ALL快一些

ALL:即全表扫描,扫描你的聚簇索引的所有叶子节点。通常情况下这需要增加索引来进行优化了。

5,possible_keys: 如果该列是null,则没有相关的索引。在这种情况下,可以通过检查where子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用explain检查查询key 列显示mysql实际决定使用的键(索引)。如果没有选择索引,键是null。要想强制mysql使用或忽视possible_keys列中的索引,在查询中使用force index、use index或者ignore index。

6,key:这一列显示mysql实际采用哪个索引来优化对该表的访问。explain 时可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,mysql认为索引 对此查询帮助不大,选择了全表查询。如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用 force index、ignore index。

7,key_len:这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。

8,ref:显示索引在哪一列被使用了,如果可能的话,是一个常数

9,rows:  这一列显示mysql认为它执行查询时必须检查的行数。

10,Extra:这一列会显示重要的附加信息,比如是否使用覆盖索引等

五、索引使用规则

1.最左前缀法则

如果索引了多列,要遵循最左前缀法则  ,指查询从索引的最左列开始,并且不跳过索引中的列。

如果跳过了某一列,那么此列之后的索引全部失效。

例:现有表 stu 如图:


stu()

(不考虑实际业务)下面我们给name、phone、lesson添加上联合索引


索引创建语句

此时我们查看表中的索引 show index from stu;

如下图所示


索引列表

下面我们来查看一下如何使用索引


上图我们可以看key这一列 上面的语句是使用了索引 但是如果我们把name='Kid'去掉的话就会变成全表扫描,如下图:


这就是最左前缀法则,所以我们创建联合索引的时候要注意索引的顺序,最前面的索引必须有,才可以使用这个索引,否则索引将会失效。

下面还有一些情况会导致索引失效:

1.在索引上进行运算操作,索引失效,例如:


2.字符串类型的数据加引号会产生隐式转换,索引会失效,例如:


这里我们可以看到 是用了索引的 但是用的只是name的联合索引 ,我们可以看到key_len的长度是发生了变化的,phone的索引没有加引号,导致phone的联合索引以及后面lesson的索引失效。

3.头部使用模糊匹配,索引失效,例如:


%加在末端 不会导致索引失效


4.使用or连接必须左右两端都有索引,只有一侧有索引会导致索引失效

首先我们先看两端都有索引的情况:


id和name都有索引,所以这个查询语句使用了name的联合索引

下面我们看只有一侧有索引的情况:


这时我们可以看到type是all 全表扫描,没有使用id的主键索引

5.范围查询导致><右侧的索引失效,例如:


上图我们可以key_len的值是99,只用到了name的索引,后面phone和lesson的索引都没有用上

下面再来看一种情况:


这次我们用的>=可以发现key_len的长度是297,意味着三个索引都用上了,所以我们使用范围查询的时候尽量用大于等于、小于等于,少用大于或者小于。

6.数据分布影响索引

这个就是说,MySQL自动判断使用索引不如全表扫描的速度快,所以就不使用索引。因数据量问题,这个就不给大家演示了,如果出现这种情况,就该考虑优化索引、或者优化数据等手段。

7.SQL提示

SQL提示是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。

use index:建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进
行评估)。
ignore index:忽略指定的索引。
force index:强制使用索引。

语法:select * from stu use index(idx_stu_name_pho_les) where name >='Jid' and phone='9876543210' and lesson='语文'

当某个字段索引过多并且我们知道使用哪个索引效率更高时,我们就可以告诉mysql使用哪个索引不使用哪个索引。

此外 当你所需要的数据在一个索引中可以全部找到不需要返回聚集索引中查询第二次数据,这就叫做覆盖索引。

通过上文我们知道二级索引(非聚集索引)存储的是创建索引的字段以及id字段

以stu表为例(stu表有一个根据主键id创建的聚集索引,和以name、phone、lesson创建的联合索引),下面我们写一个SQL:

select id,name,phone,lesson from stu where  name='Kid' and phone='9876543210' and lesson='语文'


上图我们可以看到Extra字段下面内容是Using index 这代表这条查询语句使用了覆盖索引,只查询了一次,没有进行回表操作

我们再来看一下回表的话会返回什么


因为gender字段没有在以name、phone、lesson创建的二级索引中,所以当我们查询的时候,mysql会根据id再去聚集索引中获取gender的信息,先去查询二级索引,然后查询聚集索引,这就叫做回表查询,此时效率就比第一种的效率低。

单列索引和联合索引

单列索引:一个索引只包含一个列。

联合索引:一个索引包含多个列。

在业务场景中,如果存在多个查询条件,那么建议使用联合索引,可以有效避免回表查询。

索引设计原则

1.针对数据量较大,且查询频繁的表建立索引

2.针对于常作为查询条件、排序、分组的字段建立索引

3.尽量选择区分度高的列作为索引,尽量建立唯一索引。

4.如果是字符串类型的字段,字段的长度较长,可以针对字段的特点截取前几位作为前缀索引。

5.进来使用联合索引,减少单列索引,查询时,联合索引很多时候都可以覆盖索引,节省存储空间,避免回表,提高效率

6.控制索引的数量,索引越多,维护索引的代价越大,影响增删改的效率

7.如果索引列不能存储NULL值,在创建表是最好标上not null。

今天的知识就分享到这里,欢迎大家在评论区留言指正,共同探讨、一起学习。

你可能感兴趣的:(一文拿捏MySQL索引(篇幅略长,建议收藏))