读《高性能MySQL》第三版,笔记。
官方文档:https://dev.mysql.com/doc/refman/5.7/en/mysql-indexes.html
索引有很多种类型,可以为不同的场景提供更好的性能。
在 MySQL 中,索引是在存储引擎层而不是服务器层实现的。
所以,并没有统一的索引标准:不同存储引擎的索引的工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引。
即使多个存储引擎支持同一种类型的索引,其底层的实现也可能不同。
下面我们看看 MySQL 支持的索引类型,以及他们的优点和缺点。
B-Tree 索引
当人们谈论索引的时候,如果没有特别指明类型,那多半说的是 B-Tree 索引,它们使用 B-Tree 数据结构来存储数据。
实际上很多存储引擎使用的是 B+Tree ,即每一个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的范围遍历。
我们使用术语 “ B-Tree ”,是因为 MySQL 在 CREATE TABLE
和其他语句中使用该关键字。 不过,底层的存储引擎也可能使用不同的存储结构。
例如,NDB 集群存储引擎内部实际上使用了 T-Tree 结构存储这种索引,即使其名字是 BTREE;InnoDB 则使用的是 B+Tree。
存储引擎以不同的方式使用 B-Tree 索引,性能也各有不同,各有优劣。
例如:
MyISAM | 使用前缀压缩技术使得索引更小 | 索引通过数据的物理位置引用被索引的行 |
InnoDB | 按照原数据格式进行存储 | 则根据主键引用被索引的行 |
B-Tree 通常译为着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同。
B-Tree 索引能够加块访问数据的速度,因为存储引擎不再需要进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下查找。通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点,这些指针实际上定义了子节点页中值的上限和下限。最终存储引擎要么是找到对应的值,要么该记录不存在。
叶子节点比较特别,它们的指针指向的是被索引的数据,而不是其他的节点页(不同引擎的 “ 指针 ” 类型不同)。根节点和叶子节点之间可能又很多层节点页,这和树的深度与表的大小直接相关。
B-Tree 对索引列是顺序组织存储的,所以很适合查找范围数据。
可以使用 B-Tree 索引的查询类型。
B-Tree 索引适用于全键值、键值范围或键前缀查找。
其中键前缀查找只适用于根据最左前缀的查询。前面所述的索引对如下类型的查询有效。
- 全值匹配,和索引中的所有列进行匹配。
- 匹配最左前缀。
- 匹配列前缀,只匹配某一列的值的开头部分。
- 匹配范围值。
- 精确匹配某一列并范围匹配另外一列。
- 只访问索引的查询。
因为索引树中的节点是有序的,所以除了按值查找之外,索引还可以用于查询中的 ORDER BY 操作(按顺序查找)。
下面是一些关于 B-Tree 索引的限制:
- 如果不是按照索引的最左列开始查找,则无法使用索引。
- 不能跳过索引中的列。
- 如果查询中有某个列的范围查询,则其右边所有列都无法使用所有优化查找。
到这里读者应该可以明白,前面提到的索引列的顺序是多么的重要:这些限制都和索引列的顺序有关。
在优化性能的时候,可能需要使用相同的列但顺序不同的索引来满足不同类型的查询需求。
哈希索引
哈希索引(hash index)基于哈希表实现。
只有精度匹配索引所有列的查询才有效。
对于每个一行数据,存储引擎都会对所有的索引列计算一个哈希码(hash code)。
哈希码是一个较小的值,并且不同的键值的行计算出来的哈希码页不一样。
哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。
在 MySQL 中,只有 Memory 引擎显式支持哈希索引。这也是 Memory 引擎表的默认索引类型,Memory 引擎同时也支持 B-Tree 索引。值得一提的是,Memory 引擎是支持非唯一哈希索引的,这在数据库世界里面是比较与众不同的。
如果多个列的哈希值相同,索引会以链表的方式存放多个记录指针到同一个哈希条目中。
因为索引自身只需存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找的速度非常快。
然后,哈希索引也有它的限制:
- 哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能的影响并不明显。
- 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。
- 哈希索引页不支持部分索引列匹配查找,因为哈希索引时钟是使用索引列的全部内容来计算哈希值的。
- 哈希索引只支持等值比较查询,不支持任何范围查询。
- 访问哈希索引的数据非常快,除非有很多哈希冲突。当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行。
- 如果哈希冲突很多的话,一些索引维护操作的代价也会很高。
因为这些限制,哈希索引只适用于某些特定的场合。而一旦适合哈希索引,则它带来的性能提升将非常显著。
InnoDB 引擎有一个特殊的功能叫做 “ 自适应哈希索引(adaptive hash index) ”。当 InnoDB 注意到某些索引值被使用得非常频繁时,它会在内存中基于 B-Tree 索引之上再创建一个哈希索引,这样就让 B-Tree 索引也具有哈希索引的一些优点,比如快速的哈希查找。这是一个完全自动的、内部的行为,用户无法控制或者配置,不过如果有必要完全可以关闭该功能。
创建自定义哈希索引。 如果存储引擎不支持哈希索引,则可以模拟像 InnoDB 一样创建哈希索引,这可以享受一些哈希索引的便利,例如值需要很小的索引就可以为超长的键创建索引。
思路很简单:在 B-Tree 基础之上创建一个伪哈希索引。这和真正的哈希索引不是一回事,因为还是使用 B-Tree 进行查找,但是它使用哈希值而不是键本身进行索引查找。你需要做的就是在查询的 WHERE 子句中手动指定使用哈希函数。
空间数据索引(R-Tree)
MyISAM 表支持空间索引,可以用作地理数据存储。和 B-Tree 索引不同,这类索引无须前缀查询。空间索引会从所有维度来所有数据。查询时,可以有效地使用任意维度来组合查询。必须使用 MySQL 的 GIS 相关函数如 MBRCONTAINS() 等来维护数据。MySQL 的 GIS 支持并不完善,所以大部分人都不会使用这个特性。开源关系数据库系统中对 GIS 的解决方案做得比较好的是 PostgreSQL 的 PostGIS。
全文索引
全文索引是一种特殊类型的索引,它查找的是文本中的关键词,而不是直接比较索引中的值。全文搜索和其他几类索引的匹配方式完全不一样。它有很多需要注意的细节,如停用词、词干和复数、布尔搜索等。全文索引更类似于搜索引擎做到的事情,而不是简单的 WHERE 条件匹配。
在相同的列上同时创建全文索引和基于值得 B-Tree 索引不会有冲突,全文索引适用于 MATCH AGAINST 操作,而不是普通的 WHERE 条件操作。
其他索引类型
还有很多第三方的存储引擎使用不同类型的数据结构来存储索引。
例如: ToKuDB 使用分形树索引(fractal tree index),这是一类较新开发的数据结构,既有 B-Tree 的很多优点,也避免了 B-Tree 的一些缺点。
索引的优点
- 索引大大减少了服务器需要扫描的数据量。
- 索引可以帮助服务器避免排序和临时表。
- 索引可以将随机 I/O 变为顺序 I/O。