当前位置: 首页 > article >正文

【MySQL学习笔记】MySQL的索引

MySQL索引

  • 1、索引概述
  • 2、 索引的数据结构
    • 2.1 B+Tree索引结构
    • 2.2 Hash索引结构
    • 2.3 InnoDB选择B+Tree的原因
  • 3、索引分类
  • 4、索引的语法
  • 5、SQL性能分析
    • 5.1 SQL执行频率
    • 5.2 慢查询日志
    • 5.3 profile详情
    • 5.4 explain执行计划
  • 6、索引使用规则
    • 6.1 最左前缀法则
    • 6.2 范围查询
    • 6.3索引失效情况
    • 6.4 SQL提示
    • 6.5 覆盖索引
    • 6.6 前缀索引
    • 6.7 单列索引与联合索引
  • 7、索引设计原则

1、索引概述

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

索引的优点:
1- 提高数据检索的效率,降低数据库的IO成本;
2- 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。

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

2、 索引的数据结构

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

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

存储引擎对索引的支持情况

索引InnoDBMyISAMMemory
B+Tree索引支持支持支持
Hash索引不支持不支持支持
R-Tree索引不支持支持不支持
Full-text5.6版本之后支持支持不支持

2.1 B+Tree索引结构

B+Tree的每个节点存储多个key与指针,假设key的个数为n,则指针的个数为n+1。key用于控制范围,指针指向下一个节点。
在B+Tree中,所有的元素都会出现在叶子结点,且数据只存储在叶子结点,所有的叶子结点形成单向链表。非叶子结点不存储数据,只起到索引的作用。这就使结点中的指针数量增加,树的高度降低。

而MySQL对经典B+Tree进行了优化,增加了一个指向相邻叶子结点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能。
B+Tree结构示意图:
在这里插入图片描述
MySQL索引数据结构的B+Tree:
在这里插入图片描述

2.2 Hash索引结构

哈希索引就是采用一定的Hash算法,将键值换算成新的Hash值,映射到对应的槽位上,然后存储在Hash表中。
如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决。
在这里插入图片描述
hash索引在存储的时候是没有顺序的
hash索引特点:
1- hash索引只能用于对等比较(in,=),不支持范围查询(between,>,<,…)
2- 无法利用索引完成排序操作
3- 查询效率高,通常只需要一次检索就可以了,效率通常要高于B+Tree索引
由于hash索引在存储的时候是没有顺序的,导致特点1,2。

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

2.3 InnoDB选择B+Tree的原因

为什么InnoDB存储引擎选择使用B+Tree索引结构,而不选择二叉树,红黑树,B-Tree,hash表?
首先要分析二叉树、红黑树、B-Tree存在的问题。
二叉树问题:顺序插入时,会形成一个链表,查询性能大大降低。大数据量情况下,层级较深,检索速度慢。
红黑树问题:虽然解决了二叉树形成链表的问题,但本质还是二叉树,大数据量情况下,层级深,检索速度慢。
B-Tree问题:在MySQL中,每个节点存储在一页中,页的大小固定为16k,B树节点中不仅存储了键值,还存储了数据,导致存储的键值减少,指针也减少。要保存同样大小的数据,只能增加树的高度,导致性能降低。
hash表:只能满足等值查询,不支持范围匹配。无法对数据进行排序操作。

因此可以得到以 下三点:
1- 相对于二叉树,红黑树,B+Tree层级少,搜索效率高。
2- 对于B-Tree,无论是叶子结点还是非叶子结点,都会保存数据,而每个节点是保存在一个页中,这样导致一页中存储的键值减少,指针跟着减少,要保存同样大小的数据,只能增加树的高度,导致性能降低;而B+Tree在B-Tree的基础上进行优化,只在叶子结点上存储数据,并且在相邻的叶子结点之间增加指针,形成双向链表,提高性能。
3- Hash索引只能支持等值操作,而B+Tree还支持范围匹配及排序操作。

3、索引分类

索引主要分为四类:

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

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

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

聚集索引选取规则:
1- 如果存在主键,主键索引就是聚集索引;
2- 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引;
3- 如果没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。
在这里插入图片描述

4、索引的语法

-- 创建索引
-- [unique | fulltext]:指定索引类型,可选
-- index_name:定义索引名,自定义
-- index_col:创建索引的字段,可选多个,如果同时指定多个,则称为联合索引,如果只指定一个,则称为单列索引
create [unique | fulltext] index index_name on table_name (index_col,...);
-- 创建索引时,索引默认是升序排序的,也可以指定索引的排序方式。指定索引的排序方式一般是考虑 order by 优化
create [unique | fulltext] index index_name on table_name (index_col1 asc, index_col2 desc,...);

-- 查看索引
show index from table_name;

-- 删除索引
-- 从 table_name 表中删除 index_name 索引
drop index index_name on table_name;

5、SQL性能分析

SQL性能分析主要是为SQL优化做准备,因此需要明确要为哪类SQL进行优化。一般而言,主要对DQL(也就是查询语句)这类SQL进行优化,对增删改的优化是次要的。

5.1 SQL执行频率

分析SQL执行频率。
MySQL客户端连接成功后,通过下面的命令可以提供服务器状态信息:

-- 查看服务器状态信息
-- session:指当前会话
-- global:指定全局
show [session | global] status;

通过如下指令,可以查看当前数据库的insert、update、delete、select的访问频次:

-- 查看当前数据库增删改查的访问频次
-- Com后面是下划线,一个下划线代表一个字符
-- insert --> com_insert
-- update --> com_update
-- delete --> com_delete
-- select --> com_select
show global status like 'Com_______';

5.2 慢查询日志

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。

MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:

# 开启MySQL慢查询日志开关
slow_query_log = 1
# 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time = 2

配置完毕之后,重新启动MySQL服务器进行测试,查看慢查询日志文件中记录的信息 localhost-slow.log,可以知道哪些SQL执行耗时过长,针对这些SQL进行优化。

5.3 profile详情

show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling参数,能够看到当前MySQL是否支持profile操作:

-- 查看是否支持profile操作
select @@have_profiling;

默认profiling是关闭的,可以通过 set 语句在 session/global 级别开启profiling:

-- 开启profiling
set [session | global] profiling = 1;

在开启profiling后就可以执行一系列业务SQL操作,然后通过如下指令查看指令的执行耗时:

-- 查看每一条SQL的耗时基本情况
show profiles;

-- 查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;

-- 查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;

5.4 explain执行计划

explain 或者 desc 命令获取 MySQL 如何执行 select 语句的信息,包括在 select 语句执行过程中表如何连接和连接的顺序。

-- 直接在 select 语句之前加上关键字 explain / desc
explain select 字段列表 from 表名 where 条件;

执行计划会返回很多信息(id、select_type、table、partitions、type、possible_keys、key、key_len、ref、rows、filtered、extra),下面一一介绍:
在这里插入图片描述
explain 执行计划各字段含义:

  • id:select 查询的序列号,表示查询中执行 select 子句或者是操作表的顺序( id 相同,执行顺序从上到下,id 不同,值越大,越先执行)。
  • select_type:表示 select 的类型,常见的取值有 simple (简单表,即不使用表连接或者子查询)、primary(主查询,即外层的查询)、union(union中的第二个或者后面的查询语句)、subquery(select / where 之后包含了子查询)等。
  • type:表示连接类型,性能由好到差的连接类型为null、system、const、eq_ref、ref、range、index、all。
  • possible_key:显示可能应用在这张表上的索引,一个或多个。
  • key:实际使用的索引,如果为null,则没有使用索引。
  • key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。
  • rows:MySQL认为必须要执行查询的行数,在InnoDB引擎的表中,是一个估计值,可能并不总是准确的。
  • filtered:表示返回结果的行数站需要读取行数的百分比,filtered的值越大越好。
  • extra:额外信息。

6、索引使用规则

6.1 最左前缀法则

如果索引了多列(联合索引),要遵循最左前缀法则。
最左前缀法则:查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)。
要满足最左前缀法则,只需要最左列存在,不关心位置。
举例:
若创建如下联合索引:

-- 在 user 表中根据 name,age,address 这三个字段创建联合索引
-- 最左列为 name ,要使用该索引,则需要存在 name 的条件
create index idx_user_name_age_address on user(name, age, address);

那么不同的查询语句使用 idx_user_name_age_address 索引有多种情况:

-- 使用 idx_user_name_age_address 索引,并且使用到了索引的全字段
select * from user where name = '张三' and age = 18 and address = '北京';

-- 使用 idx_user_name_age_address 索引,但是没有使用address字段,因为sql语句中没有 address 条件
select * from user where name = '张三' and age = 18;

-- 使用 idx_user_name_age_address 索引,只使用了 name 字段,因为sql语句中没有 age 和 address 条件
select * from user where name = '张三';

-- 使用 idx_user_name_age_address 索引,但只使用了 name 字段,因为跳跃了 age 这一列,使后面的字段失效
select * from user where name = '张三' and address = '北京';

-- 使用 idx_user_name_age_address 索引,并且使用了全字段,虽然 name 位置不在第一个,但是存在 name 字段条件
select * from user where age = 18 and address = '北京' and name = '张三';

-- 未使用 idx_user_name_age_address 索引,因为不满足最左前缀法则,条件没有 name 字段。
select * from user where age = 18 and address = '北京';

6.2 范围查询

联合索引中,出现范围查询(<,>),范围查询右侧的列索引失效
举例:
若创建如下联合索引:

-- 在 user 表中根据 job,age,address 这三个字段创建联合索引
-- 最左列为 job,要使用该索引,则需要存在 job 的条件
create index idx_user_job_age_address on user(job, age, address);

分析如下 sql 语句的索引使用情况:

-- 使用 idx_user_job_age_address 索引,但未使用 address 字段,这是因为 age > 25 为范围查询,范围查询右侧的列索引失效
select * from user where job = '程序员' and age > 25 and address = '北京';

-- 使用 idx_user_job_age_address 索引,并且使用了所有的字段,这是因为 age >= 25 规避了范围查询
select * from user where job = '程序员' and age >= 25 and address = '北京';

从上面的案例中可以受到启发,在项目开发中,在满足业务需求的情况下,尽量使用 >=,<=,而不是用 >,<。

6.3索引失效情况

索引列运算:不要在索引列上进行运算操作,否则索引将失效。

字符串不加引号:字符串类型字段使用时,不加引号,索引将失效。

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

or连接的条件:用 or 分割开的条件,如果 or 前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。只有 or 两侧都有索引时,才会生效。

数据分布影响:如果MySQL评估使用索引比全表扫描慢,则不使用索引。

6.4 SQL提示

SQL 提示是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。这个提示是指开发者给MySQL的提示。
语法:
在表后面加 use/ignore/force index(指定index)

-- use index:建议使用指定索引,但MySQL可以根据实际情况分析,是否使用该索引
explain select * from user use index(idx_user_name) where name = '张三';

-- ignore index: 忽略索引,不使用指定索引
explain select * from user ignore index(idx_user_name) where name = '张三';

-- force index:强制使用指定索引
explain select * from user force index(idx_user_name) where name = '张三';

6.5 覆盖索引

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

覆盖索引是研究返回字段对查询性能的影响。

研究如下几条 SQL,他们的 where 条件相同,但返回的字段不同:
前提:创建了如下几个索引

-- 在 user 表上给 job,age,gender 字段创建了联合索引 idx_user_job_age_gender 
create index idx_user_job_age_gender on user(job, age, gender);
-- 在 user 表上给 name 字段创建索引 idx_user_name 
create index idx_user_name on user(name);

分析 SQL:

-- 执行计划 extra 字段提示:using where; using index
-- 因为建立了 job,age,gender 这三个字段联合索引,联合索引中包含了查询返回的 id 和 job 数据,只需要一次二级查询即可。
explain select id, job from user where job = '程序员' and age = 25 and gender = 1;

-- 执行计划 extra 字段提示:using where; using index
-- 因为建立了 job,age,gender 这三个字段联合索引,联合索引中包含了查询返回的 id,job,age,gender 数据,只需要一次二级查询即可。
explain select id, job, age, gender from user where job = '程序员' and age = 25 and gender = 1;

-- 执行计划 extra 字段提示:using index condition
-- 虽然建立了 job,age,gender 这三个字段联合索引,但是索引列中不包含 name 字段,查询返回 name 字段需要根据 id 进行回表查询,性能降低。
explain select id, job, age, gender, name from user where job = '程序员' and age = 25 and gender = 1;

-- 执行计划 extra 字段提示:using index condition
-- * 通配表中所有字段,而 idx_user_job_age_gender 索引中只包含 id,job,age,gender数据,因此需要回表查询。
explain select * from user where job = '程序员' and age = 25 and gender = 1;

知识补充:
using index condition:查找使用了索引,但是需要回表查询数据。
using where; using index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据。
using where; using index 的性能高于 using index condition

6.6 前缀索引

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

建立前缀索引的语法:

-- idx_xxx:自定义索引名
-- table_name:表名
-- column:字段,一般为varchar,text等类型
-- n:前缀长度,截取字符串的长度
create index idx_xxx on table_name(column(n))

如何确定前缀?
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
可以通过下面的 sql 来计算选择性;

-- 这里以 user 表中的 email 字段为例,使用 email 的全部字符计算选择性
select count(distinct email) / count(*) from user;
-- 截取 email 字段的前5个字符计算选择性
select count(distinct substring(email, 1, 5)) / count(*) from user;

6.7 单列索引与联合索引

单列索引:即一个索引只包含单个列。
联合索引:即一个索引包含了多个列。
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。
多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询。

7、索引设计原则

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

http://www.kler.cn/a/501490.html

相关文章:

  • 任务调度系统Quartz.net详解2-Scheduler、Calendar及Listener
  • 零样本极速复刻语音!F5-TTS本地部署教程
  • Kivy App开发之UX控件ProgressBar进度条
  • Vue.js组件开发-图片剪裁性能优化最佳方案实例
  • vscode vue 自动格式化
  • iOS - 关联对象
  • SQL美化器优化
  • 鸿蒙路由通信(路由跳转/参数传递)
  • 搭建prometheus+grafana监控系统抓取Linux主机系统资源数据
  • 《框架程序设计》复习题解析-2
  • docker 自建rustdesk服务器测试
  • 51单片机和STM32集成蓝牙模块实用指南
  • 多个表单使用相同的 ref 和 rules,表单验证规则不生效
  • 前端开发:Web前端和HTML
  • Flutter 3.x 版本升级实战:让老项目焕发新生
  • 深入理解 Spring MVC 中的 @ModelAttribute 注解
  • 【Python学习系列】数据类型(二)
  • 《DOM NodeList》
  • Openstack持久存储-Swift,Cinder,Manila三者之间的区别
  • 【对象存储】-- s3:\\、s3n:\\、s3a:\\ 简介
  • 力扣 岛屿数量
  • 在线游戏靶场【overthewire.org】之linux基础练兵场
  • Github 2025-01-09 Go开源项目日报 Top10
  • docker--小白--导入timescaledb
  • 使用 WPF 和 C# 绘制图形
  • 稀疏编码 (Sparse Coding) 算法详解与PyTorch实现