MySQL调优之索引优化
一、索引基本知识
1、索引的优点
1、减少了服务器需要扫描的数据量
2、帮助服务器避免排序和临时表
例子:
那么执行顺序:
所以有索引的话就不会创建零时表,临时表中,磁盘零时表比内存临时表更加的消耗性能。
3、将随机IO变成顺序IO
如何理解随机IO与顺序IO呢?如图:
这是一个磁盘,B是几何扇区(仅做标记,无特殊含义),A是磁道,C是扇区(sector,是物理读写的基本单位,通常是512字节),D由这样的多个扇区连在一起称为磁盘块(IO Block,是文件系统读写数据的最小单位,也叫磁盘簇)。扇区是磁盘最小的物理存储单元,操作系统将相邻的扇区组合在一起,形成一个块,对块进行管理。值得注意的是这是一个逻辑概念而不是物理概念。
大家都知道信息是存储在磁盘的扇区上,假如去吃旋转小火锅,上面的菜就是数据,旋转的旋转带就是磁道,夹在就是寻找菜的过程,如果旋转一圈需要四分钟,那么你吃一个菜,最短用时0min,最长4min,数学期望就是2min,如果你要吃8个菜并且不在一起(这就是随机IO),那么数学期望就是16min,如果要吃的8个菜紧挨着(顺序IO),那么数学期望就是2min,可见顺序IO能缩短寻址时间。
题外话:为什么存在磁盘块?(簇)
- 读取方便:由于扇区的容量比较小(512byte),数目众多在寻址时比较困难,所以操作系统就将相邻的扇区组合在一起,形成一个块(通常4k),再对块进行整体的操作。
- 分离对底层的依赖:操作系统忽略对底层物理存储结构的设计。通过虚拟出来磁盘块的概念,在系统中认为块是最小的单位。
我们平常所说的4K对齐也就是指的块大小,它表示操作系统读取磁盘时一次读取的数据大小。如果操作系统一次读取4K,但是块大小只有2K,就相当于一次IO要做2次磁盘寻址。而如果磁盘块大小刚好也是4K,那么一次IO就只需一次寻址。相对而言,磁盘寻址效率是很低的,多一次磁盘寻址肯定会更加导致IO效率低,因此对磁盘进行4K对齐也是提高了系统的IO性能。
读取数据磁盘上大概有三步:
2、索引的用处
1、快速查找匹配WHERE子句的行
2、从consideration中消除行,如果可以在多个索引之间进行选择,mysql通常会使用找到最少行的索引
3、如果表具有多列索引,则优化器可以使用索引的任何最左前缀来查找行
4、当有表连接的时候,从其他表检索行数据
5、查找特定索引列的min或max值
6、如果排序或分组时在可用索引的最左前缀上完成的,则对表进行排序和分组
7、在某些情况下,可以优化查询以检索值而无需查询数据行
3、索引的分类
3.1、主键索引
特殊的唯一索引,不允许有空值,例如表格中自增的id。
3.2、唯一索引
索引列值在表中唯一,在数据库表结构中对字段添加唯一索引后进行数据库进行存储操作时数据库会判断库中是否已经存在此数据,不存在此数据时才能进行插入操作。
唯一索引经常用在插入数据时,例如高并发下,如何避免插入两条同样单号的数据呢?当然是在存储数据的时候查一遍,那么怎样查找快呢? 当然是创建索引,所以,在创建唯一约束的时候就创建了唯一索引。
注意的坑
添加唯一索引后有一种特殊情况,那就是如果该字段没有限制非空的话,存在插入NULL值的情况,此时,唯一索引并不起作用,也就是你可以插入n条该字段为null的数据。说白了,mysql中null != null 。
除此之外,如果插入空字符串的话,例如‘ ’ ‘ ’
不管中间是多少个空字符串在插入的时候都算作‘’ ,即空串不论多长,只能插入一条。
3.3、普通索引
其他的就不写了,提出个问题:普通索引和唯一索引哪个效率高?
后面再开篇详谈。
3.4、全文索引
具体实现是正排索引和倒排索引,主要使用的是倒排索引,如果要在很多数据中搜查关键字,例如google搜索"huawei",如果底层用like"huawei'"的话,会非常慢,需要扫描整个数据库,为了提高搜素效率,可以建立关键字索引,属性值为文章id的数组,然后根据id确定文章位置,加权后将信息以列表形式返回。
3.5、组合索引
简单来说就是将多个列联合设置为索引,使用时遵循最左匹配原则
4、索引有关的技术名词
4.1、回表
4.2、覆盖索引
前两者参考我之前的博文:什么是聚集索引、非聚集索引、覆盖索引?
4.3、索引下推
参考我之前的推文:MySQL之谓词下推
4.4、最左匹配
先说结论:当一个表格有N个属性时,将(A,B,C)设置为联合索引,那么在查询时,索引是否有效遵循下表:
索引 | 是否有效 |
---|---|
A | 有效 |
A and B | 有效 |
A or B | 无效 |
A and C | 有效 |
B and C | 无效 |
A and B and C | 有效 |
也就是,从左到右,任何一次查询匹配了最左边的A才有效,A or B 意味着有一次只匹配了B,那么无效。
5、索引采用的数据结构
5.1、哈希表
MySQL中,Memory使用的哈希表作为索引,InnoDB引擎有一个特殊的功能叫做自适应哈希索引。
5.2、B+树
参考我之前的文章:为什么MySQL索引使用B+树
6、索引匹配方式
下面的讨论中,索引为(name,age,pos)
6.1、全值匹配
全值匹配指的是和索引中的所有列进行匹配
explain select * from staffs where name = 'July' and age = '23' and pos = 'dev';
6.2、匹配最左前缀
只匹配前面的几列
explain select * from staffs where name = 'July' and age = '23';
explain select * from staffs where name = 'July';
6.3、匹配列前缀
可以匹配某一列的值的开头部分
explain select * from staffs where name like 'J%';
explain select * from staffs where name like '%y';
6.4、匹配范围值
可以查找某一个范围的数据
6.5、精确匹配某一列并范围匹配另外一列
可以查询第一列的全部和第二列的部分
explain select * from staffs where name = 'July' and age > 25;
6.6、只访问索引的查询
查询的时候只需要访问索引,不需要访问数据行,本质上就是覆盖索引
explain select name,age,pos from staffs where name = 'July' and age = 25 and pos = 'dev';
二、哈希索引
1、HashMap索引的特点
基于哈希表的实现,只有精确匹配索引所有列的查询才有效
哈希索引自身只需存储对应的hash值,所以索引的结构十分紧凑,这让哈希索引查找的速度非常快
2、HashMap索引的限制
- 哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。
- 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。
- 哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。
- 哈希索引只支持等值比较查询,包括=、IN()、<>(注意<>和<=>是不同的操作)。也不支持任何范围查询,例如WHERE price>100。
- 访问哈希索引的数据非常快,除非有很多哈希冲突(不同的索引列值却有相同的哈希值)。当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行。
- 如果哈希冲突很多的话,一些索引维护操作的代价也会很高。例如,如果在某个选择性很低(哈希冲突很多)的列上建立哈希索引,那么当从表中删除一行时,存储引擎需要遍历对应哈希值的链表中的每一行,找到并删除对应行的引用,冲突越多,代价越大。
3、自适应哈希
先总结下思路:Innodb 本身是使用的B+Tree索引,自适应hash只是提供的一种特性,当数据库中某些热点数据被频繁访问的时候,存储引擎会自己适应地创造一个哈希索引(只适用于=的情况,范围查找不适用),这个哈希索引的目的是直接对判断条件取Hash运算,得出数据存储的地址,而不用一层层的取读取B+Tree的磁盘块,从而提高了料率。
图示:
4、案例
当需要存储大量的URL,并且根据URL进行搜索查找,如果使用B+树,存储的内容就会很大
select id from url where url=""
也可以利用将url使用CRC32做哈希,可以使用以下查询方式:
select id fom url where url="" and url_crc=CRC32("")
此查询性能较高原因是使用体积很小的索引来完成查找
三、组合索引
当包含多个列作为索引,需要注意的是正确的顺序依赖于该索引的查询,同时需要考虑如何更好的满足排序和分组的需要,遵循最左匹配原则,下图前提是创建了组合索引(3,4,5):
四、聚簇索引与非聚簇索引
1、聚簇索引
定义:不是单独的索引类型,而是一种数据存储方式,指的是数据行跟相邻的键与值紧凑的存储在一起,在文件上看会将两个文件放在一起。
参考我之前的文章:什么是聚集索引、非聚集索引、覆盖索引?
1.1、优点
1、可以把相关数据保存在一起
2、数据访问更快,因为索引和数据保存在同一个树中
3、使用覆盖索引扫描的查询可以直接使用页节点中的主键值
1.2、缺点
1、聚簇数据最大限度地提高了IO密集型应用的性能,如果数据全部在内存,那么聚簇索引就没有什么优势
2、插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式
3、更新聚簇索引列的代价很高,因为会强制将每个被更新的行移动到新的位置
4、基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临页分裂的问题
5、聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候
2、非聚簇索引
与聚簇索引最大的区别就是,非聚簇索引数据那儿存储的不是数据,可能是数据的地址,该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点(例如存储数据存放的地址,而非数据),有一个指针指向对应的数据块。
五、覆盖索引
1、基本介绍
1、如果一个索引包含所有需要查询的字段的值,我们称之为覆盖索引
2、不是所有类型的索引都可以称为覆盖索引,覆盖索引必须要存储索引列的值
3、不同的存储实现覆盖索引的方式不同,不是所有的引擎都支持覆盖索引,memory不支持覆盖索引
2、优势
1、索引条目通常远小于数据行大小,如果只需要读取索引,那么mysql就会极大的较少数据访问量
2、因为索引是按照列值顺序存储的,所以对于IO密集型的范围查询会比随机从磁盘读取每一行数据的IO要少的多
3、一些存储引擎如MYISAM在内存中只缓存索引,数据则依赖于操作系统来缓存,因此要访问数据需要一次系统调用,这可能会导致严重的性能问题
4、由于INNODB的聚簇索引,覆盖索引对INNODB表特别有用
3、详细介绍案例
1、当发起一个被索引覆盖的查询时,在explain的extra列可以看到using index的信息,此时就使用了覆盖索引
2、在大多数存储引擎中,覆盖索引只能覆盖那些只访问索引中部分列的查询。不过,可以进一步的进行优化,可以使用innodb的二级索引来覆盖查询。
例如:actor使用innodb存储引擎,并在last_name字段又二级索引,虽然该索引的列不包括主键actor_id,但也能够用于对actor_id做覆盖查询
其他参考我之前的文章:什么是聚集索引、非聚集索引、覆盖索引?
六、优化细节
-
当使用索引列进行查询的时候尽量不要使用表达式,把计算放到业务层而不是数据库层
select actor_id from actor where actor_id=4;
select actor_id from actor where actor_id+1=5;
-
尽量使用主键查询,而不是其他索引,因此主键查询不会触发回表查询
-
使用前缀索引
有时候需要索引很长的字符串,这会让索引变的大且慢,通常情况下可以使用某个列开始的部分字符串,这样大大的节约索引空间,从而提高索引效率,但这会降低索引的选择性,索引的选择性是指不重复的索引值和数据表记录总数的比值,范围从1/#T到1之间。索引的选择性越高则查询效率越高,因为选择性更高的索引可以让mysql在查找的时候过滤掉更多的行。
一般情况下某个列前缀的选择性也是足够高的,足以满足查询的性能,但是对应BLOB,TEXT,VARCHAR类型的列,必须要使用前缀索引,因为mysql不允许索引这些列的完整长度,使用该方法的诀窍在于要选择足够长的前缀以保证较高的选择性,通过又不能太长。
案例演示:
-
使用索引扫描来排序
mysql有两种方式可以生成有序的结果:通过排序操作或者按索引顺序扫描,如果explain出来的type列的值为index,则说明mysql使用了索引扫描来做排序
扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那么就不得不每扫描一条索引记录就得回表查询一次对应的行,这基本都是随机IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢
mysql可以使用同一个索引即满足排序,又用于查找行,如果可能的话,设计索引时应该尽可能地同时满足这两种任务。
只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的排序方式都一样时,mysql才能够使用索引来对结果进行排序,如果查询需要关联多张表,则只有当orderby子句引用的字段全部为第一张表时,才能使用索引做排序。order by子句和查找型查询的限制是一样的,需要满足索引的最左前缀的要求,否则,mysql都需要执行顺序操作,而无法利用索引排序
-
union all,in,or都能够使用索引,但是推荐使用in
-
范围列可以用到索引
范围条件是:<、>
范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
-
强制类型转换会全表扫描
-
更新十分频繁,数据区分度不高的字段上不宜建立索引
更新会变更B+树,更新频繁的字段建议索引会大大降低数据库性能
类似于性别这类区分不大的属性,建立索引是没有意义的,不能有效的过滤数据,
一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算
-
创建索引的列,不允许为null,可能会得到不符合预期的结果
-
当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致
-
能使用limit的时候尽量使用limit
-
单表索引建议控制在5个以内
-
单索引字段数不允许超过5个(组合索引)
-
创建索引的时候应该避免以下错误概念
索引越多越好
过早优化,在不了解系统的情况下进行优化(写时尽量rang级别,上线之后再优化)
七、索引监控
1、show status like 'Handler_read%';
2、参数解释
Handler_read_first:读取索引第一个条目的次数
Handler_read_key:通过index获取数据的次数
Handler_read_last:读取索引最后一个条目的次数
Handler_read_next:通过索引读取下一条数据的次数
Handler_read_prev:通过索引读取上一条数据的次数
Handler_read_rnd:从固定位置读取数据的次数
Handler_read_rnd_next:从数据节点读取下一条数据的次数
八、索引优化分析案例
1、预先准备好数据
逐步开始进行优化:
2、第一个案例:
通过查看执行计划发现type=all,需要进行全表扫描:
优化一、为transaction_id创建唯一索引
当创建索引之后,唯一索引对应的type是const,通过索引一次就可以找到结果,普通索引对应的type是ref,表示非唯一性索引赛秒,找到值还要进行扫描,直到将索引文件扫描完为止,显而易见,const的性能要高于ref
优化二、使用覆盖索引,查询的结果变成 transaction_id,当extra出现using index,表示使用了覆盖索引
3、第二个案例
__EOF__

本文链接:https://www.cnblogs.com/Courage129/p/14194248.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端