MySQL索引详解及优化策略

1. 索引介绍

索引是存储引擎用于快速查找记录的一种数据结构。索引优化是对查询性能优化最有效的手段。
索引可以包含一个或多个列的值。如果索引包含多个列,那么列的顺序也十分重要,因为MySQL只能高效地使用索引的最左前缀列

1.1 索引优点

索引可以让服务器快速定位到表的指定位置:最常见的B-Tree索引,按照顺序存储数据,所以MySQL可以用来做ORDER BY 和 GROUP BY操作.
总结下来,索引有如下三个优点:

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

1.2 适合建立索引的字段

  1. 经常搜索
  2. 经常排序
  3. 经常跟在Where语句后面
  4. 数据类型小
  5. 简单数据类型
  6. 列中尽量避免null

2. 索引类型

2.1 B-Tree索引

使用B-Tree数据结构来存储数据,大多MySQL引擎都支持该索引。B-Tree索引可以加快访问数据的速度,因为B-Tree对索引列顺序组织存储,范围查找快.

  1. 主键和唯一性约束字段的B树索引,效率几乎和海量数据没有关系。
  2. 键值重复率低的字段比较适合使用B树索引。
  • 唯一索引
    索引列的所有值都只能出现一次,即必须唯一,值可以为空,一对一关系。
    普通索引(一对多关系)是允许数据重复的,如果确定了某列数据不会重复,则可创建唯一索引,唯一索引有两个好处,索引更有效:插入新数据,如果重复,MySQL拒绝插入。
  • 主键索引
    主键是一种唯一性索引,但它必须指定为PRIMARY KEY,每个表只能有一个主键
    主键本身默认创建索引

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

  1. 全值匹配:全值匹配指的是和索引中的所有列进行匹配。
  2. 匹配最左前缀:即只使用索引的第一列。
  3. 匹配列前缀:也可以只匹配某一列的值的开头部分。
  4. 匹配范围值
  5. 精确匹配某一列并范围匹配另外一列
  6. 只访问索引的查询:覆盖索引

2.1.2 索引顺序查询

因为索引树中的节点是有序的,所以除了按值查找之外,索引还可以用于查询中的ORDER BY操作(按顺序查找)。
一般来说,如果B-Tree可以按照某种方式查找到值,那么也可以按照这种方式用于排序。

2.1.3 B-Tree索引的限制

  1. 如果不是按索引的最左列开始查找,则无法使用索引。
  2. 不能跳过索引中的列。
  3. 如果查询中有某个列的范围查询,则最右边的所有列都无法使用索引优化查询。如果范围查询列值的数量有限,那么可以通过使用多个等于条件来代替范围查找。

2.2 Hash索引(Memory引擎支持)

哈希索引基本哈希表实现,只有精确匹配索引所有列的查询才有效。结构十分紧凑,查询速度非常快。
对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码值较小。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。MySQL中只有Memory引擎显示支持哈希索引。哈希表只能在内存实现,没法在磁盘实现.
索引列会被存储在匹配到的hash bucket里面的表里,这个表里会有实际的数据行指针,再根据实际的数据行指针查找对应的数据行。
hash索引适用于等值(=)查询
在这里插入图片描述

2.2.1 Hash索引流程

概括来说,要查找一行数据或者处理一个where子句:

  1. 根据where条件里面的参数生成合适的哈希函数
  2. 索引列进行匹配,匹配到对应hash bucket,找到对应hash bucket意味着也找到了对应的数据行指针(row pointer)
  3. 读取数据
    哈希索引比起B树索引简单,因为它不需要遍历B树,所以访问速度会更快
    在这里插入图片描述

2.2.2 Hash索引的缺点

  1. 因为Hash索引比较的是经过Hash计算的值,所以只能进行等式比较,不能用于范围查询
  2. 由于哈希值是按照顺序排列的,但是哈希值映射的真正数据在哈希表中就不一定按照顺序排列,所以无法利用Hash索引来加速任何排序操作
  3. 不能用部分索引键来搜索,因为组合索引在计算哈希值的时候是一起计算的。
  4. 当哈希值大量重复且数据量非常大时,其检索效率并没有B-tree索引高。

2.3 空间索引(R-Tree)

MyIsam表支持空间索引,Mysql本身对GIS的支持并不完善
开源关系数据库中对GIS的解决方案做的比较好的是PostgreSQL的PostGIS

2.4 全文索引(MyISAM引擎支持)

全文索引是一种特殊的索引,它查找的是文本中的关键词而不是直接比较索引中的值。
全文索引更加类似于搜索引擎做的事情,而不是简单的WHERE匹配。在同一列上同时创建全文索引和基于值的B-Tree索引不会有冲突。
全文索引主要用于词汇的快速搜索,比较适合对列中的字段进行模糊查询(如SQL语句中包含%)或者语言类的查询,其缺点是占用空间太大(会创建很多中间表)

应用场景
文本字段上的普通索引只能加快对出现在字段内最前面的字符串进行的检索操作,如果字段里存放的是由几个或者多个单词构成的大段文字,普通索引就不行了,这种场合用全文索引比较合适
效率
查询效率:唯一索引>自增主键>主键
插入效率:主键>自增主键>唯一索引
创建全文索引

  • 创建article表
CREATE TABLE article (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
content TEXT,
FULLTEXT (title, content) —在title和content列上创建全文索引
);

给现有的article表的title和content字段创建全文索引
索引名称为fulltext_article

ALTER TABLE article
ADD FULLTEXT INDEX fulltext_article (title, content)

使用全文索引

SELECT * FROM article WHERE MATCH(title, content) AGAINST(‘查询字符串’)

不使用全文索引

SELECT * FROM article WHERE content LIKE%查询字符串%

强烈注意:MySQL自带的全文索引只能用于数据库引擎为MyISAM的数据表,如果是其他数据引擎,则全文索引不会生效。此外,MySQL自带的全文索引只能对英文进行全文检索,目前无法对中文进行全文检索。如果需要对包含中文在内的文本数据进行全文检索,我们需要采用Sphinx(斯芬克斯)/Core

  • 备注1:目前,使用MySQL自带的全文索引时,如果查询字符串的长度过短将无法得到期望的搜索结果。MySQL全文索引所能找到的词的默认最小长度为4个字符。另外,如果查询的字符串包含停止词,那么该停止词将会被忽略。
  • 备注2:如果可能,请尽量先创建表并插入所有数据后再创建全文索引,而不要在创建表时就直接创建全文索引,因为前者比后者的全文索引效率要高。

3. 高性能的索引策略

3.1 独立的列

独立的列是指索引列不能是表达式的一部分,也不能是函数的参数。如果查询中的列不是独立的,则MySQL就不会使用索引。

3.2 前缀索引和索引选择性

有时候需要索引很长的字符列,这会让索引变得很大且慢。通常可以索引开始部分的字符,这样可以大大节约索引空间,从而提高索引效率。但这样会降低索引的选择性。

  • a. 索引的选择性是指,不重复的索引值(也称为基数)和数据表的记录总数(#T)的比值.
    范围从1/#T到1之间。索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。
    唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

  • b. 一般情况下某个列前缀的选择性也是足够高的,足以满足查询性能。
    对于BLOB、TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。

  • c. 诀窍在于要选择足够长的前缀以保证较高的选择性,同时又不能太长(以便节省空间)。
    计算合适的前缀长度的一个方法是计算完整列的选择性,并使前缀的选择性接近于完整列的选择性。

下面是如何计算完整列的选择性

SELECT COUNT(DISTINCT city)/COUNT(*) FROM sakila.city_demo;        
查询结构值为:0.031
通常来说,这个例子中如果前缀的选择性能够接近于0.031,基本上就可以用了。
可以在一个查询中针对不同前缀长度进行计算,这对于大表非常有用。

下面给出了如何在同一个查询中计算不同前缀长度的选择性:

SELECT COUNT(DISTINCT LEFT(city,3)) AS sel3,
COUNT(DISTINCT LEFT(city,4)) AS sel4,COUNT(DISTINCT LEFT(city,5)) AS sel5,
COUNT(DISTINCT LEFT(city,6)) AS sel6,COUNT(DISTINCT LEFT(city,7)) AS sel7 
FROM sakila.city_demo;
查询结果值,按顺序为:0.0238 ,  0.0293, 0.0305,0.0309,0.0310
查询显示当前前缀长度到达7的时候,再增加前缀长度,选择性提升的幅度已经很小了。

  • d. 只看平均选择性是不够的,也有例外的情况,需要考虑最坏情况下的选择性。
  • e. 前缀索引是一种能使索引更小、更快的有效办法。但另一方面也有其缺点:MySQL无法使用前缀索引做ORDER BY和GROUP BY , 也无法使用前缀索引做覆盖扫描。
  • f. 有时候后缀索引也有用途(例如,找到某个域名的所有电子邮件地址)。MySQL原生并不支持反向索引,但是可以把字符串反转后存储,并基于此建立前缀索引。可以通过触发器来维护这种索引。

3.3 多列索引

在MySQL或更新的版本中,会使用”索引合并”策略,查询能同时使用两个单列索引进行扫描,并将结果进行合并。这种算法有三个变种:

1.OR条件的联合(union),
2.AND条件的相交
3.组合前两种情况的联合及相交

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

  • 当出现服务器对多个索引做相交操作时(通常多个AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引。
  • 当服务器需要对多个索引做联合操作时(通常由多个OR条件),通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上。
    特别是当其中有些索引的选择性不高,需要合并扫描返回的大量数据时。
  • 优化器不会把这些计算到”查询成本”(cost)中,优化器值关系随机页面读取。
    这样不仅消耗更多的CPU和内存资源,还会影响查询的并发性。
  • 如果在EXPLAIN中看到有索引合并,应该好好检查一下查询和表的结构,看是不是已经最优的。也可以哦太难过参数optimizer_switch来关闭索引合并功能。也可以使用IGNORE INDEX提示让优化器忽略掉某些索引。

3.4 选择合适的索引列顺序

正确的索引顺序依赖于使用该索引的查询,并同时满足需要考虑如何更好地满足排序和分组的需要。

  • a. 在一个多列的B-Tree索引中,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列,等等。所以,索引可以按照升序或者降序进行全表扫描,以满足符合列顺序的ORDER BY,GROUP BY和DISTINCT等字句的查询需求。
  • b. 当不需要考虑排序和分组时,将选择性最高的列放在前面通常是很好的。这时候索引的作用只是用于优化WHERE条件查询。
  • c. 更具运行频率最高的查询来调整索引的顺序,让这种情况下索引的选择性最高。
  • d. 如果是从诸如pt-query-digest这样的工具的报告中提起”最差”查询,那么再按上面办法选定索引顺序往往是非常高效的。

如果没有类似的具体查询来运行,那么最好还是按经验法则来做,因为全局法则考虑的是全局基数和选择性,而不是某个具体查询。

SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity, 
count(*) from payment
查询结果值为:
staff_id_selectivity  0.0001,customer_id_selectivity  0.0373count(*)  16049 
customer_id 的选择性更高,所以答案是将其作为索引列的第一列

3.5 聚簇索引

InnoDB的聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。它的数据实际上存储在索引的叶子页中。” 聚簇”表示把数据行和相邻的键值紧凑地存储在一起。因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引(即主键索引)。

非聚簇索引

  1. 非聚集索引制定了表中记录的逻辑顺序
  2. 非聚集索引强调的是逻辑分类。可以说是定义了一套存储规则,而需要有一块控件来维护这个规则,这个被称之为索引表。

但是记录的物理和索引不一定一致,两种索引都采用B+树结构,非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针方式。非聚集索引层次多,不会造成数据重排。
非聚集索引叶节点仍然是索引节点,只是有一个指针指向对应的数据块,此如果使用非聚集索引查询,而查询列中包含了其他该索引没有覆盖的列,那么他还要进行第二次的查询,查询节点上对应的数据行的数据。

聚簇索引与非聚簇索引区别
聚集索引和非聚集索引的根本区别是表记录的排列顺序和与索引的排列顺序是否一致。

InnoDB将通过主键聚集数据。
如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键作为聚簇索引。InnoDB只聚集在同一个页面中的记录。包含相邻键值的页面可能会相距甚远。

聚簇索引的优点:

  • a. 可以把相关的数据保存在一起。
  • b. 数据访问更快。
  • c. 使用覆盖索引扫描的查询可以直接使用叶节点中的主键值。

聚簇索引的缺点:

  • a. 更新聚簇索引列的代价很高,因为会强制将每个被更新的行移动到新的位置。
  • b. 可能会导致页分裂。
  • c. 导致全表扫描变慢,尤其是行比较稀疏。
  • e. 二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引的叶子节点包含了引用行的主键列。
  • f. 二级索引访问需要两次索引查询,而不是一次。
    这是因为二级索引叶子节点保存的不是指向行的物理位置的指针,而是行的主键值。这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子节点获得对应的主键值,然后根据这个值去聚簇索引中查找到对应的行。这里做了重复工作:两次B-Tree查找而不是一次。

最好避免随机的(不连续且值的分布范围非常大)聚簇索引,特别是对于IO密集型的应用。
例如:从性能角度考虑,使用UUID作为聚簇索引会很糟糕:它使得聚簇索引的插入变得完全随机,这是最坏的情况,使得数据没有任何聚集。向UUID主键插入行不仅花费更长的时间,而且索引占用的空间也更大。这一方面是由于主键字段更长,另一方面毫无疑问是由于页分裂和碎片导致的。

使用InnoDB时应该尽可能地按主键顺序插入数据,并且尽可能地使用单调增加的聚簇键的值插入新行。
这样可以顺序地写入数据,减少随机IO,减少碎片和减少分页。
但对于高并发工作负载,按主键顺序插入可能造成明显的争用。

3.6 覆盖索引

如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为”覆盖索引”

覆盖索引的好处

  1. 索引条目通常远小于数据行大小,所以如果只需要读取索引,那么MySQL就会极大地减少数据访问量。
  2. 因为索引是按照值顺序存储的(至少在单个页内如此),所以对于IO密集型的范围查询会比随机从磁盘读取每一行数据的IO要少得多。
  3. 由于InnoDB的聚簇索引,覆盖索引对InnoDB表特别有用。 InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询。

不是所有类型的索引都可以作为覆盖索引
覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引。不是所有的索引都支持覆盖索引。

当发起一个索引覆盖的查询时,在EXPLAIN的Extra列可以看到”Using index”的信息。

MySQL不能在索引中执行LIKE操作
因为该操作可能转换为简单的比较操作,但是如果是通配符开头的LIKE查询,存储引擎就无法比较匹配。MySQL服务器只能提取数据行的值而不是索引值来作比较。

延迟关联
延迟对列的访问。在查询第一阶段MySQL可以做覆盖索引,在FROM子句的子查询中使用索引。

3.7 使用索引扫描来做排序

MySQL有两种方式可以生成有序的结果

通过排序操作;或者按索引顺序扫描;如果EXPLAIN出来的type列的值为”index”,则说明MySQL使用了索引来做排序(不要和Extra列的”Using index”搞混淆了)。

  • a. 索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就回表查询一次对应的行。这基本上都是随机IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢,尤其是在IO密集型的工作负载时。
  • b. 只有当索引的列顺序和ORDER BY子句顺序完全一致,并且所有列的顺序方向(倒序或正序)都一样时MySQL才能够使用索引来对结果做排序。如果查询需要关联多张表,则只有ORDER BY子句引用的字段全部为第一个表时,才能使用索引做排序。
  • c. 有一种情况下ORDER BY子句可以不满足索引的最左前缀的要求,就是前导列为常量的时候。

3.8 压缩(前缀索引)

MyISAM使用前缀索引压缩来减少索引的大小,从而让更多的索引可以放入内存中,这在某些情况下能极大地提高性能。默认值压缩字符串,到哪通过参数设置可以对整数压缩。

3.9 冗余和重复索引

MySQL允许在相同列上创建多个索引,无论是有意还是无意的。MySQL需要单独维护重复的索引,并且优化器在优化查询的时候也需要逐个地进行考虑,这会影响性能。

  • a.重复索引

例如如下代码

CREATE TABLE test (
ID INT NOT NULL PRIMARY KEY,
A INT NOT NULL,
B INT NOT NULL,
UNIQUE(ID),
INDEX(ID)
) ENGINE = InnoDB

一个经验不足的用户可能是想创建一个主键,先加上唯一限制,然后再加上索引以供查询使用。事实上,MySQL的唯一限制和主键限制都是通过索引实现的,因此,上面的写法实际上在相同的列上创建了三个重复的索引。通常并没有理由这样做,除非是在同一列上创建不同类型的索引来满足不同的查询需求。

  • b.冗余索引

如果创建了索引(A,B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。
冗余索引通常发生在为表添加新索引的时候。例如,有人可能会增加一个新的索引(A,B)而不是扩展已有的索引(A)。还有一种情况是将一个索引扩展为(A,ID),其中ID是主键,对于InnoDB来说主键已经包含在二级索引中了,所以这也是冗余的。

  • c.解决冗余索引和重复索引的方法很简单,删除这些索引就可以了,但首先要找出这样的索引。

3.10 未使用的索引

对于服务器上一些永远不用的索引,完全是累赘,建议考虑删除。

3.11 索引和锁

InnoDB只有在访问行的时候才会对其进行加锁,而索引能够减少InnoDB访问的行数,从而减少锁的数量。

发布了114 篇原创文章 · 获赞 34 · 访问量 9694

猜你喜欢

转载自blog.csdn.net/JAVA_I_want/article/details/104399537