Mysql索引

在谈论mysql的索引之前,我们先来了解一下MySQL的索引结构。

  • MySQL的索引数据结构选型是B+Tree,5.5版本以前使用的是B-Tree。那么二者有何区别呢?

一、MySQL的索引数据结构

1.1.1 什么是AVL树?

首先平衡二叉树我们比较了解,即在二叉查找树的基础上,控制其左右两边的子树高度。降低树的高度,这样有助于提升查询速率。

那么B树就在平衡二叉树的基础上再进行改进。由于AVL树尽管平衡了左右子树的高度尽可能小,但是一个结点只能保存一个数据,所以AVL树的做法在这个大数据量的时代仍然是杯水车薪。

1.1.2 B树的出世

既然一个结点只能保存一个数据行不通,那就将多个数据保存到一个结点上。于是就有了B树的产生,B树的每个叶子结点会保存多个数据。但是一个结点中的数据并不是随意排列的,而是也根据的是左结点小于根结点,根结点小于右结点。

如下图:

B树是为磁盘等外部存储设备设计的一种平衡查找树。由图中可以了解到,每个结点都是以磁盘块为单位读入内存的,位于同一个磁盘块中的数据会被一并读出来。

在操作系统的存储管理中,一类管理方式为分页存储管理。而InnoDB中也是按页将数据调入内存,页是磁盘管理的最小单位。在InnoDB中默认的页大小为16KB。但是由于磁盘块的存储空间没有页那么大,所以每次申请磁盘空间都是调入连续的磁盘块。

1.1.3 结点结构

  • 指针 + 键值 + 数据

1.1.4 B树的优缺点

优点:

(1)一个结点可以储存多条数据

(2)树的高度比较小,有利于提高查询速度。

(3)每个结点可以根据实际情况包含大量的关键字信息和分支

缺点:

(1)若数据是文本,或者占用储存大小比较大的时候,每个结点能储存的数据条数将会减少,因为页的大小是固定的,如果一条数据就占用了页的空间,则一个结点只能保存一条数据,又回到了平衡二叉树的问题上。

(2)I/O操作次数仍然比较高,不利于提高查询性能。如果像上述的问题发生,一条数据一个结点的情况发生,那将很大可能导致查询速度慢。

  • 由上述可见,B树仍然存在查询速率不高的情况,所以引入B+树。

1.2.1B+树

B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。

1.2.2 引入B+树能解决什么问题?

针对B树存在的缺点——不确定每条数据中的数据占用大小,若占用过大,则会导致I/O操作次数没有改善的情况。B+树去掉了每条数据的数据域,保留指针和键值。因为键值大多是int或者bigint这样的整形数据类型,要么占个四个字节,要么占用八个字节。去掉了数据域就相当于去掉了B树的不确定因素。而B+树是将所有键值和数据保存在叶子结点中。

如下图:

1.2.3 B+树的优点

非叶子结点将非键值的数据都去除掉,除了叶子结点外只存键值和指针。这样做带来的好处是可以让每个页都存储尽可能多的键值,缩小树的高度,减少对磁盘的I/O操作,提升查询的速率。

  • 就通过上面的叙述还不是很直观的感受到其查询速率的提升,通过下面的例子就能明白!

首先,按页的大小为16KB来算,一般建表定义的主键类型为int,占用四个字节;指针一般为4个或者8个字节。按8个字节算的话:

一页中(即一个非叶子结点)大概能存储16KB/(8B+8B)=1K条数据,1K大约为1000条数据

则一个树的深度为3的话,B+Tree索引可以维护10^3 * 10^3 * 10^3 = 10亿 条数据记录。

实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2~4层。MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘I/O操作,由此可见,使用这种方式的索引可以带来极大提升的查询效率。

1.2.4 B+树和B树的不同之处

(1)非叶子结点只储存键值信息(即主键)

(2)所有叶子结点都有一个链指针

(3)数据记录都存放在叶子结点中

二、MySQL的索引

MySQL的索引主要可以分为聚集索引和辅助索引,上述的B+树结构就是聚集索引的结构。

  • 辅助索引和聚集索引的区别?

① 辅助索引的叶子结点不是包含键+数据;而是包含当前索引键+当前行对应的主键。

因此我们可以知道,使用辅助索引的话就会引发回表。在使用辅助索引构建的B+树中,找到对应的主键值,再根据主键值去聚集索引的B+树中寻找对应的那条数据记录。显然这样方式的查询速率比直接使用聚集索引更慢。

2.1 索引的具体分类:

① 主键索引(关键词:PK_)

在一张表创建的时候需要指定其中的列作为主键,方便构建出表的索引树,每张表必须有一个聚集索引树,如上述所说的,辅助索引也要依赖于聚集索引才能实现。

若用户没有具体指定主键的话,InnoDB会自定帮你创建一个不可见的、长度为6字节的row_id,并且row_id是由InnoDB维护全局的disctsys.row_id,每次插入一条数据时,都会让全局row_id+1(row_id将作为未定义主键的表的主键id)

② 唯一索引(UK_)

唯一索引和主键索引唯一的区别就是唯一索引运允许字段为空值,而主键索引不能存空值。

③ 普通索引(idx_)

普通索引常常是用来进行条件快速查找的,除此之外对字段本身没有要求。

④ 组合索引

组合索引可以看做是多个列的普通索引。组合索引通过是遵循最左匹配原则,但是在特殊情况下,不遵循。

⑤ 全文索引(倒排索引)

背景:全文索引是搜索引擎的关键技术,在mysql5.6版本以前,InnoDB是不支持的,5.6版本之后才支持全文索引。

实现原理:与like和正则表达式不同,全文索引是类似于将文章中的每个词进行建立索引,记录其存在的位置id。

优点:相较于like和正则表达式,其性能更胜一筹。前者是需要对所有行进行匹配的;而全文索引就像上面所说,是建立了一个辅助表,表中的信息为词和其存在的位置id。因此,在查询性能方面会更好。

缺点:可能存在精度问题,例如在百度时,我们输入的信息可能只是部分匹配。得到的匹配结果,可能会将我们输入的关键字拆分开来。如下图,我们输入的是我爱java,但是查询的结果关键字不是连贯的。这就是全文索引里的分词机制,从而导致的精度问题。

三、提速神器——索引

  • 上述我们了解了mysql的索引结构,mysql的索引,那现在就来谈谈索引给查询速率带来的收益吧!

*面试中的常见问题:怎么提高sql的查询速度?

我:闭着眼睛先把建立索引说了。(当然还有其他方法,这里就不细说)

  • 造成sql查询速度慢的根本原因:对磁盘的I/O操作过于频繁。而造成这一局面的原因有很多,可能是索引建立的不合适,也可能是sql的写法有问题或者是数据量大却不走索引等等。接下来我们就先来说一说索引的命中问题。

3.1 索引命中

3.1.1 回表和索引下推

  • 回表

回表的发生:在使用普通索引(辅助索引)查到数据时,如果所查询的数据不包含在索引字段或者主键时,就会产生回表(即拿着查询到的主键,再去聚集索引树查找出行数据)

显而易见,当发生回表时,查询的速度肯定是不如没有回表的。

举个例子:当我们创建一张表,字段包含no(主键)、age、name(普通索引字段)。

select * from 表名 where name = ‘张三’

像上面这条sql语句,查询的age字段不在普通索引的索引树中,就会先找到主键值,然后再去聚集索引树中根据主键值找到相应的行数据。这就是回表,为了避免回表造成的时间浪费以及资源消耗,如果我们就需要查询其中的no的话,则最好像如下方式书写:

select no from 表名 where name = ‘张三’

这样就不会造成回表,因为要查询的no为主键,所以只需要在普通索引树中找一边即可,也称作索引覆盖

总结:

针对上面对于回表的介绍,我们应该在写sql语句时注意,避免对select后面加 * ,若只需要其中的部分字段就写具体字段名。

同时平时我们在使用mybatisplus这种框架时,其内部使用的单表查询自带的sql工具使用的就是select * 这样的sql,所以如果不是要快速开发完成,最好手写sql语句。

减少回表即减少了对磁盘的IO操作,提升查询速率。

  • 索引下推(ICP)

mysql5.6之前没有索引下推,若需要使用到两个或两个以上的索引字段,通过匹配完一个索引字段后回表找到该条记录,并将此纪录返回给mysql的server层。在server层再对第二个索引字段进行判断,若不符合条件,则继续下一个条记录。重复以上步骤。

按联合索引来说,比如A、B字段都是索引字段,在构建的B+树时,结点信息中有保存两个索引字段的信息,但是mysql的存储引擎却只判断了索引字段A,通过回表找到数据记录,然后将数据给server层,由server层对B进行判断。这显然是很浪费时间的做法,在存储引擎中就可以对两个字段进行判断,然后再回表找出数据记录返回给server层这种做法才是比较明智之举。

mysql5.6之后有索引下推后,存储引擎可以在对联合索引(二级索引 || 非主键索引)遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数,正如上一段的最后阐述。

创建一张表,字段为:no、name、age、sex。设联合索引的索引字段为name、age。

select * from 表名 where name > "张三" and age = 3

话外知识点:范围判断查询到的数据量占表低于30%,否则MySQL优化器将不选择走索引。

MySQL5.6之前:

1、当匹配了第一个name,返回数据对应的主键

2、根据主键回表查出这条数据,返回给server层,由server层对查询到的数据进行判断 age = 3 。

3、若不等于3,则回到第一步,继续匹配下一个name相等的数据,如此重复。

存储引擎只会判断一个索引字段,然后交给server层做其余判断,如下代码:

[mysql> explain select * from user where name>'张三' and age=3 
***************************1.row ***************************
             id:1
    select_type:SIMPLE
          table: user
           type: ref
  possible_keys: name
            key: name
            ref: const,const
           rows: 1
          Extra: Using where
1 row in set (0.01 sec)
​

根据Extra是using where可以看出,表示数据在 server 层还进行了过滤操作。这个过滤操作就是上述第二步,判断age是否等于3。

MySQL5.6之后:

1、由存储引擎直接判断name和age两个字段,找到符合条件的数据

2、定位到索引树的叶子结点,查到该数据的主键值

3、根据主键值进行回表查找

4、将数据返回给server层

运行结果如下:

[mysql> explain select * from user where name>'张三' and age=3 
***************************1.row ***************************
             id:1
    select_type:SIMPLE
          table: user
     partitions: NULL
           type: ref
  possible_keys: name
            key: name
            ref: const,const
           rows: 1
          Extra: Using index condition
1 row in set, 1 warning (0.00 sec)
​

原本遇到范围判断,age是不会走索引的,但是可以看到同样一个sql运行的结果不一致,这个结果的Extra显示为Using index condition,这就代表使用了索引下推,就是因为在存储引擎就将name和age进行判断过滤(所以age实际上还是走了索引),然后再将过滤后的数据进行回表。

3.2 索引失效

  1. 索引列参与计算

  2. 索引列发生类型转换

  3. 索引列使用函数处理

  4. 索引列使用模糊方式判断

  • like除了 %a、%a%,其余可以走索引

  • 遇到>、<、>=、<=这类的范围判断,若查询得到的数据占总数据量30%以下,才可以走索引,否则将不能走索引

  • 若遇到 != 非精确查询也无法走索引。

  1. sql语句中出现or、not null 、not in、not exist等关键词

  2. 若为联合索引,则需要遵守最左匹配原则(但是有时候也会不满足最左匹配原则依旧可以走索引)

参考文章:

掘金:避免回表,引入索引下推|提高索引命中率 | 提前下班啦 - 掘金

掘金:索引下推,yyds! - 掘金

掘金:探索MySQL是否走索引(一)——范围查询一定走索引吗? - 掘金

CSDN:MySQL索引原理B+树_哪 吒的博客-CSDN博客_b+树索引

CSDN:mysql 范围查询走索引吗?_chuxi8272的博客-CSDN博客

MySQL索引是一种数据结构,可以帮助MySQL快速定位和访问表中的数据。使用索引可以提高查询效率,降低数据库的负载。下面是MySQL索引的一些基本概念和使用方法: 1. 索引类型 MySQL支持多种类型的索引,包括B树索引、哈希索引、全文索引等。其中,B树索引是最常用的一种,也是默认的索引类型。B树索引可以用于精确匹配和范围查询,而哈希索引主要用于等值查询,全文索引则用于文本检索。 2. 索引创建 可以在创建表时指定索引,例如: ``` CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50), email VARCHAR(50), INDEX idx_email (email) ); ``` 也可以在已有的表上添加索引,例如: ``` ALTER TABLE users ADD INDEX idx_name (name); ``` 3. 索引使用 查询语句中可以使用WHERE子句和ORDER BY子句来利用索引,例如: ``` SELECT * FROM users WHERE email = 'example@example.com'; SELECT * FROM users WHERE name LIKE 'John%' ORDER BY id DESC; ``` 需要注意的是,索引并不是越多越好,过多的索引会占用过多的磁盘空间并降低写操作的性能。因此,需要根据实际情况选择合适的索引。同时,还需要定期对索引进行维护,包括优化查询语句、删除不必要的索引等。 4. 索引优化 MySQL提供了一些工具来优化索引,例如EXPLAIN命令可以帮助分析查询语句的执行计划,找出慢查询和不必要的全表扫描。可以使用OPTIMIZE TABLE命令来优化表的索引和碎片,从而提高查询性能。还可以使用缓存来避免频繁的查询操作,例如使用Memcached或Redis等缓存工具。 以上就是MySQL索引的一些基本概念和使用方法,需要根据实际情况进行选择和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值