十几年老Java咳血推荐:MySQL索引原理失效情况,两万字肝爆,建议收藏!

Posted Java老猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了十几年老Java咳血推荐:MySQL索引原理失效情况,两万字肝爆,建议收藏!相关的知识,希望对你有一定的参考价值。

一、前言

mysql 作为主流的数据库,是各大厂面试官百问不厌的知识点,但是需要了解到什么程度呢?仅仅停留在 建库、创表、增删查改等基本操作的水平可不够。在面试后端开发的时候,一连几个问题,简直会被问到一脸懵⭕️。。

面试官:MySQL 语句怎么优化?

面试官:分库,分表都适合哪些场景?

面试官:讲讲 InnoDB 如何使用 B+ 树存储的?

图片

还有很多栗子,这里就不一一论述学习MySQL 的重要性了。
img

二、MyISAM 索引实现

MyISAM 引擎使用 B+Tree 作为索引结构,叶节点的 data 域存放的是数据记录的地址。下图是 MyISAM 索引的原理图:
MySQL索引实现原理分析

这里设表一共有三列,假设我们以 Col1 为主键,则图 8 是一个 MyISAM 表的主索引(Primary key)示意。可以看出 MyISAM 的索引文件仅仅保存数据记录的地址。

辅助索引

在 MyISAM 中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求 key 是唯一的,而辅助索引的 key 可以重复。如果我们在 Col2 上建立一个辅助索引,则此索引的结构如下图所示

MySQL索引实现原理分析

同样也是一颗 B+Tree,data 域保存数据记录的地址。因此,MyISAM 中索引检索的算法为首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其data 域的值,然后以 data 域的值为地址,读取相应数据记录。

MyISAM 的索引方式也叫做“非聚集索引”,之所以这么称呼是为了与 InnoDB的聚集索引区分。

三、mysql索引知识

1 B+Tree索引

在InnoDB中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表(IOT),InnoDB使用B+树索引模型,数据都是存储在B+树中的。

假设,有一个表的主键列为ID,字段为k,并且在k上有索引。表中R1~R5的(ID,k)值分别为(100,1)、(200,2)、(300,3)、(500,5)、(600,6),**每一个索引在****InnoDB里面对应一棵B+****树,**两棵树的简意示意图如下:

img

2 主键索引和普通索引的区别

  • 主键索引的叶子节点存的是整行数据。主键索引也被称为聚簇索引(clustered index)
  • 非主键索引的叶子节点内容是主键的值。非主键索引也被称为二级索引(secondary index)

如果语句是select * from T where ID=500,即主键查询方式,则只需要搜索ID这棵B+树;

如果语句是select * from T where k=5,即普通索引查询方式,则需要先搜索k索引树,得到ID的值为500,再到ID索引树搜索一次,这个过程称为回表!

也就是说,基于非主键索引的查询需要多扫描一棵索引树,因此,我们在应用中应该尽量使用主键查询。

3 唯一索引vs普通索引

从查询上来说

  • 对于普通索引来说,查找到满足条件的第一个记录后,需要查找下一个记录,直到碰到第一个不满足条件的记录。
  • 对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。

从更新上来说

A 如果目标页在内存中:

  • 对于唯一索引来说,找到3和5之间的位置,判断有没有冲突,插入这个值,语句执行结束;
  • 对于普通索引来说,找到3和5之间的位置,插入这个值,语句执行结束。

B 如果目标页在不在内存中:

  • 对于唯一索引来说,需要将数据页读入内存,判断到没有冲突,插入这个值,语句执行结束;
  • 对于普通索引来说,则是将更新记录在change buffer,语句执行就结束了。

从这里可以看到,查询上普通索引只是比唯一索引多了一个一次指针寻找和一次计算,由于数据是按页读取的,数据几乎都在内存中,所以性能相差不大。

但从更新上来看,如果数据不在内存中,唯 一索引需要将数据从磁盘上读取到内存中,这样会引发随机读,导致IO消耗增多,而普通索引可以利用change buffer,IO上边要节省很多。性能相差会很多,所以如果可以在业务端保证数据的唯一性,那就可以使用普通索引。

四、InnoDB 索引实现

虽然 InnoDB 也使用 B+Tree 作为索引结构,但具体实现方式却与 MyISAM 截然不同。

1.第一个重大区别是 InnoDB 的数据文件本身就是索引文件。从上文知道,MyISAM 索引文件和数据文件是分离的,索引文件仅保存数据记录的地址

而在InnoDB 中,表数据文件本身就是按 B+Tree 组织的一个索引结构,这棵树的叶点data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。

MySQL索引实现原理分析

上图是 InnoDB 主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为 InnoDB 的数据文件本身要按主键聚集,

1 **.InnoDB 要求表必须有主键(MyISAM 可以没有),**如果没有显式指定,则 MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL 自动为 InnoDB 表生成一个隐含字段作为主键,类型为长整形。

同时,请尽量在 InnoDB 上采用自增字段做表的主键。因为 InnoDB 数据文件本身是一棵B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持 B+Tree 的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。如下图所示:

MySQL索引实现原理分析

这样就会形成一个紧凑的索引结构,近似顺序填满。由于每次插入时也不需要移动已有数据,因此效率很高,也不会增加很多开销在维护索引上。

2.第二个与 MyISAM 索引的不同是 InnoDB 的辅助索引 data 域存储相应记录主键的值而不是地址。换句话说,InnoDB 的所有辅助索引都引用主键作为 data 域。
例如,图 11 为定义在 Col3 上的一个辅助索引:

MySQL索引实现原理分析

聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引(回表):首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。

引申:为什么不建议使用过长的字段作为主键?

因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。

聚簇索引与非聚簇索引

InnoDB 使用的是聚簇索引, 将主键组织到一棵 B+树中, 而行数据就储存在叶子节点上, 若使用"where id = 14"这样的条件查找主键, 则按照 B+树的检索算法即可查找到对应的叶节点, 之后获得行数据。 若对 Name 列进行条件搜索, 则需要两个步骤:
第一步在辅助索引 B+树中检索 Name, 到达其叶子节点获取对应的主键。
第二步使用主键在主索引 B+树种再执行一次 B+树检索操作, 最终到达叶子节点即可获取整行数据。

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

为了更形象说明这两种索引的区别, 我们假想一个表如下图存储了 4 行数据。 其中Id 作为主索引, Name 作为辅助索引。 图示清晰的显示了聚簇索引和非聚簇索引的差异

MySQL索引实现原理分析

联合索引及最左原则

联合索引存储数据结构图:

img

最左原则:

例如联合索引有三个索引字段(A,B,C)

查询条件:

(A,,)—会使用索引

(A,B,)—会使用索引

(A,B,C)—会使用索引

(,B,C)—不会使用索引

(,,C)—不会使用索引

五、mysql索引优化

1 查看索引使用情况

使用方法:在select语句前加上explain

示例:EXPLAIN SELECT surname,first_name form a,b WHERE a.id=b.id

EXPLAIN列的解释:

  • table:显示这一行的数据是关于哪张表的。
  • type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、index和ALL。
  • possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句。
  • key: 实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MySQL会选择优化不足的索引。这种情况下,可以在SELECT语句中使用USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MySQL忽略索引。
  • key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好。
  • ref:显示索引的哪一列被使用了,如果可能的话,是一个常数。
  • rows:MySQL认为必须检查的用来返回请求数据的行数。
  • Extra:关于MySQL如何解析查询的额外信息。

Extra列返回的描述的意义:

Distinct: 一旦MySQL找到了与行相联合匹配的行,就不再搜索了。
 
Not exists: MySQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了。
 
Range checked for each Record(index map:#): 没有找到理想的索引,因此对于从前面表中来的每一个行组合,MySQL检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一。
 
Using filesort: 看到这个的时候,查询就需要优化了。MySQL需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。
 
Using index: 列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候。
 
Using temporary: 看到这个的时候,查询需要优化了。这里,MySQL需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上。
 
Where used: 使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题不同连接类型的解释(按照效率高低的顺序排序)。
 
system: 表只有一行:system表。这是const连接类型的特殊情况。
 
const: 表中的一个记录的最大值能够匹配这个查询(索引可以是主键或惟一索引)。因为只有一行,这个值实际就是常数,因为MySQL先读这个值然后把它当做常数来对待。
 
eq_ref: 在连接中,MySQL在查询时,从前面的表中,对每一个记录的联合都从表中读取一个记录,它在查询使用了索引为主键或惟一键的全部时使用。
 
ref: 这个连接类型只有在查询使用了不是惟一或主键的键或者是这些类型的部分(比如,利用最左边前缀)时发生。对于之前的表的每一个行联合,全部记录都将从表中读出。这个类型严重依赖于根据索引匹配的记录多少—越少越好。
 
range: 这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西时发生的情况。
 
index: 这个连接类型对前面的表中的每一个记录联合进行完全扫描(比ALL更好,因为索引一般小于表数据)。
 
ALL: 这个连接类型对于前面的每一个记录联合进行完全扫描,这一般比较糟糕,应该尽量避免。

2 mysql索引使用策略

  1. 最好全值匹配–索引怎么建我怎么用。
  2. 最佳左前缀法则–如果是多列复合索引,要遵守最左前缀法则。指的是查询要从索引的最左前列开始并且不跳过索引中的列。
  3. 不在索引列上做任何操作(计算,函数,(自动或者手动)类型装换),会导致索引失效而导致全表扫描。
  4. 存储引擎不能使用索引中范围条件右边的列。–范围之后索引失效(< ,>,between and)。
  5. 尽量使用覆盖索引–索引和查询列一致,减少select *。–按需取数据用多少取多少。
  6. 在MYSQL使用不等于(<,>,!=)的时候无法使用索引,会导致索引失效。
  7. is null或者is not null 也会导致无法使用索引。
  8. like以通配符开头(’%abc…’)MYSQL索引失效会变成全表扫描的操作。–覆盖索引。
  9. 隐式转换索引失效:字符串不加单引号。
  10. where条件少用or,用它来连接时索引会失效。

3 mysql索引使用原则

1、复合索引:选择索引列的顺序

1)尽量把字段长度小的列放在联合索引的最左侧(因为字段长度越小,一页能存储的数据量越大,IO性能也就越好)

2)区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数)

3)使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引)

2、表关联查询

1)类型和大小要相同,可以使用索引。

​ VARCHAR(10)和 CHAR(10)大小相同,但 VARCHAR(10)与 CHAR(15)不相同。

2)字符串列之间比较,两列应使用相同的字符集。例如,将utf8列与 latin1列进行比较会不使用索引。

3)将字符串列与时间或数字列进行比较时,在没有转换情况下,不使用索引。

3、常见的索引列建议

  1. WHERE 字段

  2. ORDER BY、GROUP BY、DISTINCT 中的字段不要将符合1和2中字段的列都建立一个索引,通常将1、2中的字段建立联合索引效果更好

3)多表join的关联列

4、通过索引扫描的行记录数超过全表的10%~30%左右,优化器不会走索引,而变成全表扫描

5、避免使用双%号的查询条件。 (如果无前置%,只有后置%,是可以用到列上的索引的)

覆盖索引、前缀索引、索引下推,在满足语句需求的情况下,尽量少地访问资源是数据库设计的重要原则之一。我们在使用数据库的时候,尤其是在设计表结构时,也要以减少资源消耗为目标。

最后

小伙伴们,帮忙一键三连呀

题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在Java学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升

故此将并将重要的Java进阶资料包括并发编程、JVM调优、SSM、设计模式、spring等知识技术、阿里面试题精编汇总、常见源码分析等录播视频免费分享出来,需要领取的麻烦点这里自行下载或者是添加QQ1404119194,备注csdn

Java进阶视频资料

在这里插入图片描述

Java面试题精编汇总

在这里插入图片描述

JAVA核心知识点整理在这里插入图片描述

在这里插入图片描述

以上是关于十几年老Java咳血推荐:MySQL索引原理失效情况,两万字肝爆,建议收藏!的主要内容,如果未能解决你的问题,请参考以下文章

阿里规范 - MySQL 数据库 - 索引规约 - 10 - 推荐防止因字段类型不同造成的隐式转换,导致索引失效。

#yyds干货盘点#MySQL索引优化系列:索引失效

Day811.MySQL调优之索引:索引的失效与优化 -Java 性能调优实战

面经-阿里Lazada电面

Mysql(14)—高性能的索引策略以及常见索引失效的情况

Mysql(13)—高性能的索引策略以及常见索引失效的情况