【MySQL】索引 (八)

🚗MySQL学习·第八站~

🚩本文已收录至专栏:MySQL通关路

❤️文末附全文思维导图,感谢各位点赞收藏支持~

一.引入

索引(index)是帮助MySQL高效获取数据的数据结构(有序)。数据库除了存储数据之外,还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 当我们在查找数据的时候,就可以在这些数据结构上实现高级查找算法,快速查找到我们想要的数据,这种数据结构就是索引

假如我们要执行一条查询SQL语句 : select * from user where age = 45;

  • 在无索引情况下

在无索引的情况下,查询数据就需要从第一行开始扫描,一直扫描到最后一行,我们称之为全表扫描,性能很低

  • 在有索引情况下

如果我们针对于这张表的age字段建立了索引,假设索引的数据结构就是二叉树(实际并不是,而是一种比二叉树更高效的数据结构),那么也就意味着,会对age这个字段建立一个二叉树的索引结构。此时我们在进行查询时,只需要扫描三次就可以找到数据了,极大的提高的查询的效率。

  • 当然使用索引也存在的优缺点,需要我们慎重选择
优势 劣势
提高数据检索的效率,降低数据库的IO成本 索引列也是要占用空间
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗 索引大大提高了查询效率,同时却也降低更新表的速度, 如对表进行INSERT、UPDATE、DELETE时,效率降低。

二.数据结构

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

索引结构 描述
B+Tree索引 最常用的索引类型,大部分引擎都支持 B+ 树索引
Hash索引 底层数据结构是用哈希表实现 的, 只有精确匹配索引列的查询才有效, 不支持范围查询,也无法利用索引完成排序,但是查询效率较高。
R-tree(空间索 引) 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类 型,通常使用较少
Full-text(全文 索引) 是一种通过建立倒排索引,快速匹配文档的方式。类似于 Lucene,Solr,ES

不同的存储引擎对于索引结构的支持情况也有所不同

索引 InnoDB MyISAM Memory
B+tree索引 支持 支持 支持
Hash 索引 不支持 不支持 支持
R-tree 索引 不支持 支持 不支持
Full-text 支持 支持 不支持

我们平常所说的索引,如果没有特别指明,一般都是指B+树结构组织的索引。

特别说明 :MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加了一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree ,提高区间访问的性能,利于排序。

三.分类说明

在MySQL数据库中,将索引的具体类型主要分为以下几类:

分类 含义 特点 关键字
主键索引 针对于表中主键创建的索引 默认自动创建, 只能有一个 PRIMARY
唯一索引 避免同一个表中某数据列中的值重复 可以有多个 UNIQUE
常规索引 快速定位特定数据 可以有多个
全文索引 全文索引查找的是文本中的关键词,而不是比 较索引中的值 可以有多个 FULLTEXT

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

分类 含义 特点
聚集 索引(Clustered Index) 数据存储与索引放到了一块 ,索引结构的叶子节点保存了行数据 必须有 ,而且只有一个
二级 (非聚集)索引(Secondary Index) 数据与索引分开存储 ,索引结构的叶子节点关联的是对应的主键 可以存在多个

聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引。

  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。

  • 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引

  • 聚集索引 的叶子节点下挂的是这一行的数据

  • 二级(非聚集)索引 的叶子节点下挂的是该字段值对应的主键值

当我们执行上述的SQL语句:

  • 由于是根据name字段进行查询,所以先根据name='Arm'到name字段的二级索引中进行匹配查找。但是在二级索引中只能查找到 Arm 对应的主键值 10。
  • 由于查询返回的数据是*,所以此时,还需要根据主键值10,到聚集索引中查找10对应的记录,最 终找到10对应的行row。
  • 最终拿到这一行的数据,直接返回即可。

这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取数据的方式,就称之为回表查询。

由于存在回表查询,我们通过聚集索引查询值的方式要比通过二级索引查询值的方式快很多。因为走聚集索引,可以直接返回数据。 而走二级索引,需要先获取id值,然后再查询聚集索引获取值要慢许多。

四.相关操作

(1) 创建索引

  • 语法
sql 复制代码
CREATE [ UNIQUE | FULLTEXT ] INDEX 索引名 ON table_name (字段1,字段2,... );
  • 示例
sql 复制代码
-- 为用户表的姓名name字段创建名为idx_user_name的唯一索引
CREATE UNIQUE INDEX idx_user_name ON user(name);

-- 为用户表的手机号phone字段创建名为idx_user_phone的普通索引
CREATE INDEX idx_user_phone ON user(phone);

-- 为用户表的profession,age,status字段创建名为idx_user_pro_age_stae的联合索引
CREATE INDEX idx_user_pro_age_sta ON user(profession,age,status);

(2) 查看索引

  • 语法
sql 复制代码
SHOW  INDEX  FROM  表名;
  • 查看我们刚在创建的索引

(3) 删除索引

  • 语法
sql 复制代码
DROP INDEX 索引名 ON 表名;
  • 删除name 索引,再次查看发现索引已删除

五.索引失效情况

(1) 最左前缀法则

如果索引了多列(联合索引),要遵守最左前缀法则

最左前缀法指的是:查询会从索引的最左列开始,最左边的索引列必须存在,否则索引全部失效。并且不能跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。

注:在where条件中索引最左列存在即可,在order by多字段排序中索引最左列必须为排序条件最左列。

  • 例如对于我们上述在 user 表中创建的联合索引,这个联合索引涉及到三个字段,顺序分别为:profession, age,status。 遵循最左前缀法指的是,查询时,最左变的索引列,也就是profession必须存在,否则索引全部失效。 而且中间不能遗漏某一列,否则该列后面的字段索引将失效,例如遗漏age列,status也会失效。

例如我们可以查看以下几种SQL的explain执行计划,可以发现索引有效

sql 复制代码
-- 全部存在,没问题
explain select * from user where profession = '软件工程' and age = 31 and status = '0';

-- 漏了status,其索引失效
explain select * from user where profession = '软件工程' and age = 31;

-- 漏了status,age,其索引失效
explain select * from user where profession = '软件工程';

-- 漏了age,其索引失效与status索引都失效
explain select * from user where profession = '软件工程' and status = '0';

-- 与编写的先后顺序无关,存在即可
explain select * from user where age = 31 and status = '0' and profession = '软件工程';

而我们遗漏了profession,即违背最左前缀法则将会导致索引失效

sql 复制代码
-- 失效
explain select * from user where age = 31 and status = '0';

-- 失效
explain select * from user where status = '0';

-- 失效
explain select * from user where age = 31;

(2) 范围查询

联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效

  • 例如我们进行如下查询
sql 复制代码
explain select * from user where profession = '软件工程' and age > 30 and status = '0';

根据长度key_len我们可以知道:联合索引生效了,但是只有profession与age的索引生效了,status字段的索引没有生效。

值得注意的是 :当范围查询使用>=<= 时,则不会产生影响。 因此,在业务允许的情况下,进行范围查询时,为了避免索引失效,我们可以使用类似于 >=<= 这类的范围查询,代替使用><

(3) 索引列运算

当我们在索引列上进行运算操作, 索引也将失效。

  • 例如我们对索引列进行函数运算,索引将失效
sql 复制代码
explain  select  *  from  user  where  substring(phone,10,2) = '15'

(4) 字符串不加引号

当我们对添加索引的字符串类型字段进行操作时,如果字符串不加引号 ,对于查询结果,没什么影响,但是数据库存在隐式类型转换,索引将失效

  • 例如 phone 为 varchar类型字段

(5) 模糊查询

如果仅仅是尾部模糊匹配,索引不会失效 。如果是头部模糊匹配,索引失效

  • 我们发现,在like模糊查询中,在关键字后面加%,索引可以生效。而如果在关键字 前面加了%,索引将会失效

(6) or连接条件

用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到

  • 例如我们先删除age字段上的联合索引,再进行or连接查询

我们发现当or连接的条件,左右两侧字段都有索引时,索引才会生效~

(7) 数据分布影响

如果MySQL评估使用索引比全表扫描更慢,则不使用索引。

  • 例如,下面使用相同的SQL语句,只是传入的字段值不同,最终的执行计划也完全不一样

这是因为MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不如走全表扫描来的快,此时索引就会失效。

六.使用优化

(1) SQL提示

当我们的字段存在多个索引时,MySQL会进行评估自动选择一个索引进行使用。我们也可以借助于SQL提示指定MySQL使用哪个索引

  • use index :建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进行评估,即或许不会遵从建议)
sql 复制代码
explain select * from 表名 use index(索引名) where 查询条件....;
  • ignore index :忽略指定的索引
sql 复制代码
explain select * from 表名 ignore index(索引名) where 查询条件....;
  • force index : 强制使用索引
sql 复制代码
explain select * from 表名 force index(索引名) where 查询条件....;

(2) 覆盖索引

在需求允许的情况下,尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *的使用。

例如我们使用age索引查询数据,如果只需要id,profession,age, status,那么我们可以直接写成select id,profession,age, status from user where profession = '软件工程' and age = 31 and status = '0' ,而不是select *.

从上述的执行计划我们可以看到,前面两天SQL的结果为 Using where; Using Index ; 而后面两条SQL的结果为: Using index condition

Extra 含义
Using where; Using Index 查找使用了索引,但是需要的数据都在索引列中能找到,所以不需 要回表查询数据
Using index condition 查找使用了索引,但是需要回表查询数据

这里便牵涉到了我们上述所介绍的回表查询,我们在user表中创建了一个联合索引 idx_user_pro_age_sta,该索引关联了三个字段 profession、age、status,而这个索引也是一个二级索引,所以叶子节点下面挂的是这一行的主键id。 所以当我们查询返回的数据在 id、profession、age、status 之中,则直接走二级索引直接返回数据了。 如果超出这个范围,就需要拿到主键id,再去扫描聚集索引,再获取额外的数据 了,这个过程就是回表。 而我们如果一直使用select * 查询返回所有字段值,很容易就会造成回表查询(除非是根据主键查询,此时只会扫描聚集索引)。

  • 根据id查询,直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。
  • 根据name字段查询,查询二级索引,但是由于查询返回在字段为 id,name,在name的二级索 引中,这两个值都是可以直接获取到的,因为覆盖索引,所以不需要回表查询,性能高。
  • 多查询一个gender字段,由于在name的二级索引中,不包含gender,所以,需要两次索引扫描,也就是需要回表查询,性能相 对较差一点。

(3) 前缀索引

当字段类型为字符串(varchar,text,longtext等)时,有时候这些字段值会非常大(例如一段很长的文本),如果直接对此建立索引,这会让索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率

  • 创建语法
sql 复制代码
create index 索引名 on 表名(字段名(索引长度)) ;

有时候我们难以确定应该创建多长的索引,这时可以根据索引的选择性来决定。选择性是指不重复的索引值(基数)和数据表的记录总数的比值, 索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

sql 复制代码
-- 可以直接计算比值
select  count(distinct 字段名) / count(*) from 表名;

-- 也可以使用函数截取部分数据进行计算
 select  count(distinct substring(字段名,开始位置,结束位置)) / count(*) from 表名;
  • 前缀索引的查询流程

(4) 设计原则

  • 针对于数据量较大,且查询比较频繁的表建立索引。
  • 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
  • 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
  • 如果是字符串类型的字段,字段的长度较长 ,可以针对于字段的特点,建立前缀索引
  • 在复合业务场景的情况下,尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间, 避免回表,提高查询效率。
  • 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率
  • 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含 NULL值时,它可以更好地确定哪个索引最有效地用于查询

七.全文概览

相关推荐
远歌已逝2 小时前
维护在线重做日志(二)
数据库·oracle
qq_433099403 小时前
Ubuntu20.04从零安装IsaacSim/IsaacLab
数据库
Dlwyz3 小时前
redis-击穿、穿透、雪崩
数据库·redis·缓存
工业甲酰苯胺5 小时前
Redis性能优化的18招
数据库·redis·性能优化
没书读了6 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
i道i7 小时前
MySQL win安装 和 pymysql使用示例
数据库·mysql
小怪兽ysl7 小时前
【PostgreSQL使用pg_filedump工具解析数据文件以恢复数据】
数据库·postgresql
wqq_9922502777 小时前
springboot基于微信小程序的食堂预约点餐系统
数据库·微信小程序·小程序
爱上口袋的天空7 小时前
09 - Clickhouse的SQL操作
数据库·sql·clickhouse
Oak Zhang8 小时前
sharding-jdbc自定义分片算法,表对应关系存储在mysql中,缓存到redis或者本地
redis·mysql·缓存