高性能Mysql-创建高性能索引

索引(在MySQL中也叫做 “键(key)”)是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键。 尤其是当表中的数据量越来越大时, 索引对性能的影响愈发重要。 在数据量较小且负载较低时, 不恰当的索引对性能的影响可能还不明显, 但当数据量逐渐增大时, 性能则会急剧下降。索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高几个数量级, “最优” 的索引有时比一个 “好的” 索引性能要好两个数量级。

1、索引基础

在Mysql中,存储引擎先在索引上查找到对应的值,然后根据匹配的索引记录找到对应的数据行。例如:

SELECT first_name FROM sakila.actor WHERE actor_id = 5;

如果在actor_id列上建有索引, 则MySQL将使用该索引找到actor_id为5的行, 也就是说, MySQL先在索引上按值进行查找, 然后返回所有包含该值的数据行。

索引可以包含一个或多个列的值。 如果索引包含多个列, 那么列的顺序也十分重要, 因为MySQL只能高效地使用索引的最左前缀列。 创建一个包含两个列的索引, 和创建两 个只包含一列的索引是大不相同的, 下面将详细介绍。

1.1 索引的类型

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

B-Tree索引

当人们谈论索引的时候, 如果没有特别指明类型, 那多半说的是B-Tree索引, 它使用B-Tree数据结构来存储数据。大多MySQL引擎都支持这种索引。不过,底层的存储引擎也可能使用不同的存储结构,例如,NDB集群存储引擎内部实际上使用了T-Tree结构存储这种索引,即使其名字是BTREE。下图展示了B-Tree索引的抽象设计,大致反应了innodb是如何工作的。

B-Tree索引能够加快访问数据的速度,因为存储引擎不再需要进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。

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

哈希索引

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

在MySQL中, 只有Memory引擎显式支持哈希索引。这也是Memory引擎表的默认索 引类型,Memory引擎同时也支持B-Tree索引。

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

•    哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能的 影响井不明显。
•    哈希索引数据井不是按照索引值顺序存储的,所以也就无法用于排序。
•    哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。例如,在数据列(A,B)上建立哈希索引,如果查询只有数据列A, 则无法使用该索引。

•    哈希索引只支持等值比较查询,包括=、 IN() 、<=>(注意。和〈=〉是不同的操作)。 也不支持任何范围查询,例如WHERE price> 100.
•    访问哈希索引的数据非常快,除非有很多哈希冲突(不同的索引列值却有相同的哈希值)。 当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行。
•    如果哈希冲突很多的话,一些索引维护操作的代价也会很高。 例如,如果在某个选择性很低(哈希冲突很多) 的列上建立哈希索引,那么当从表中删除一行时, 存储引擎需要遍历对应哈希值的链表中的每一行,找到井删除对应行的引用,冲突越多, 代价越大。

空间数据索引

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

全文索引

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

2、索引的优点

①.    索引大大减少了服务器需要扫描的数据量。
②.    索引可以帮助服务器避免排序和临时表。
③.    索引可以将随机I/O变为顺序I/O。

3、高性能索引策略

3.1 独立的列

我们通常会看到一些查询不当的使用索引,或者使得mysql无法使用已有的索引。如果查询中的列不是独立的,则mysql就不会使用索引。“独立的列”是指索引不能是表达式的一部分,也不能是函数的参数。

例如:SELECT actor_id FRO问sakila.actor WHERE actor_id + 1 = s;这个无法使用索引。

3.2 前缀索引和索引选择性

有时候需要索引很长的字符列, 这会让索引变得大且慢。一个策略是前面提到过的模拟哈希索引。但有时候这样还不够,那我们还能做什么呢?

通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。 但这样也会降低索引的选择性。 索引的选择性是指,不重复的索引值和数据表的记录总数(#T)的比值,范围从 1/#T 到1之间。索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。

前缀索引是一种能使索引更小、更快的有效办法,但另一方面也有其缺点:Mysql无法使用前缀索引做order by和group by,也无法使用前缀索引做覆盖扫描。

3.3 多列索引

很多人对多列索引的理解都不够。 一个常见的错误就是, 为每个列创建独立的索引, 或者按照错误的顺序创建多列索引。

在多个列上建立独立的单列索引大部分情况下并不能提高MySQL的查询性能。 MySQL5.0和更新版本引入了一种叫 “索引合并” (index merge)的策略, 一定程度上可以使用表上的多个单列索引来定位指定的行。 更早版本的MySQL只能使用其中某一个单列索引, 然而这种情况下没有哪一个独立的单列索引是非常有效的。

索引合井策略有时候是一种优化的结果, 但实际上更多时候说明了表上的索引建得很糟糕:

•    当出现服务器对多个索引做相交操作时(通常有多个AND条件), 通常意味着需要一 个包含所有相关列的多列索引, 而不是多个独立的单列索引。
•    当服务器需要对多个索引做联合操作时(通常有多个OR条件), 通常需要艳费大量CPU和内存资源在算棒的缓存、 排序和合并操作上。 特别是当其中有些索引的选择性不高, 需要合井扫描返回的大量数据的时候。

•   更重要的是, 优化器不会把这些计算到 “查询成本” (cost)中,优化器只关心随机页面读取。这会使得查询的成本被 “低估”,导致该执行计划还不如直接走全表扫描。这样做不但会消耗更多的CPU和内存资源, 还可能会影响查询的井发性, 但如果是单独运行这样的查询则往往会忽略对井发性的影响。 通常来说, 还不如像在MySQL 4.1或者更早的时代一样, 将查询改写成 UNION 的方式往往更好。

3.4 选择合适的索引列顺序

在一个B-tree索引中,索引列的顺序意味着索引首先从左列进行排序,其次是第二列,等等。所以,索引可以按照升序或者降序进行扫描,以满足精确符合列顺序的ORDER BY、 GROUP BY和DISTINCT等子句的查询需求。

对于如何选择索引的列顺序有一个经验法则:将选择性最高的列放到索引最前列。

3.5 聚簇索引

聚簇索引并不是一种单独的索引类型, 而是一种数据存储方式。 具体的细节依赖于其实现方式, 但InnoDB 的聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。当表有聚簇索引时,它的数据行实际上存放在索引的叶子页(leaf page) 中。 术语 “聚簇” 表示数据行和相邻的键值紧凑地存储在一起。

聚簇主键可能对性能有帮助, 但也可能导致严重的性能问题。 所以需要仔细地考虑聚簇索引, 尤其是将表的存储引擎从InnoDB改成其他引擎的时候(反过来也一样)。
聚集的数据有一些重要的优点:

•    可以把相关数据保存在一起。 例如实现电子邮箱时, 可以根据用户ID 来聚集数据, 这样只需要从磁盘读取少数的数据页就能获取某个用户的全部邮件。 如果没有使用聚簇索引, 则每封邮件都可能导致一次磁盘I/O.
•    数据访问更快。 聚簇索引将索引和数据保存在同一个B-Tree中, 因此从聚簇索引中 获取数据通常比在非聚簇索引中查找要快。
•    使用覆盖索引扫描的查询可以直接使用页节点中的主键值。

如果在设计表和查询时能充分利用上面的优点, 那就能极大地提升性能。 同时, 聚簇索引也有一些缺点:

•    聚簇数据最大限度地提高了I/O密集型应用的性能, 但如果数据全部都放在内存中, 则访问的顺序就没那么重要了, 聚簇索引也就没什么优势了。
•    插入速度严重依赖于插入顺序。 按照主键的顺序插入是加载数据到InnoDB表中速度最快的方式。 
•    更新聚簇索引列的代价很高, 因为会强制InnoDB将每个被更新的行移动到新的位置。
•    基于聚簇索引的表在插入新行, 或者主键被更新导致需要移动行的时候, 可能面临 “页分裂(page split)” 的问题。 当行的主键值要求必须将这一行插入到某个已满的页中时, 存储引擎会将该页分裂成两个页面来容纳该行, 这就是一次页分裂操作。 页分裂会导致表占用更多的磁盘空间。
•    聚簇索引可能导致全表扫描变慢, 尤其是行比较稀疏, 或者由于页分裂导致数据存储不连续的时候。
•    二级索引(非聚簇索引) 可能比想象的要更大, 因为在二级索引的叶子节点包含了引用行的主键列。

3.6 覆盖索引

如果一个索引覆盖或包含所有需要查询的字段的值,我们就称之为覆盖索引。覆盖索引是非常有用的工具,能够极大地提高性能。

在选择索引和编写利用这些索引的查询时, 有如下三个原则始终需要记住:

1.    单行访问是很慢的。 特别是在机械硬盘存储中(SSD的随机I/O要快很多, 不过这一点仍然成立)。 如果服务器从存储中读取一个数据块只是为了获取其中一行, 那么就浪费了很多工作。 最好读取的块中能包含尽可能多所需要的行。 使用索引可以创建位置引用以提升效率。
2.    按顺序访问范围数据是很快的,这有两个原因。 第一,顺序I/O不需要多次磁盘寻道, 所以比随机I/O要快很多(特别是对机械硬盘)。 第二, 如果服务器能够按需要顺序 读取数据, 那么就不再需要额外的排序操作, 井且GROUP BY查询也无须再做排序和 将行按组进行聚合计算了。
3.    索引覆盖查询是很快的。 如果一个索引包含了查询需要的所有列, 那么存储引擎就不需要再回表查找行。 这避免了大量的单行坊问, 而上面的第1点已经写明单行访问是很慢的。

猜你喜欢

转载自blog.csdn.net/dongzl0230/article/details/83502172