MySQL优化二:如何创建高性能索引之索引基础

索引是存储引擎用于快速找到记录的一种数据结构。这是索引的基本功能。

索引对于良好的性能非常关键。尤其是当表中的数据量越来越大时,索引对性能的影响越发重要。在数据量较小且负载较低时,不恰当的索引对性能的影响可能还不明显,但当数据量主键增大时,性能则会急剧下降。

索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高几个数量级。最优的索引有时比一个好的索引要高两个数量级。创建一个真正最优的索引经常需要重写查询。

要理解MySQL中索引是如何工作的,最简单的方法就是看一本书的目录;如果想在一本书中找到某个特定主体,一般会看书的目录找到对应的页面。

在MySQL中,存储引擎用类似的方法使用索引,其先在索引中找到对应值,然后根据匹配的索引记录找到对应的数据行。

索引可以包含一个或多个列的值。如果索引包含多个列,那么列的顺序也很重要,因为MySQL只能高效的使用索引的最左前缀列。创建一个包含两个列的索引和创建两个包含一个列的索引不是完全一样的。

注意:使用ORM一样需要理解索引。

ORM工具能够生产负荷逻辑的、合法的查询,除非只是生成非常基本的查询,否则它很难生成适合索引的查询。

索引的类型

索引有很多类型,可以为不同的场景提供更好的性能。在MySQL中,索引是在存储引擎层而不是服务层实现。所以,并没有统一的索引表中;不同存储引擎的索引工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引。即使多个存储引擎支持同一种类型的索引,其底层的实现也可能不同。

B-Tree

当我们讨论索引时,一般说的是B-Tree索引,它使用B-Tree数据结构来存储数据。大多数MySQL引擎都支持这种索引。Archive引擎是一个礼物;5.1之前不支持任何索引,5.1以后才支持单个自增列的索引。

不过,底层的存储引擎也可能使用不同的存储结构,例如NDB集群存储引擎内部实际使用了T-Tree结构存储这种索引,即使其名字是BTREE;InnoDB则是使用了B+Tree。

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

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

B-Tree索引能够加快访问数据的速度,因为存储引擎不在需要进行全表扫描来获取需要的数据,取而呆滞的是从索引的根节点开始进行搜索。跟节点的槽中存放了指向字节点的指针,存储引擎根据这些指针想下层查找。通过比较节点也的值的上限和下限来判断。最终存储引擎找到值,或记录不存在。

叶子节点比较特别,他们的指针指向的是被索引的数据,而不是其他的节点也(不同引擎的指针类型不同。)上图中仅绘制了一个节点和其对应的叶子节点,其实在根节点和叶子检点之间可能有很多层节点页。树的深度和表的大小直接相关。

B-Tree对索引列的顺序组织存储的,所以很适合查找范围数据。例如,在一个机遇文本域的索引书上,按字母顺序传递连续的值进行查找是非常合适的,所以像找出所有已I到K开头的名字,这样的查找效率会非常高。

注意,索引对多个值进行排序的依据是create table语句中定义索引时列的顺序。

可以使用B-Tree索引的查询类型。B-Tree索引使用与全键值、键值范围或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找。前面所述的索引对一下类型的查询有效。

① 全值匹配

全职匹配指的是和索引中的所有列进行匹配,例如姓名为 abc 出生日期为2019-01-10.

② 匹配最左前缀

例如在查找所有姓a的人。

③ 匹配列前缀

也可以只匹配某一列的值的开头部分。例如前面提到的索引可用于查找所有以a开头的的名字。这里只使用了索引的第一列。

④ 匹配范围值

例如前面提到的索引可用于查找姓a到b之间的人。这里也只是用了索引的第一列。

⑤ 精确匹配某一列并范围匹配另外一列

⑥ 只访问索引的查询

B-Tree通常可以支持只访问索引的查询,即查询只需要访问索引,而无需访问数据行。

因为索引树中的节点是有序的,所以除了按值查找之外,索引还可以用于查询中的order by操作。一般来说,如果B-Tree可以按照某种方式查找到值,那么也可以按照这种方式用于排序。所以,如果order by子句满足前面列出的集中查询类型,那么这个索引页可以满足对应的排序需求。

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

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

② 不能跳过索引中的列。例:索引包含a b c三个列查询时不能只使用a c两列查询

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

所以索引中列的顺序很重要,在优化性能的时候,可能需要使用相同的列但顺序不同的索引来满足不同类型的查询需求。

也有写限制并不是B-Tree本身导致的,而是MySQL优化器和存储引擎使用索引的方式导致的。

哈希索引

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

在MySQL中,只有Memory引擎显式支持哈希索引。这也是Memory引擎表的默认索引类型,Memory引擎同时也支持B-Tree索引。值的一提的是,Memory引擎是支持非唯一哈希索引的,如果多个列的哈希值相同,索引会以链表的方式存放到多个记录指针到同一个哈希条目录中。

MySQL会先计算所查找值的哈希值,然后在索引中查找,找到指向行的指针后在去比较两个值是否相同。

因为索引自身之需存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找的速度非常快。然而哈希索引也有它的限制。

① 哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过访问内存中的行的速度很快,所以大部分情况下不会影响性能。

② 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。

③ 哈希索引不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。

④ 哈希索引只支持等值比较查询,包括 =、in、<=>。也不支持任何范围查询。

⑤ 如果哈希冲突很多的话,一些索引维护操作的代价也会很高。例如,如果在某个哈希冲突很多的列上建立索引,那么当从表中删除一行数据时,存储引擎需要遍历对应哈希值的链表中的每一行,找到并删除对应的索引,冲突越多,代价越大。

因为这些限制,哈希索引只使用与某些特定的场合。而一旦适合哈希索引,则它带来的性能提醒将非常显著。

处理Memory引擎外,NDB集群引擎也支持唯一哈希索引,且在NDB集群引擎中作用非常特殊,大家可以自行了解下。

InnoDB引擎有一个特殊的功能叫做自适应哈希索引。当InnoDB注意到某些索引值被使用的非常频繁时,它会在内存中基于B-Tree索引之上在创建一个哈希索引,这样就让B-TRee索引也具有哈希索引的一些优点,比如快速的哈希查找。这是一个完全自动的、内部的行为,用户无法控制或配置,不过可以关闭。

创建自定义哈希索引。如果存储引擎不支持哈希索引,则可以模拟像InnoDB一样创建哈希索引,这可以享受一些哈希索引的遍历,例如只需要很小的索引就可以为超长的键创建索引。

思路很简单,在B-Tree基础上创建一个伪哈希索引。还是使用B-Tree进行查找。但是它使用哈希值而不是键本身进行索引查找。你需要做的就是在查询的WHERE查询中手动指定使用哈希函数。

也可以使用触发器实现,例:

先创建表,然后创建触发器。先临时修改一下语句分隔符,这样可以在触发器定义中使用分号

然后让我们测试一下

如果使用这种方式,记住不要使用SHA1()和MD5()作为哈希函数。因为这两个函数计算出来的哈希值是非常长的字符串,会浪费大量空间,比较时也会更慢。SHA1()和MD5()是强加密函数,设计目标是最大限度消除冲突,但这里不需要这样高的要求。简单哈希函数的冲突在一个可以接受的范围,同时又能提供更好的性能。

如果数据表非常大CRC32()会出现大量的哈希冲突,则可以考虑自己实现一个简单的64位哈希函数。这个自定义函数要返回整数,而不是字符串。一个简单的办法就可以使用MD5()函数的返回值的一部分来作为自定义哈希函数。这比自己写一个哈希算法的性能可能会差一点,但实现简单。

select conv(right(md5('https://blog.csdn.net/yongqi_wang'),16),16,10) as hash64;

处理哈希冲突,当使用哈希索引进行查询的时候,必须在where自居中包含常量值

select id from hash_table where url_crc = crc32('https://blog.csdn.net/yongqi_wang') and url = 'https://blog.csdn.net/yongqi_wang';

否则一旦出现哈希冲突,另一个字符串的哈希值也相同那么查询时无法正确工作的。

要避免冲突问题,必须在where条件中带入哈希值和对应列值。如果不是想查询具体值,例如只是统计记录数,则可以不带入列值,直接使用crc32()的哈希值查询即可。还可以使用如fnv64()函数作为哈希函数,这是移植自Percona Server的函数,可以以插件的方式在任何MySQL版本中使用,哈希值为64位,速度快,且冲突比crc32()要少很多(crc32()函数中索引有93000条出现冲突的概率是百分之一)。

空间数据索引(R-Tree)

MyISAM表支持空间索引,可以用做地理数据存储。和B-Tree索引不同,这类索引无须前缀查询。空间索引会从所有纬度来索引数据。查询时,可以有效的使用任意纬度来组合查询。必须使用MySQL的GIS相关函数如MBRCONTAINS()等来维护数据。MySQL的GIS支持并不完善,所以大部分人都不会使用这个特性。开源关系数据库系统中对GIS的解决方案做的比较好的是PostgreSQL的PostGIS。

全文索引

全文索引是一种特殊类型的索引,它查找的是文本中的关键词,而不是直接比较索引中的值。全文搜索和其他积累索引的匹配方式完全不一样。它有许多需要注意的细节,如停用词、词干和负数、布尔搜索等。全文索引更乐死与搜索引擎做的事情而不是简单的where条件匹配。

在相同的列上同时创建全文索引和基于值的B-Tree索引不会有冲突,全文索引使用与match against操作,而不是普通的where、条件操作。

创建全文索引
alter table test_user add fulltext index idx_user_name(user_name);
使用全文索引
select * from test_user where match(user_name) against('abc' in boolean mode);

其他索引类别

还有很多第三方的存储引擎使用不同类型的数据结构来存储索引。例如TokuDB使用分形树索引,这是一类新开发的数据结构,即有B-Tree的很多优点,也避免了B-Tree的一些缺点。

ScaleDB使用Patricia tries,其他一些存储引擎技术如InfiniDB和Infobright则使用了一些特殊的数据结构来优化某些特殊的查询。

猜你喜欢

转载自blog.csdn.net/yongqi_wang/article/details/86221994