创建高性能的索引
1. 索引(在MySQL中也叫做"键(key)")是存储引擎用于快速找到记录的一种数据结构。
2. 索引可以包含一个或多个列的值。如果索引包含多个列,那么列的顺序也十分重要,因为MySQL只能高效低使用索引的最左前缀列。
3. B-Tree索引:当人们谈论索引的时候,如果没有特别执行索引类型,那多半说是B-Tree索引,它使用B-Tree数据结构来存储数据。
1). 可以使用B-Tree索引的查询类型:
a. 全职匹配:全值匹配指的是和索引中的所有列进行匹配。
b. 匹配最左前缀:即只使用索引的第一列。
c. 匹配列前缀:也可以值匹配某一列的值的开头部分。
d. 匹配范围值
e. 精确匹配某一列并范围匹配另外一列
f. 只访问索引的查询:覆盖索引
2). 因为索引树中的节点是有序的,所以除了按值查找之外,索引还可以用于查询中的ORDER BY操作(按顺序查找)。一般来说,如果B-Tree可以按照某种方式查找到值,那么也可以按照这种方式用于排序。
3). B-Tree索引的限制
a. 如果不是按索引的最左列开始查找,则无法使用索引。
b. 不能跳过索引中的列。
c. 如果查询中有某个列的范围查询,则最右边的所有列都无法使用索引优化查询。如果范围查询列值的数量有限,那么可以通过使用多个等于条件来代替范围查找。
4. 哈希索引(hash index):哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。结构十分紧凑,查询速度非常快。
InnoDB引擎有一个特殊的功能叫做"自适应哈希索引"。当InnoDB注意到某个索引值被使用得非常频繁时,它会在内存中基于B-Tree索引之上再创建一个哈希索引,这样B-Tree索引也就有哈希索引的一些优点。
5. 空间数据索引,全文索引,其他索引类别
6. 索引的优点:索引可以让服务器快速定位到表的指定位置。最常见的B-Tree索引,按照顺序存储数据,所以MySQL可以用来做ORDER BY 和 GROUP BY操作。总结下来,索引有如下三个优点:
1). 索引大大减少了服务器需要扫描的数据量
2). 索引可以帮助服务器避免排序和临时表
3). 索引可以将随机IO变为顺序IO。
7. 三星系统:索引将相关的记录放到一起则获得一星;如果索引中数据顺序和查找中的排序顺序一致则获得二星;如果索引中的列包含了查询中需要的全部列则获得三星。
8. 索引是最好的解决方案吗?对于非常小的表,大部分情况下简单的全表扫描更高效。对于中到大型的表,索引就非常有效。但对于特大型的表,建立和使用索引的代价将随之增长。这种情况下,则需要一种技术
可以直接区分出查询需要的一组数据,而不是一条记录一条记录的匹配。
如果表的数量特别多,可以建立一个元数据信息表,用来查询需要用到的某些特性。例如:记录"哪个用户信息存储在哪个表里面"。
9. 高性能的索引策略:
1). 独立的列:独立的列是指索引列不能是表达式的一部分,也不能是函数的参数。如果查询中的列不是独立的,则MySQL就不会使用索引。
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). 多列索引:在MySQL或更新的版本中,会使用"索引合并"策略,查询能同时使用两个单列索引进行扫描,并将结果进行合并。这种算法有三个变种:OR条件的联合(union),AND条件的相交,
组合前两种情况的联合及相交。
索引合并策略有时候是一种优化的结果,但实际上更多的时候说明表上的索引建的很糟糕:
a. 当出现服务器对多个索引做相交操作时(通常多个AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引。
b. 当服务器需要对多个索引做联合操作时(通常由多个OR条件),通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上。特别是当其中有些索引的选择性不高,需要合并扫描返回的大量数据时。
c. 优化器不会把这些计算到"查询成本"(cost)中,优化器值关系随机页面读取。这样不仅消耗更多的CPU和内存资源,还会影响查询的并发性。
d. 如果在EXPLAIN中看到有索引合并,应该好好检查一下查询和表的结构,看是不是已经最优的。也可以哦太难过参数optimizer_switch来关闭索引合并功能。也可以使用IGNORE INDEX提示让优化器
忽略掉某些索引。
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.0373, count(*) 16049
customer_id 的选择性更高,所以答案是将其作为索引列的第一列
e. 尽管关于选择性和基数的经验法则值得去研究和分析,但一定要记住别忘了WHERE字句中的排序、分组和范围条件等其他因素,这些因素可能对查询的性能造成非常大的影响。
5). 聚簇索引:InnoDB的聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。它的数据实际上存储在索引的叶子页中。"聚簇"表示把数据行和相邻的键值紧凑地存储在一起。因为无法同时把数据行存放在
两个不同的地方,所以一个表只能有一个聚簇索引。
5.1). InnoDB将通过主键聚集数据。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键作为聚簇索引。InnoDB只聚集在同一个页面中的记录。包
含相邻键值的页面可能会相距甚远。
5.2). 聚簇索引的优点:
a. 可以把相关的数据保存在一起。
b. 数据访问更快。
c. 使用覆盖索引扫描的查询可以直接使用叶节点中的主键值。
5.3). 聚簇索引的缺点:
a. 更新聚簇索引列的代价很高,因为会强制将每个被更新的行移动到新的位置。
b. 可能会导致页分裂。
c. 导致全表扫描变慢,尤其是行比较稀疏。
e. 二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引的叶子节点包含了引用行的主键列。
f. 二级索引访问需要两次索引查询,而不是一次。这是因为二级索引叶子节点保存的不是指向行的物理位置的指针,而是行的主键值。这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子
节点获得对应的主键值,然后根据这个值去聚簇索引中查找到对应的行。这里做了重复工作:两次B-Tree查找而不是一次。
5.4). 最好避免随机的(不连续且值的分布范围非常大)聚簇索引,特别是对于IO密集型的应用。例如:从性能角度考虑,使用UUID作为聚簇索引会很糟糕:它使得聚簇索引的插入变得完全随机,这是最坏的情况,
是的数据没有任何聚集。向UUID主键插入行不仅花费更长的时间,而且索引占用的空间也更大。这一方面是由于主键字段更长,另一方面毫无疑问是由于页分裂和碎片导致的。
5.5). 使用InnoDB时应该尽可能地按主键顺序插入数据,并且尽可能地使用单调增加的聚簇键的值插入新行。这样可以顺序地写入数据,减少随机IO,减少碎片和减少分页。
但对于高并发工作负载,按主键顺序插入可能造成明显的争用。
6). 覆盖索引:如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为"覆盖索引"
6.1 覆盖索引的好处:
a. 索引条目通常远小于数据行大小,所以如果只需要读取索引,那么MySQL就会极大地减少数据访问量。
b. 因为索引是按照值顺序存储的(至少在单个页内如此),所以对于IO密集型的范围查询会比随机从磁盘读取每一行数据的IO要少得多。
c. 由于InnoDB的聚簇索引,覆盖索引对InnoDB表特别有用。InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询。
6.2 不是所有类型的索引都可以作为覆盖索引。覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引。不是所有
的索引都支持覆盖索引。
6.3 当发起一个索引覆盖的查询时,在EXPLAIN的Extra列可以看到"Using index"的信息。
6.4 MySQL不能在索引中执行LIKE操作。因为该操作可能转换为简单的比较操作,但是如果是通配符开头的LIKE查询,存储引擎就无法比较匹配。MySQL服务器只能提取数据行的值而不是索引值来作比较。
6.5 延迟关联:延迟对列的访问。在查询第一阶段MySQL可以做覆盖索引,在FROM子句的子查询中使用索引。
7). 使用索引扫描来做排序:MySQL有两种方式可以生成有序的结果:通过排序操作;或者按索引顺序扫描;如果EXPLAIN出来的type列的值为"index",则说明MySQL使用了索引来做排序(不要和Extra列的
"Using index"搞混淆了)。
a. 索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就回表查询一次对应的行。这基本上都是随机IO,因此
按索引顺序读取数据的速度通常要比顺序地全表扫描慢,尤其是在IO密集型的工作负载时。
b. 只有当索引的列顺序和ORDER BY子句顺序完全一致,并且所有列的顺序方向(倒序或正序)都一样时,MySQL才能够使用索引来对结果做排序。如果查询需要关联多张表,则只有ORDER BY子句引用的
字段全部为第一个表时,才能使用索引做排序。
c. 有一种情况下ORDER BY子句可以不满足索引的最左前缀的要求,就是前导列为常量的时候。
8). 压缩(前缀索引):MyISAM使用前缀索引压缩来减少索引的大小,从而让更多的索引可以放入内存中,这在某些情况下能极大地提高性能。默认值压缩字符串,到哪通过参数设置可以对整数压缩。
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. 解决冗余索引和重复索引的方法很简单,删除这些索引就可以了,但首先要找出这样的索引。
10). 未使用的索引:对于服务器上一些永远不用的索引,完全是累赘,建议考虑删除。
11). 索引和锁:
a. InnoDB只有在访问行的时候才会对其进行加锁,而索引能够减少InnoDB访问的行数,从而减少锁的数量。
10. 索引案例学习:
1). 过滤条件:
a. 可以通过使用IN语句让MySQL使用索引,避免使用范围查询导致SQL语句无法使用索引。但是这种技巧也不能滥用,因为每额外增加一个IN()条件,优化器就需要做的组合(每个in的一个元素
相当于一条查询)都将以指数形式增加。
b. 在有更多不同值的列上创建索引的选择性会更好。一般来说这样做都是对的,因为可以让MySQL更有效的过滤掉不需要的行。
c. 尽可能将需要做范围查询的列放到索引的后面,以便优化器能使用尽可能多的索引列。
d. 考虑表上的所有选项。当设计索引时,不要只为现有的查询考虑需要哪些索引,还需要考虑对查询进行优化。
2). 避免多个范围条件:
a. MySQL无法再使用范围列后面的其他索引列了,但是对于"多个等值条件查询"则没有这个限制。
3). 优化排序:
a. 对于那些选择性非常低的列,可以增加一些特殊的索引来锁排序。例如:可以创建(sex,rating)索引:SELECT <cols> FROM profiles WHERE sex='M' order by rating LIMIT 10;
这个查询同时使用了ORDER BY 和 LIMIT ,如果没有索引的话会很慢。
b. 优化索引另一个比较好的策略是使用延迟关联,通过使用覆盖索引查询返回需要的主键,再根据这些主键关联原表获得需要的行。这可以减少MySQL扫描那些需要丢弃的行数。
11. 维护索引和表:
1). 找到并修复损坏的表:如果遇到数据损坏,最重要的是找出是什么导致了损坏,而不只是简单地修复,否则可能还会不断的损坏。可以通过设置innodb_force_recovery参数进入InnoDB的强制回复模式来
修复数据。另外还可以通过使用开源的InnoDB数据恢复工具箱直接从InnoDB数据文件恢复数据。
2). 更新索引统计信息:InnoDB不在磁盘存储索引统计信息,而是通过随机的索引访问进行评估并将其存储在内存中。InnoDB引擎通过抽样的方式计算统计信息,首先随机地读取少量的索引页面,然后以此
为样本计算索引的统计信息。
3). 减少索引和数据的碎片:
数据碎片分类:
行碎片:这种碎片指的是数据行被存储为多个地方的多个片段中。即使查询值从索引中访问一行记录,行碎片也会导致性能下降。
行间碎片:行间碎片是指逻辑上顺序的夜,或者行在磁盘上不是顺序存储的。行间碎片对诸如全表扫描和聚簇索引扫描之类的操作有很大的影响,因为这些操作原本能从磁盘上顺序存储的数据中获益。
剩余空间碎片:剩余空间碎片是指数据页中有大量的空余空间。这会导致服务器读取大量不需要的数据,从而造成浪费。
InnoDB不会出现短小的行碎片,InnoDB会移动短小的行并重写到一个片段中。
新版的InnoDB新增了"在线添加"和删除索引功能,可以先删除,然后再重新创建索引的方式来消除索引的碎片化。
12. mysql中每个表都有一个聚簇索引(clustered index),除此之外的表上的每个非聚簇索引都是二级索引,又叫辅助索引(secondary indexes)。