百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

一文搞懂Mysql索引的分类和实现逻辑

wptr33 2025-01-04 23:28 26 浏览

索引介绍

索引(index)在mysql中也叫做键(key)是存储引擎用于快速找到记录的一种数据结构。mysql的索引类似于一本书的“目录”,如果想在一本书中找到某个特定主题,一般会先看书的“目录”,找到对应的页码。

在mysql中,存储引擎用类似的方法使用索引,其现在索引中找到对应值,然后根据匹配的索引记录找到对应的数据行。加入运行下面的查询:


select username from system_user where user_id = 5;


如果在user_id列上建有索引,则Mysql将使用该索引找到user_id为5的行,也就是说,Mysql先在索引上按值查找,然后返回所有包含该值的数据行。

索引可以包含一个或多个列的值。如果索引包含多个列,那么列的顺序页十分重要,因为Mysql只能高效的使用索引的最左前缀列。创建一个包含两个列的索引,和创建两个只包含一列的索引是大不相同的。


索引类型

索引有多种类型,以适应不同的应用场景。Mysql中,索引是在存储引擎层而不是服务器层实现的。所以,没有统一的索引标准,不同引擎的索引工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引。即使多个存储引擎支持同一种类型的索引,其底层的实现也可能不同。

Mysql的索引类型有:B-Tree、哈希(hash index)、空间数据索引(R-Tree)、全文索引、其他索引(一些特殊引擎使用)

B-Tree

存储引擎以不同的方式使用B-Tree索引,性能也各有不同,各有优劣。例如,MyISAM使用前缀压缩技术使得索引更小,但InnoDB则按照原数据格式进行存储。再如MyISAM索引通过数据的物理位置引用被索引的行,而InnoDB则根据主键引用被索引的行

建立在B-Tree结构(从技术上来说是B+Tree)上的索引

B-Tree索引能够加快访问速度,因为存储引擎不再需要进行全表扫描获取需要的数据,取而代之的是从索引的根节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。通过比较子节点页的值和要查找的值可以找到合适的指针进入下层子节点,这些指针实际上定义了子节点页中值的上限和下限。最终存储引擎要么是找到对应的值,要么该记录不存在。

B-Tree对索引列是顺序组织存储的,所以很适合查找范围数据。

可以使用B-Tree索引的查询类型。B-Tree索引适用于全键值、键值范围或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找。

全值匹配

全值匹配指的是和索引中的所有列进行匹配。

匹配最左前缀

只是用索引的第一列。

匹配列前缀

也可以只匹配某一列的值的开头部门。

匹配范围值

只是用索引的第一列。

精确匹配某一列并范围匹配另外一列

第一列的全匹配,第二列的列前缀匹配。

只访问索引的数据(覆盖索引)

B-Tree通常可以支持“只访问索引的查询”,即查询只需要访问索引,而无需访问数据行。

举例说明:

#创建索引
ALTER TABLE `jr_person`ADD INDEX `name_mobile` (`name`, 'alias',`mobile`) USING BTREE;
#全值匹配
EXPLAIN SELECT * from jr_person where alias = '维修工' and NAME = '刘国军' and mobile = '15837109838';
执行结果
+----+-------------+-----------+-------+---------------+-------------+---------+-------------+------+-------+
| id | select_type | table     | type  | possible_keys | key         | key_len | ref         | rows | Extra |
+----+-------------+-----------+-------+---------------+-------------+---------+-------------+------+-------+
|  1 | SIMPLE      | jr_person | const | name_mobile   | name_mobile | 127     | const,const |    1 |       |
+----+-------------+-----------+-------+---------------+-------------+---------+-------------+------+-------+
1 row in set (0.64 sec)

#匹配最左前缀,即只是用索引第一列
EXPLAIN SELECT * from jr_person where NAME = '刘国军';
执行结果
+----+-------------+-----------+------+---------------+-------------+---------+-------+------+-------------+
| id | select_type | table     | type | possible_keys | key         | key_len | ref   | rows | Extra       |
+----+-------------+-----------+------+---------------+-------------+---------+-------+------+-------------+
|  1 | SIMPLE      | jr_person | ref  | name_mobile   | name_mobile | 82      | const |    1 | Using where |
+----+-------------+-----------+------+---------------+-------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

#匹配列前缀
EXPLAIN SELECT * from jr_person where name like '刘%';
执行结果
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+
| id | select_type | table     | type  | possible_keys | key         | key_len | ref  | rows | Extra       |
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+
|  1 | SIMPLE      | jr_person | range | name_mobile   | name_mobile | 82      | NULL |  216 | Using where |
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+
1 row in set (0.00 sec)

#匹配范围值
EXPLAIN SELECT * from jr_person where name BETWEEN '刘' and '张';
执行结果
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+
| id | select_type | table     | type  | possible_keys | key         | key_len | ref  | rows | Extra       |
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+
|  1 | SIMPLE      | jr_person | range | name_mobile   | name_mobile | 82      | NULL |  776 | Using where |
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+
1 row in set (0.04 sec)

#精确匹配某一列并范围匹配另外一列
EXPLAIN SELECT * from jr_person where NAME = '刘国军' and  alias like '维%';
+----+-------------+-----------+------+---------------+-------------+---------+-------+------+-------------+
| id | select_type | table     | type | possible_keys | key         | key_len | ref   | rows | Extra       |
+----+-------------+-----------+------+---------------+-------------+---------+-------+------+-------------+
|  1 | SIMPLE      | jr_person | ref  | name_mobile   | name_mobile | 82      | const |    1 | Using where |
+----+-------------+-----------+------+---------------+-------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

#只访问索引的查询
EXPLAIN SELECT name,alias,mobile from jr_person where alias = '维修工' and NAME = '刘国军' and mobile = '15837109838';
执行结果
+----+-------------+-----------+-------+---------------+-------------+---------+-------------+------+-------+
| id | select_type | table     | type  | possible_keys | key         | key_len | ref         | rows | Extra |
+----+-------------+-----------+-------+---------------+-------------+---------+-------------+------+-------+
|  1 | SIMPLE      | jr_person | const | name_mobile   | name_mobile | 127     | const,const |    1 |       |
+----+-------------+-----------+-------+---------------+-------------+---------+-------------+------+-------+
1 row in set (0.00 sec)

关于Explan各个字段的介绍参考这篇博文:Explain命令详解

哈希索引

哈希索引(hash index)基于哈希表实现,只有精确匹配索引所有列的查询才有效。存储的每一行数据,存储引擎对所有的索引列计算一个哈希码(hash code);哈希索引将所有的哈希码储存在索引中,同时在哈希表中保存指向每个数据行的指针。


由于索引本身只存储哈希值,所以索引的结构十分紧凑,这让哈希索引查找速度变得非常快的同时,也产生了一些限制:

1、哈希索引只包含哈希值和行指针,而不存储字段,所以不能使用中银中的值来避免读取行(覆盖索引)。不过,访问内存中的行的速度非常快,所以大部分情况下这一点对应能的影响并不明显。

2、哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。

3、哈希索引不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。例如,在数据列(A,B)上建立哈希索引,如果查询只有数据列A,则无法使用该索引。

4、哈希索引只支持等值比较查询,包括=,in(),<=>(至于<=> 和<> 是不同的操作,<=>与=类似区别在于null值的判断)

5、访问哈希索引的数据非常快,除非有很多哈希冲突(不同的索引列值却有相同的哈希值),当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,知道赵丹所有符合条件的行。

6、如果哈希冲突很多的话,一些索引维护操作的代价也会很高。例如,如果在某个选择性很低(哈希冲突很多)的列上建立哈希索引,那么当从表中删除一行时,存储引擎需要遍历对应哈希链表中的每一行,找到并删除对应行的引用,冲突越多,代价越大。

因为以上种种限制,哈希索引只适用于某些特定场合。而一旦适合哈希索引,则他带来的性能提升将非常显著。


InnoDB引擎不支持hash索引,但是他有一个特殊的功能叫做“自适应哈希索引(adaptive hash index)”。当InnoDb注意到某些索引值被使用的非常频繁时,他会在内存中基于B-Tree索引之上再建立一个哈希索引,这样就让B-Tree索引也具有哈希索引的一些优点,比如快速的哈希查找。这是一个完全自动的、内部的行为,用户无法控制或者配置,不过如果有比较,完全可以关闭该功能。


创建自定义哈希索引。如果存储引擎不支持哈希索引,则可以模拟像InnoDB一样创建hash索引,这可以享受一些哈希索引的遍历,例如只需要很小的索引就可以为超长的建创建索引。

具体思路:在B-Tree基础上创建一个伪哈希索引。这和真正的哈希索引不是一回事,因为还是使用B-Tree进行查找,但是它使用哈希值而不是键值本身进行查找。你需要做的就是在查询的Where子句中手动指定使用的哈希函数。

下边是一个实例,例如要存储大量的URL,并需要根据URL进行搜索查找。如果使用B-Tree来存储URL,存储的内容就会很打,因为URL本身都很长。正常情况下会有如下查询:

select id from url where url = "http://www.phpblog.cn";

若删除原来URL列上的索引,而新增一个被索引的url_crc列,使用CRC32做哈希,就一颗使用下面的方式查询:

select id from url where url = "http:'//www.phpblog.cn" and url_crc = CRC32("http://www.phpblog.cn")

这样做性能会很高,因为mysql优化器会使用这个选择性很高而体积很小的基于url_crc列的索引来完成查找。即使有多个记录有相同的索引值,查找仍然很快,只需要根据哈希值做快速的整数比较就能找到索引条目,然后一一比较返回对应的行。另外一种方式就是对完整的URL字符串做索引,那样会非常慢。


这样实现的缺陷是需要维护哈希值。可以手动维护,也可以用触发器实现。


如果使用这种方式,记住不要使用md5()和SHA1()作为哈希函数。因为这两个函数计算出来的哈希值是非常长的的字符串,会浪费大量的空间,比较时也会更慢。SHA1()和MD5()是强加密函数,设计目标是最大限度消除冲突,但这里并不需要这样高的要求。简单哈希函数的冲突在一个可以接受的范围,同时又能够提供更好的性能。


如果数据表非常大,CRC32()会出现大量的哈希冲突,则可以考虑自己实现一个简单的64位哈希函数。这个自定义函数要返回整数,而不是字符串。一个简单的方法可以使用MD5()函数返回值的一部分作为自定义哈希函数。这可能比自己写一个哈希算法的性能要差,不过这样实现最简单:

select conv(right(md5('http://www.phpblog.cn'),16),16,10) as HASH64;

CONV 可以转变目标数据的进制,上边的案例将md5的后16位,从16进制转换成10进制数字。


索引的优点

索引可以让服务器(数据库服务器)快读地定位到表的制定位置。但这并不是索引的唯一作用,到目前为止可以看到,根据创建索引的数据结构不同,索引也有一些其他的附加作用。

最常见的B-Tree索引,由于是按顺序存储数据,所以MySQL可以用作ORDER BY和GROUP BY操作。因为数据是有序的,所以B-Tree也就会将相关的列值存储在一起。最后,因为索引中存储了实际的值,所以某些查询只使用索引就能够完成全部查询。根据此特性,总结下来索引有如下三个优点:

  1. 索引大大减少了服务器需要扫描的数据量。
  2. 索引可以帮助服务器避免排序和临时表。
  3. 索引可以将随机I/O变为顺序I/O。

索引是最好的解决方案吗?

做引并不总是最好的工具。总的来说,只有当索引帮助存储引擎快速查找到记录带来的好处大于其带来的额外工作时,索引才是有效的。对于非常小的表,大部分情况下简单的全表扫描更高效。对于中到大型的表,索引就非常有效。但对于特大型的表建立和使用索引的代价将随之增长。这种情况下,则需要一种技术可以直接区分出查询需要的一组数据,而不是一条记录一条记录的匹配。比如可以使用分区技术。

相关推荐

MySQL进阶五之自动读写分离mysql-proxy

自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...

Postgres vs MySQL_vs2022连接mysql数据库

...

3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?

引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...

一文由浅入深带你完全掌握MySQL的锁机制原理与应用

本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...

验证Mysql中联合索引的最左匹配原则

后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...

MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)

目录1.索引基础...

你会看 MySQL 的执行计划(EXPLAIN)吗?

SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...

MySQL 从入门到精通(四)之索引结构

索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...

mysql总结——面试中最常问到的知识点

mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...

mysql总结——面试中最常问到的知识点(2)

首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...

MySQL基础全知全解!超详细无废话!轻松上手~

本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...

深入剖析 MySQL 中的锁机制原理_mysql 锁详解

在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...

Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析

引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...

MySQL基础篇:DQL数据查询操作_mysql 查

一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...

MySql:索引的基本使用_mysql索引的使用和原理

一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...