《高性能MySQL》读书笔记之创建高性能的索引

索引是存储引擎用于快速找到记录的一种数据结构。索引优化是对查询性能优化的最有效手段。索引能够轻易将查询性能提高几个数量级。创建一个最优的索引经常需要重写查询。

5.1 索引基础

在MySQL中,存储引擎首先在索引中找到对应值,然后根据匹配的索引记录找到对应的数据行。
索引可以包含一个或多个列的值。如果索引包含多个列,那么列的顺序也十分重要,因为MySQL只能高效地使用索引的最左前缀列。

5.1.1 索引的类型

索引有很多类型,可以为不同的场景提供更好的性能。在MySQL中,索引是在储存引擎层而不是服务器层实现的。

B-Tree索引:B-Tree通常意味着所有的值都是按顺序储存的,并且每一个叶子页到根的距离相同。

储存引擎以不同的方式使用B-Tree索引,性能也各有不同,各有优劣。MyISAM使用前缀压缩技术使得索引更小,但InnoDB则按照原数据格式进行储存。MyISAM索引通过数据的物理位置引用被索引的行,而InnoDB则根据主键引用被索引的行。

B-Tree对索引是顺序组织存储的,所以很适合查找范围数据。

B-Tree索引适用于全键值、键值范围或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找。

可以使用B-Tree索引的查询类型:

全值匹配:指的是和索引中所有的列进行匹配。

匹配最左前缀:只使用索引的第一列。

匹配列前缀:也可以匹配某一列的开头部分。

匹配范围值:也只使用了索引的第一列。

精确匹配某一列并范围匹配另外一列:即第一列全匹配,第二列范围匹配。

只访问索引的查询:即查询只需要访问索引,而无须访问数据行。

因为索引树中的节点是有序的,所以除了按值查找之外,还可以用于查询中的ORDER BY操作。

下面是一些关于B-Tree索引的限制:

如果不是按照索引的最左列进行查找,则无法使用索引。

不能跳过索引中的列。

如果查询中有某个列的范围查找,则其右边所有列都无法使用索引优化查找。

所以索引列的顺序非常重要,在优化性能的时候,可能需要使用相同的列但顺序不同的

索引来满足不同类型的查询需求。

哈希索引(hash index): 基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码(hash code),哈希码是一个较小的值,并且不同键值的行计算出的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。

在MySQL中,只有Memory引擎显式支持哈希索引。如果多个列的哈希值相同,索引会以链表的方式存放多个记录指针到同一个哈希条目中。

因为哈希索引只需要存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找的速度非常快。

哈希索引的限制:
哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。

哈希索引无法用于排序。
哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容计算哈希值的。例如在数据列 (A,B)上建立哈希索引,如果查询只有数据列A,则无法使用该索引。

哈希索引只支持等值比较查询,包括 =, IN(), <=>(注意<>和<=>是不同的操作)。也不支持任何范围查询。

访问哈希索引的数据非常快,除非有很多哈希冲突(不同的索引列却有相同的哈希值)。
如果哈希冲突很多的话,一些索引维护操作的代价也会很高。
因为这些限制,哈希索引只适用于某些特定的场合。而一旦适合哈希索引,则它带来的性能提升将非常显著。

创建自定义哈希索引:如果存储引擎不支持哈希索引,则可以创建自定义的哈希索引。
思路很简单:在B-Tree上创建一个伪哈希索引。这和真正的哈希索引不是一回事,因为还是使用B-Tree进行查找,但是它使用哈希值而不是键本身进行索引查找。需要做的就是在查询的WHERE子句中手动指定使用哈希函数,可以使用CRC32做哈希,不要使用SHA1()和MD5()作为哈希函数(因为这两个函数计算出来的哈希值是非常长的字符串,会浪费大量空间,比较时也会比较慢)。

如果数据表非常大,CRC32()会出现大量的哈希冲突(当索引有93000条记录时出现冲突的概率是1%),则可以考虑自己实现一个简单的64位哈希函数,对这个函数的要求是返回整数。一个简单的办法是可以使用MD5()函数返回值的一部分作为自定义哈希函数,还可以使用如FNV64()函数作为哈希函数,这是移植自Percona Server 的函数,可以以插件的方式在任何MySQL版本中使用,生成的哈希值是64位,速度快,且冲突比CRC32()要少得多。

处理哈希冲突:当使用哈希索引进行查询时,必须在WHERE子句中包含常量值,例如:
mysql>SELECt id FROM url WHERE url_ctc = CRC32(“http://www.mysql.com”) AND url=”http://www.mysql.com”;

 

5.2 索引的优点

最常见的B-Tree索引,按照顺序存储数据,所以MySQL可以用来做ORDER BY和GROUP BY操作。因为数据是有序的,所以B-Tree也就会将相关的列值都存储在一起。最后,因为索引中存储了实际列的值,所以某些查询只使用索引就能够完成全部查询。

三大优点:
1. 索引大大减少了服务器需要扫描的数据量
2. 索引可以帮助服务器避免排序和临时表
3. 索引可以将随机I/O变为顺序I/O
总的来说,只有当索引帮助存储引擎快速地找到记录带来的好处大于其带来的额外工作时,索引才是有效的。对于非常小的表,大部分情况下简单的全表扫描更高效。对于中到大型的表,索引就非常有效。但对于特大型的表,建立和使用索引的代价将随之增长。

5.3 高性能的索引策略
5.3.1 独立的列
如果查询中的列不是独立的,则MySQL就不会使用索引。“独立的列”是指索引的列不能是表达式的一部分,也不是函数的参数。
mysql>SELECT actor_id FROM sakila.actor WHERE actor_id + 1 =5;
mysql>SELECt … WHERE TO_DAYS(CURRENT_DATE) – TO_DAY(date_col) <= 10;

5.3.2 前缀索引和索引选择性

有时候需要索引很长的字符列,这会让索引变得大且慢。一个策略是前面提到过的模拟哈希索引。但有时候这样做还不够,通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率,但这样会降低索引的选择性。索引的选择性是指,不重复的索引值(也称为基数,cardinality)和数据表的记录总数(#T)的比值,范围从1/#T到1之间。索引的选择性越高则查询效率越高。唯一索引的选择性是1,这是最好的索引选择性,性能是最好的。

5.3.3 多列索引
一个常见错误就是,为每个列创建独立索引,或者按照错误的顺序创建多列索引。
在多个列上创建索引大部分情况下并不能提高MySQL的查询性能。
例如,表film_actor在字段film_id和actor_id上各有一个单列索引。但对于下面的查询,这两个单列索引都不是好的选择:
mysql>SELECT film_id,actor_id FROM sakila.film_actor
->WHERE actor_id = 1 OR film_id = 1;
在MySQL 5.0 和更新的版本中,查询能同时使用这两个单列索引进行扫描,并将结果进行合并。这种算法有三个变种:OR条件的联合(union),AND条件的相交(intersection),组合前两种情况的联合和相交。通过EXPLAIN中的Extra可以看到这一点:
mysql>EXPLAIN SELECT film_id,actor_id FROM sakila.film_actor
->WHERE actor_id = 1 OR film_id = 1\G;
********************1. row****************************


Extra: Using union(PRIMARY,idx_fk_film_id),Using where

索引合并策略有时候是一种优化的结果,但实际上更多时候说明表上的索引建得很糟糕。
当出现服务器对多个索引做相交操作时(通常有多个AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引。
当服务器需要多个索引做联合操作时(通常有多个OR操作),通常需要耗费大量的CPU和内存资源在算法的缓存、排序和合并操作上。

 

(未完待续)