索引基础
在MySQL中,存储引擎首先在索引中找到对应值,然后根据匹配的索引记录找到对应的数据行。
优点
- 大大减少了服务器需要扫描的数据行数。
- 帮助服务器避免进行排序和分组,以及避免创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,不需要排序和分组,也就不需要创建临时表)。
- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起)。
B+ Tree原理
1. 数据结构
B Tree 指的是 Balance Tree,也就是平衡树。平衡树是一颗查找树,并且所有叶子节点位于同一层。
B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。
在 B+ Tree 中,一个节点中的 key 从左到右非递减排列,如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1。
2. 操作
进行查找操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。
插入删除操作会破坏平衡树的平衡性,因此在插入删除操作之后,需要对树进行一个分裂、合并、旋转等操作来维护平衡性。
3. 与红黑树的比较
红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 作为索引结构,主要有以下两个原因:
(一)更少的查找次数
平衡树查找操作的时间复杂度和树高 h 相关,O(h)=O(logdN),其中 d 为每个节点的出度。
红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,查找的次数也就更多。
(二)利用磁盘预读特性
为了减少磁盘 I/O 操作,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的磁盘旋转时间,速度会非常快。
操作系统一般将内存和磁盘分割成固定大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点。并且可以利用预读特性,相邻的节点也能够被预先载入。
MySQL索引
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
1. B+ Tree索引
是大多数 MySQL 存储引擎的默认索引类型。
MyISAM使用前缀压缩技术使得索引更小,InnoDB按原数据格式进行存储。MyISAM通过数据的(行号)物理位置引用被索引的行,InnoDB根据主键引用被索引的行。
不再需要进行全表扫描,从索引(树)的根节点开始进行搜索,所以查找速度快很多。
根节点的槽中存放了指向子结点的指针,这些指针实际上定义了子节点页中的上限和下限,由于节点直接是按照顺序排列的,在分组查询和范围查询中,可以直接锁定范围,所以根据范围查找的速度也会很快。
可以指定多个列作为索引列,多个索引列共同组成键。注意当一个索引包含了多个列值时,索引对多个值进行排序的依据是 CREATE TABLE 语句中定义索引时列的顺序。
适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。
查询类型
- 全值匹配
指的是和索引中的所有列进行匹配。
- 匹配最左前缀
当多个列组成索引列时,只使用索引的第一列。
- 匹配列前缀
只匹配某一列的值的开头部分。
- 匹配范围值
可以查找特定范围的数据。
- 精确匹配某一列并范围匹配另一列
第一列全匹配,第二列范围匹配。
- 只访问索引的查询
查询只需要访问索引,而无须访问数据行。
限制
如果不是按照索引列的顺序进行查找,则无法使用索引。
不能跳过索引中的列。
如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找。
索引的限制都和索引列的顺序有关,在性能优化时,可以使用相同的列但顺序不同的索引来满足不同类型的查询需求。
主索引与辅助索引
InnoDB 的 B+Tree 索引分为主索引和辅助索引,主索引是按主键值顺序索引,辅助索引是按列值的随机排列索引。
主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
因为索引中存储了实际的列值,所以某些查询只需要索引就能完成全部查询。
辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。
如果通过辅助索引查找行,需要先找到对应的主键值,然后根据这个值去聚簇索引中查找到对应的行,自适应哈希索引能够减少这个的工作。
2. 哈希索引
哈希索引基于哈希表实现,能以 O(1) 时间进行查找,但是失去了有序性:
- 无法用于排序与分组;
- 只支持精确查找,无法用于部分查找和范围查找。
对于每一行数据,存储引擎都会对所有的索引列计算出一个哈希码,不同键值的行的哈希码不一样。
哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。如果多个列的哈希值相同,索引会以链表的方式存放多个记录指针到同一个哈希条目中。
哈希索引自身只需要存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希查找的速度可以达到常数时间。
数据结构
哈希表里有两个列,一个是槽,放置经过哈希处理的槽值。一个是槽值对应的数据行所在位置。
槽的编号是顺序的,而数据行不是,这也就意味着在哈希索引中是无法实现排序与分组的,只支持精确查找,无法用于部分查找和范围查找。
查找过程
MySQL先计算出之前使用了哈希存储的条件的哈希值,并使用该值寻找对应的记录指针,因为可能不同的值经过哈希处理后可能会出现相同的哈希值,所以查找到数据行后还要比较该行的条件是否符合。
访问哈希索引的数据非常快,除非有很多哈希冲突。当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行。
存在的限制
只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。
哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。
哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。
哈希索引值支持等值比较查询,也不支持范围查询。
使用
InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上自动再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
如果存储引擎不支持哈希索引,可以创建自定义哈希索引:在B+ Tree基础上创建一个伪哈希索引,使用哈希值而不是剑指本身进行索引查找,然后在查询的WHERE子句中手动指定使用哈希函数。
实例
需要存储大量URL,并需要根据URL进行搜索查找。
可以新增一个crc索引列,使用CRC32做哈希,MySQL优化器会使用这个选择性很高而体积很小的基于crc列的索引来完成查找。
若多个记录有相同的索引值,根据哈希值做快速的整数比较就能找到索引条目,然后一一比较返回对应的行。
可以使用触发器在插入和更新时维护crc列。要避免哈希冲突,必须在WHERE中代入哈希值和队列列值。
3. 全文索引
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。
查找条件使用 MATCH AGAINST,而不是普通的 WHERE。
全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。
InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。
4. 空间数据索引
MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。
必须使用 GIS 相关的函数来维护数据。
索引优化
1. 独立的列
在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。
例如下面的查询不能使用 actor_id 列的索引:
SELECT actor_idFROM sakila.actorWHERE actor_id + 1 = 5;123
一个良好的使用WHERE条件是始终将索引列单独放在比较符号的一侧。
2. 多列索引
MySQL 5.0 和更新版本引入了“索引合并”的策略,查询能跟同时使用多个单列索引来进行扫描,并将结果合并。
有三种合并算法,OR条件的联合,AND条件的相交,以及前两个的结合。
MySQL会使用一些技术来优化辅助查询,索引合并策略是一种优化的结果。
优化器不会把联合操作中所使用的缓存、排序、合并的成本计算到“查询成本”上,优化器只看随机页面读取的成本。
3. 索引列的顺序
让选择性最强的索引列放在前面,但更重要的是避免随机I/O和排序。
例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。
SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,COUNT(*)FROM payment;1234Copy to clipboardErrorCopiedstaff_id_selectivity: 0.0001customer_id_selectivity: 0.0373COUNT(*): 160491234
4. 前缀索引
前缀索引是一种能使索引更小、更快的有效办法,缺点是MySQL无法使用前缀索引做分组和排序操作,也无法使用前缀索引做覆盖扫描。
当索引很长的时候,可以使用模拟哈希索引,或者索引开始的部分字符,从而节约索引空间,提高索引效率。前缀应该足够长,使得前缀索引的选择性接近于索引的整个列。
索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,每个记录的区分度越高,查询效率也越高。
为了决定前缀的合适长度,需要找到最常见的值的列表,然后和最常见的前缀列表进行比较。一个办法是,计算完整列的选择性,并使前缀的选择性接近于完整列的选择性。
不仅要看评价选择性,同时也要注意最坏情况下的选择性。常见使用场景是针对很长的十六进制唯一ID使用前缀索引。
5. 覆盖索引
索引包含所有需要查询的字段的值。
优点:
- 索引通常远小于数据行的大小,只读取索引能大大减少数据访问量。
- 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。
能在索引中做最左前缀匹配的LIKE比较,如果是通配符开头的LIKE查询则无法比较。
聚簇索引
聚簇索引是一种数据存储方式。
InnoDB的聚簇索引仔同一个结构保存了索引和数据行,其中数据行存放仔索引的叶子页中。叶子页包含了行的全部数据,但是叶节点页只包含了索引列。
InnoDB通过主键聚集数据,如果没有主键,InnoDB会选择一个唯一的非空索引代替,如果没有,会隐式定义一个主键来作为聚簇索引。
优点
- 把相关数据保存在一起,减少I/O读取次数。
- 数据访问更快。
- 可以直接使用页节点中的主键值。
缺点
- 插入速度严重依赖于插入顺序,可以使用OPTIMIZE TABLE命令重新组织表。
- 因为会强制InnoDB将每个被更新的行移动到新的位置,所以更新聚簇索引列的代价很高。
- 可能会发生“页分裂”现象。当的主键值要求必须将这一行插入到某个已满的页中时,会将该页分裂成两个页面来容纳该行。页分裂会导致表占用更多的磁盘空间。
- 在行比较稀疏或数据存储不连续时,可能导致全表扫描变慢。
索引的优点
- 大大减少了服务器需要扫描的数据行数。
- 帮助服务器避免进行排序和分组,以及避免创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,不需要排序和分组,也就不需要创建临时表)。
- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起)。
索引的使用条件
- 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效;
- 对于中到大型的表,索引就非常有效;
,可能导致全表扫描变慢。
索引的优点
- 大大减少了服务器需要扫描的数据行数。
- 帮助服务器避免进行排序和分组,以及避免创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,不需要排序和分组,也就不需要创建临时表)。
- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起)。
索引的使用条件
- 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效;
- 对于中到大型的表,索引就非常有效;
- 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。