CategoryResourceRepost/极客时间专栏/SQL必知必会/第二章:SQL性能优化篇/34丨答疑篇:关于索引以及缓冲池的一些解惑.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

160 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="34丨答疑篇关于索引以及缓冲池的一些解惑" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fa/0f/fa4af3b805cdbe60de8ab9fa7d4c420f.mp3"></audio>
这篇文章是进阶篇的最后一篇在这一模块中我主要针对SQL运行的底层原理进行了讲解其中还有很多问题没有回答我总结了进阶篇中常见的一些问题希望能对你有所帮助。下面的内容主要包括了索引原则、自适应Hash、缓冲池机制和存储引擎等。
## 关于索引B+树索引和Hash索引以及索引原则
### 什么是自适应 Hash 索引?
在回答这个问题前让我们先回顾下B+树索引和Hash索引
因为B+树可以使用到范围查找同时是按照顺序的方式对数据进行存储因此很容易对数据进行排序操作在联合索引中也可以利用部分索引键进行查询。这些情况下我们都没法使用Hash索引因为Hash索引仅能满足=&lt;&gt;和IN查询不能使用范围查询。此外Hash索引还有一个缺陷数据的存储是没有顺序的在ORDER BY的情况下使用Hash索引还需要对数据重新排序。而对于联合索引的情况Hash值是将联合索引键合并后一起来计算的无法对单独的一个键或者几个索引键进行查询。
MySQL默认使用B+树作为索引因为B+树有着Hash索引没有的优点那么为什么还需要自适应Hash索引呢这是因为Hash索引在进行数据检索的时候效率非常高通常只需要O(1)的复杂度也就是一次就可以完成数据的检索。虽然Hash索引的使用场景有很多限制但是优点也很明显所以MySQL提供了一个自适应Hash索引的功能Adaptive Hash Index。注意这里的自适应指的是不需要人工来制定系统会根据情况自动完成。
什么情况下才会使用自适应Hash索引呢如果某个数据经常被访问当满足一定条件的时候就会将这个数据页的地址存放到Hash表中。这样下次查询的时候就可以直接找到这个页面的所在位置。
需要说明的是自适应Hash索引只保存热数据经常被使用到的数据并非全表数据。因此数据量并不会很大因此自适应Hash也是存放到缓冲池中这样也进一步提升了查找效率。
InnoDB中的自适应Hash相当于“索引的索引”采用Hash索引存储的是B+树索引中的页面的地址。如下图所示:
<img src="https://static001.geekbang.org/resource/image/69/e0/692193e1df655561619cb464201ba3e0.jpg" alt=""><br>
你能看到采用自适应Hash索引目的是方便根据SQL的查询条件加速定位到叶子节点特别是当B+树比较深的时候通过自适应Hash索引可以明显提高数据的检索效率。
我们来看下自适应Hash索引的原理。
自适应Hash采用Hash函数映射到一个Hash表中如下图所示查找字典类型的数据非常方便。
Hash表是数组+链表的形式。通过Hash函数可以计算索引键值所对应的bucket的位置如果产生Hash冲突就需要遍历链表来解决。
<img src="https://static001.geekbang.org/resource/image/a6/ae/a6d510f8ca80feef8cb21b5fe55ef0ae.jpg" alt=""><br>
我们可以通过`innodb_adaptive_hash_index`变量来查看是否开启了自适应Hash比如
```
mysql&gt; show variables like '%adaptive_hash_index';
```
<img src="https://static001.geekbang.org/resource/image/70/99/70e9907a16ec51f03cb99295fafd0899.png" alt=""><br>
我来总结一下InnoDB本身不支持Hash索引但是提供自适应Hash索引不需要用户来操作存储引擎会自动完成。自适应Hash是InnoDB三大关键特性之一另外两个分别是插入缓冲和二次写。
### 什么是联合索引的最左原则?
关于联合索引的最左原则,读者@老毕 给出了一个非常形象的解释:
假设我们有x、y、z三个字段创建联合索引x, y, z之后我们可以把x、y、z分别类比成“百分位”、“十分位”和“个位”。
查询“x=9 AND y=8 AND z=7”的过程就是在一个由小到大排列的数值序列中寻找“987”可以很快找到。
查询“y=8 AND z=7”就用不上索引了因为可能存在187、287、387、487………这样就必须扫描所有数值。
我在这个基础上再补充说明一下。
查询“z=7 AND y=8 AND x=9”的时候如果三个字段x、y、z在条件查询的时候是乱序的但采用的是等值查询=或者是IN查询那么MySQL的优化器可以自动帮我们调整为可以使用联合索引的形式。
当我们查询“x=9 AND y&gt;8 AND z=7”的时候如果建立了(x,y,z)顺序的索引这时候z是用不上索引的。这是因为MySQL在匹配联合索引最左前缀的时候如果遇到了范围查询比如&lt;&gt;和between等就会停止匹配。索引列最多作用于一个范围列对于后面的Z来说就没法使用到索引了。
通过这个我们也可以知道联合索引的最左前缀匹配原则针对的是创建的联合索引中的顺序如果创建了联合索引x,y,z那么这个索引的使用顺序就很重要了。如果在条件语句中只有y和z那么就用不上联合索引。
此外SQL条件语句中的字段顺序并不重要因为在逻辑查询优化阶段会自动进行查询重写。
最后你需要记住,如果我们遇到了范围条件查询,比如(&lt;&lt;=&gt;&gt;=和between等那么范围列后的列就无法使用到索引了。
### Hash索引与B+树索引是在建索引的时候手动指定的吗?
如果使用的是MySQL的话我们需要了解MySQL的存储引擎都支持哪些索引结构如下图所示参考来源 [https://dev.mysql.com/doc/refman/8.0/en/create-index.html](https://dev.mysql.com/doc/refman/8.0/en/create-index.html)。如果是其他的DBMS可以参考相关的DBMS文档。
<img src="https://static001.geekbang.org/resource/image/f7/38/f7706327f9ebc7488653d69b4cd5f438.png" alt=""><br>
你能看到针对InnoDB和MyISAM存储引擎都会默认采用B+树索引无法使用Hash索引。InnoDB提供的自适应Hash是不需要手动指定的。如果是Memory/Heap和NDB存储引擎是可以进行选择Hash索引的。
## 关于缓冲池
### 缓冲池和查询缓存是一个东西吗?
首先我们需要了解在InnoDB存储引擎中缓冲池都包括了哪些。
在InnoDB存储引擎中有一部分数据会放到内存中缓冲池则占了这部分内存的大部分它用来存储各种数据的缓存如下图所示
<img src="https://static001.geekbang.org/resource/image/0e/dc/0eb57c0d0ea7611b16ac6efa76771bdc.jpg" alt=""><br>
从图中你能看到InnoDB缓冲池包括了数据页、索引页、插入缓冲、锁信息、自适应Hash和数据字典信息等。
我们之前讲过使用缓冲池技术的原因。这里重新回顾一下。InnoDB存储引擎基于磁盘文件存储访问物理硬盘和在内存中进行访问速度相差很大为了尽可能弥补这两者之间I/O效率的差值我们就需要把经常使用的数据加载到缓冲池中避免每次访问都进行磁盘I/O。
“频次*位置”这个原则可以帮我们对I/O访问效率进行优化。
首先,位置决定效率,提供缓冲池就是为了在内存中可以直接访问数据。
其次频次决定优先级顺序。因为缓冲池的大小是有限的比如磁盘有200G但是内存只有16G缓冲池大小只有1G就无法将所有数据都加载到缓冲池里这时就涉及到优先级顺序会优先对使用频次高的热数据进行加载。
了解了缓冲池的作用之后,我们还需要了解缓冲池的另一个特性:预读。
缓冲池的作用就是提升I/O效率而我们进行读取数据的时候存在一个“局部性原理”也就是说我们使用了一些数据大概率还会使用它周围的一些数据因此采用“预读”的机制提前加载可以减少未来可能的磁盘I/O操作。
那么什么是查询缓存呢?
查询缓存是提前把查询结果缓存起来这样下次不需要执行就可以直接拿到结果。需要说明的是在MySQL中的查询缓存不是缓存查询计划而是查询对应的结果。这就意味着查询匹配的鲁棒性大大降低只有相同的查询操作才会命中查询缓存。因此MySQL的查询缓存命中率不高在MySQL8.0版本中已经弃用了查询缓存功能。
查看是否使用了查询缓存,使用命令:
```
show variables like '%query_cache%';
```
<img src="https://static001.geekbang.org/resource/image/cb/c7/cb590bd0aac9751401943487534360c7.png" alt=""><br>
缓冲池并不等于查询缓存它们的共同点都是通过缓存的机制来提升效率。但缓冲池服务于数据库整体的I/O操作而查询缓存服务于SQL查询和查询结果集的因为命中条件苛刻而且只要数据表发生变化查询缓存就会失效因此命中率低。
## 其他
很多人对InnoDB和MyISAM的取舍存在疑问到底选择哪个比较好呢
我们需要先了解InnoDB和MyISAM各自的特点。InnoDB支持事务和行级锁是MySQL默认的存储引擎MyISAM只支持表级锁不支持事务更适合读取数据库的情况。
如果是小型的应用需要大量的SELECT查询可以考虑MyISAM如果是事务处理应用需要选择InnoDB。
这两种引擎各有特点当然你也可以在MySQL中针对不同的数据表可以选择不同的存储引擎。
最后给大家提供一下专栏中学习资料的下载。
如果你想导入文章中的“product_comment”表结构和数据点击[这里](https://github.com/cystanford/product_comment)即可。你也可以在[网盘](https://pan.baidu.com/s/1LBEAm50DDP9AjErLtGplLg)里下载提取码为32ep。
关于文章中涉及到的思维导图,点击[这里](https://github.com/cystanford/SQL-XMind)下载即可。
最后留一道思考题,供你消化今天答疑篇里的内容。
假设我们有x、y、z三个字段创建联合索引x, y, z。数据表中的数据量比较大那么对下面语句进行SQL查询的时候哪个会使用联合索引如果使用了联合索引分别使用到了联合索引的哪些部分
A
```
SELECT x, y, z FROM table WHERE y=2 AND x&gt;1 AND z=3
```
B
```
SELECT x, y, z FROM table WHERE y=2 AND x=1 AND z&gt;3
```
C
```
SELECT x, y, z FROM table WHERE y=2 AND x=1 AND z=3
```
D
```
SELECT x, y, z FROM table WHERE y&gt;2 AND x=1 AND z=3
```
欢迎你在评论区写下你的答案,我会和你一起交流,也欢迎把这篇文章分享给你的朋友或者同事,一起交流一下。