5、创建高性能的索引

索引是存储引擎用于快速找到记录的一种数据结构。
索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高几个数量级,"最优"的索引有时比一个“好的”索引性能要好两个数量级。

索引基础

要理解Mysql中索引是如何工作的,最简单的方法就是去看看一本书的“索引”部分。在Mysql中存储引擎用类似的方法使用索引,其先在索引中找到对应的值,然后根据匹配的索引记录找到对应的数据行。

索引的类型

B-Tree索引

大多数 Mysql 存储引擎都支持这种索引。存储引擎以不同的方式使用 B-Tree 索引,性能也各有不同,各有优劣。例如 MyISAM 使用前缀压缩技术使得索引更小,但 InnoDB 则按照原数据格式进行存储。再如 MyISAM 索引通过数据的物理位置引用被索引的行,而 InnoDB 则根据主键引用被索引的行。
B-Tree 通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同。B-Tree 索引能够加快访问数据的速度,因为索引引擎不再需要进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点,这些指针实际上定义了子节点页中值的上限和下限。叶子节点比较特别,他们的指针指向是被索引的数据,而不是其他的节点页。
B-Tree 对索引列使顺序存储的,所以很适合范围查找。B-Tree 索引适用于全键值、键值范围或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找。索引对以下几种类型的查询有效:

  - 全值匹配
  - 匹配最左前缀
  - 匹配列前缀
  - 匹配范围值
  - 精确匹配某一列并范围匹配另外一列
  - 只访问索引的查询

因为索引树中的节点是有序的,所以除了按值查找之外,索引还可以用于查询中的 ORDER BY 操作。一般来说,如果 B-Tree 可以按照某种方式查找到值,那么也可以按照这种方式用于排序。
当然关于 B-Tree 也有一些限制
对于组合索引如果不是按照索引的最左列开始查找,则无法使用索引。
不能跳过索引中的列。
如果查询中有某个列的范围查询,则其右边的所有列都无法使用索引优化查找。

哈希索引

哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。
在 Mysql 中,只有 Memory 引擎显示支持哈希索引。
因为索引自身只需要存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找的速度非常快。然而,哈希索引也有一些限制:

  - 哈希索引只包含哈希值和行指针。而不存储字段值,所以不能使用索引中的值来避免读取行。
  - 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。
  - 哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值。
  - 哈希索引只支持等值比较查询。
  - 访问哈希索引的数据非常快,除非有很多哈希冲突。当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行。
  - 如果哈希冲突的话,一些索引维护操作的代价也会很高。

空间数据索引(R-Tree)

MyISAM 表支持空间索引,可以用作地理数据存储。和 B-Tree 索引不同,这类索引无须前缀查。空间索引会从所有维度来索引数据。查询时,可以有效地使用任意维度来组合查询。必须使用 Mysql 的 GIS 相关函数如 MBRCONTAINS() 等来维护数据。Mysql 的 GIS 支持并不完善。所以正常不会使用这个特性。

全文索引

全文索引是一种特殊类型的索引,他查找的是文本中的关键词,而不是直接比较索引中的值。全文索引和其他几类索引的匹配方式完全不一样。
在相同的列上同时创建文本索引和基于值的 B-Tree 索引不会有冲突,全文索引适用于 MATCH AGAINST 操作,而不是普通的 WHERE 条件操作。

其他索引类别

聚簇索引、覆盖索引等

索引的优点

索引可以让服务器快速地定位到表的指定为止。但这并不是索引的唯一作用。最常见的 B-Tree 索引,按照顺序存储数据,索引 Mysql 可以用来做 ORDER BY 和 GROUP BY 操作。因为数据是有序的,所有 B-Tree 也就会将相关的列值都存储在一起。最后因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。
所以总结下来索引有以下几个优点:

  • 索引大大减少了服务器需要扫描的数据量
  • 索引可以帮助服务器避免排序和临时表
  • 索引可以将随机IO变为顺序IO

那么索引是最好的解决方案吗?
总的来说,只有当索引帮助存储引擎快速查找到记录带来的好处大于带来的额外工作,索引才是有效的。对于非常小的表,大部分情况下简单的全表扫描更高效。对于中大型的表,索引就非常有效。

高性能的索引策略

正确的创建和使用索引是实现高性能查询的基础。

独立的列

是指索引列不能是表达式的一部分,也不能是函数的参数。也就是说作为条件的列不能显示或者隐藏的参与计算。例如日期格式化,计算等等。

前缀索引和索引选择性

有时候需要索引很长的字符列,这会让索引变得大且慢。通常我们可以只索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但是这样也会降低索引的选择性。索引的选择性是指不重复的索引值和数据表的记录总数的比值。索引的选择性越高则查询效率越高,因为选择性高的索引可以让Mysql在查询时过滤掉更多的行。
一般情况下列前缀的选择性也是很好的。对于BLOB、TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,因为 Mysql 不允许索引这些列的完整长度。
前缀索引是一种能使索引更小,更快的有效方法,但另一方面也有其缺点:Mysql无法使用前缀索引做 ORDER BY 和 GROUP BY ,也无法使用前缀索引做覆盖扫描。

多列索引

在多个列上建立独立的单列索引大部分情况下并不能提高Mysql的查询性能。Mysql5.0以后引入了一种叫“索引合并”的策略,一定程度上可以使用表上的多个单列索引来定位指定的行。
索引合并策略有时候是一种优化的结果,但实际上更多时候说明表上的索引建的很糟糕:

  - 当出现服务器对多个索引做相交操作时,通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引。
  - 但服务器需要对多个索引做联合操作时,通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上。
  - 优化器不会把这些计算到查询成本中,优化器只关心随机页面读取。这会使得查询的成本被低估,导致执行计划还不如走全表扫描。

选择合适的索引列顺序

我们遇到的最容易引起困惑的问题就是索引列的顺序,正确的顺序依赖于使用该索引的查询,并且同时需要考虑如何更好地满足排序和分组的需要。
在一个多列 B-Tree 索引中,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列。所以索引可以按照升序或者降序进行扫描,以满足精确符合列顺序的ORDER BY 和 GROUP BY 和 DISTINCT 等子句的查询需求。
当不需要考虑排序和分组时,将选择性最高的列放在前面通常是最好的。

聚簇索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。具体细节依赖于实现方式,InnoDB的聚簇索引实际上再同一个结构中保存了 B-Tree 索引和数据行。
当表有聚簇索引时,他的数据行实际上存放在索引的叶子页中。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。
下面看看聚簇索引的优缺点:
优点:

  - 可以把相关数据保存在一起。
  - 数据访问快,聚簇索引将索引和数据保存在同一个 B-Tree 中,因此从聚簇索引中获取数据通常比在非聚簇索引中查找要快。
  - 使用覆盖索引扫描的查询可以直接使用页节点中的主键值。

缺点:

  - 聚簇索引最大限度地提高了IO密集型应用的性能,但如果数据全部都放在内存中,则访问的顺序就没那么重要了,聚簇索引也就没有优势了。
  - 插入速度严重依赖于插入顺序,按照主键的顺序插入是加载数据到InnoDB中速度最快的方式。
  - 更新聚簇索引的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置。
  - 基于聚簇索引的表在插入新行或者主键被更新导致需要移动行的时候,可能面临页分裂的问题。
  - 聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续。
  - 二级索引可能比想象的要更大,因为二级索引的叶子节点包含了引用行的主键列。
  - 二级索引访问需要两次索引查找,而不是一次。

InnoDB的二级索引和聚簇索引很不相同,InnoDB二级索引的叶子节点中存储的不是行指针而是主键值,并以此作为指向行的指针,这样的策略减少了当出现行移动或者数据页分裂时二级索引的维护工作。使用主键值当做指针会让二级索引占用更多的空间,换来的好处就是InnoDB在移动时无须更新二级索引中的这个指针。
当我们使用InnoDB表时主键的选择最简单的就是使用 AUTO_ICREMENT 自增列,这样可以保证数据行时按顺序写入。最好避免随机的聚簇索引,特别是对于IO密集型的应用。从性能角度考虑,使用UUID作为聚簇索引会使数据插入变得随机。
当我们使用UUID作为主键的时候,插入数据时因为新行的主键值不一定比之前插入的大,所以InnoDB 无法简单地总是把新行插入到索引的最后,而是需要为新的行寻找合适的位置,可能在已有数据的中间并且分配空间,这会增加很多额外的工作,下面是一些缺点:

  - 写入的目标页可能已经刷到磁盘上并从缓存中移除,或者是还没有被加载到缓存中,InnoDB就必须在插入之前先找到并从磁盘读取目标页到缓存中,这将导致大量的随机IO。
  - 因为写入是乱序的,InnoDB不得不频繁地做页分裂操作,以便为新的行分配空间。页分裂会导致移动大量数据,一次插入最少需要修改三个页而不是一个页。
  - 由于频繁的页分裂,页会变得稀疏并被不规则地填充,所以最终数据会有碎片。

覆盖索引

如果一个索引包含所有需要查询的字段的值,无须回表,那么就称为覆盖索引。覆盖索引极大地提高性能。下面看看覆盖索引带来了什么好处:
索引条目通常远小于数据行大小,所以如果只需要读取索引,那么Mysql就会极大地减少数据访问量。对于IO密集型的应用也有帮助,因为索引比数据更小,更容易全部放入内存中。
因为索引是按照列值顺序存储,所以对于IO密集型的范围查询会比随机从磁盘读取每一行数据的IO要少得多。
由于InnoDB的聚簇索引,覆盖索引对InnoDB表特别有用。InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询。

使用索引扫描来做顺序

Mysql 有两种方式可以生成有序的结果:通过排序操作或者按索引顺序扫描。如果 EXPLAIN 出来的type 列的值为 index,则说明Mysql使用了索引扫描来做排序。
扫描索引本身很快,因为只需要从一条索引记录移动到紧接着的下一条记录。但是如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就回表查询一次对应的行。这基本都是随机IO。因此按索引顺序读取数据的速度通常要比顺序全表扫描慢。
只有当索引的列顺序和 ORDER BY 子句的顺序完全一致,并且所有列的排序方向都一样时,Mysql才能够使用索引来对结果做排序。如果查询需要关联多张表,则只有当 ORDER BY 子句引用的字段全部为第一个表时,才能使用索引做排序。

压缩(前缀压缩)索引

MyISAM使用前缀压缩来减少索引的大小,从而让更多的索引可以放入内存,这在某些情况下提高了性能。默认只压缩字符串,可以通过参数设置对整数做压缩。
MyISAM压缩每个索引快的方法是,先完全保存索引块中的第一个值,然后将其他值和第一个值进行比较得到相同前缀的字节数和剩余的不同后缀部分,把这部分存储起来即可。
压缩快使用更少的空间,代价是某些操作可能更慢。因为每个值的压缩前缀都依赖前面的值,所以MyISAM查找时无法再索引块使用二分查找而只能从头开始扫描。

冗余和重复索引

Mysql允许在相同列上创建多个索引,无论是有意的还是无意的。Mysql需要单独维护重复的索引并且优化器在优化查询时也需要逐个地进行考虑。
重复索引是指在相同列上按照相同顺序创建的相同类型的索引。

未使用的索引

除了冗余索引和重复索引,可能会有一些永远不用的索引,这样的索引建议删除。

索引和锁

索引可以让查询锁定更少的行。
InnoDB只有在访问行的时候才会对其加锁,而索引能够减少访问的行数,从而减少锁的数量。

总结

  • 单行访问时很慢的。如果服务器从存储中读取一个数据块只是为了获取其中一行,那么就浪费了很多工作。最好读取的块中能包含尽可能多所需要的行。
  • 按顺序访问范围数据是很快的。第一,顺序IO不需要多次磁盘寻道,所以比随机IO要快很多;第二,如果服务器能够按需要顺序读取数据,那么就不再需要额外的排序操作,并且GROUP BY查询也无须再做排序和将行按组聚合计算了。
  • 索引覆盖查询是很快的,避免了回表查找。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值