创建高性能索引

索引是存储引擎用于快速找到记录的一种数据结构。在数据量较小且负载较低时,不恰当的索引对性能的影响可能还不明显,但当数据量逐渐增大时,性能会急剧下降。索引优化应该是对查询性能优化最有效的手段了,索引能轻易将查询性能提高几个数量级。创建一个最优的索引经常需要重写查询。

B-Tree索引

索引对多个值进行排序的依据是建表语句中定义索引时列的顺序。
全值匹配指的是和索引中所有列进行匹配。
B-Tree通常可支持只访问索引的查询,即查询只需要访问索引,无须访问数据行。
索引树中的节点是有序的,除了按值查找外,索引还可以用于查询中的order by。
如果不是按照索引的最左列开始查找,则无法使用索引。
不能跳过索引中的列。
如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找。如果范围查询列值的数量有限,可以通过使用多个等于条件来代替范围条件。

哈希索引

基于哈希表实现,只有精确匹配索引所有的列的查询才有效。对每行数据,存储引擎会对所有索引列计算一个哈希码。哈希索引将所有哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。

存在的限制:
哈希索引只包含哈希值和行指针,不存储字段值,不能使用索引中的值来避免读取行。
哈希索引数据并不是按照索引值顺序存储的,无法用于排序。
哈希索引不支持部分索引列匹配查找,始终是使用索引列的全部内容计算哈希值。
如果哈希宏图很多,索引维护代价很高。

InnoDB有个功能叫自适应哈希索引。当InnoDB注意到某些索引值被使用得非常频繁时,会在内存中基于B-Tree索引之上再创建一个哈希索引。
如果存储引擎不支持哈希,可在B-Tree基础之上创建一个伪哈希索引。使用哈希值而不是键本身进行索引查找。需要在查询的where子句指定哈希函数。比如URL的查询,用哈希值代替URL减少存储量。基于一个新列查找。
缺陷是需要维护哈希值,可用触发器实现。
要避免哈希冲突,必须在where子句中带入哈希值和对应的列值。

对索引的评价

索引可以让服务器快速定位到表的指定位置。
索引的优点:
大大减少了服务器需要扫描的数据量
可以帮助服务器避免排序和临时表
索引可以将随机IO变为顺序IO
只有当索引帮助存储引擎快速查找到记录带来的好处大于其带来的额外工作时,索引才是有效的。对于非常小的表,大部分情况下简单的全表扫描更高效。对于大到中型表,索引就非常有效。对于特大型表,代价随之增长,可使用分区技术。
若表的数量非常多,可建立元数据信息表,如查询哪个用户的信息存储在哪个表中的元数据。

索引策略

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

有时候需要索引很长的字符列,这会让索引变得大且慢。可以选择足够从的前缀保证较高的选择性,同时又不能太长。可以通过计算选择性选择合适的前缀长度。
无法使用前缀索引做order by和group by。
有时候后缀索引也有用,如电子邮件地址。MySQL并不支持反向索引,可以把字符串反转存储建立前缀索引。

在多个列上建立单独的索引大部分情况下并不能提高MySQL的查询性能。MySQL更新版本引入索引合并策略,一定程度上可使用表上的多个单列索引来定位指定的列。
查询能够同时使用两个单列索引进行扫描,并将结果进行合并。
对多个索引合并会耗费大量资源。优化器并不会将这些计算到查询成本上,只会关心随机页面的读取。如果在explain中看到索引合并,要好好检查查询结果,可以设置参数关闭索引合并功能。

当不需要考虑排序和分组时,将选择性最高的列放在前面通常是很好的,这时索引的作用只是优化where查找。
性能不只是依赖所有列的选择性,也和查询条件值分布有关,需要根据那些运行频率最高的查询来调整索引列的顺序。
不要假设平均情况下的性能也能代表特殊情况下的性能,特殊情况可能会摧毁整个应用的性能。比如查询好友数较多的用户。经验法则在多数情况下也是有用的。

聚簇索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。InnoDB的聚簇索引在同一个结构中保存了B-Tree索引和数据行。
当表有聚簇索引时,它的数据行实际上存放在索引的叶子页中。因为无法同时把数据行存放在两个不同的地方,一个表只能有一个聚簇索引。
InnoDB通过主键聚集数据,如果没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有这样的索引,InnoDB会隐式定义一个主键作为聚簇索引。

优点:可将相关数据保存在一起。数据访问速度更快。
缺点:更新聚簇索引列的代价很高,因会强制InnoDB将每个被更新的行移动到新的位置。插入新行可能产生页分裂。

MyISAM按照数据插入的顺序存储在磁盘上,在每行数据的旁边显示了行号。聚簇索引的每个叶子节点都包含了主键值、事务ID、用于事务和MVCC的回滚指针以及所有剩余的列。
InnoDB二级索引的叶子节点中存储的不是行指针而是主键值。这样减少了当行移动或数据页分裂时二级索引的维护工作。

可以定义一个代理键作为主键,这种主键的数据应该和应用无关,最简单的方法时使用auto_increment自增列,可保证数据行是按顺序写入,对根据主键做关联操作的性能会更好。
如果是按随机值,不按顺序,会导致大量的随机IO,造成频繁的页分裂。把这些随机值载入到聚簇索引后,需要做optimize table重建表优化页的填充。
因此,使用InnoDB时应尽可能按主键顺序插入数据,尽可能单调增加局促键的值来插入新行。
但是对于高并发工作负载,在InnoDB中按主键顺序插入可能会造成明显的争用。并发插入可能导致间隙锁的竞争以及auto_increment锁机制。可以更改参数配置或重新设计应用。

覆盖索引

包含所有需要查询的字段的值,只需要扫描索引而无须回表,通过索引查询直接得到要查询列的值。
如果索引不能覆盖到所有查询的列,可以用延迟访问列,用子查询自连接查询。

索引扫描排序

如果索引不能覆盖查询所需的全部列,就得每扫描一条索引记录就回表查询一次对应的行。
只有当索引的列顺序和order by子句的顺序完全一致,且所有列的排序方向都一样时,才能使用索引对结果排序。如果查询要关联多张表,只有当order by子句引用的字段全为第一个表时,才能使用索引做排序。需要满足索引的最左前缀要求。
如果where子句查询对前面的列指定了常量,order by子句可不遵循最左前缀要求。

压缩索引使用更少的空间,操作可能更慢。因为每个值的压缩前缀都依赖前面的值,所以MyISAM查找无法使用二分。

冗余和重复索引

MySQL允许在相同的列上创建多个索引,无论是有意的还是无意的。MySQL需要单独维护重复的索引,并且优化器在优化查询时也需要逐个进行考虑,会影响性能。
MySQL的唯一限制和主键限制都是通过索引实现的,会在相同列上创建重复的索引。
冗余索引通常发生在为表添加新索引的时候。
应该尽量扩展已有的索引而不是创建新索引。有时候出于性能方面的考虑需要冗余索引,因为扩展已有的索引会导致其变得太大,从而影响其他使用该索引的查询性能。

索引和锁

索引可让查询锁定更少的行。
InnoDB只有访问行的时候才会对其加锁,而索引能减少InnoDB访问的行数,从而减少锁的数量。
MySQL5.1和更新版本中,InnoDB可在服务器端过滤掉行后就释放锁,早期只有事务提交后才能释放锁。
InnoDB在二级索引上使用共享锁,但访问主键索引需要排它锁。

常用策略

第一个要考虑的事情是需要使用索引来排序,还是先检索数据再排序。
需要看看哪些列拥有很多不同的取值,哪些列在where子句中出现最频繁。在有更多不同值的列上创建索引的选择性会更好,因为可以更有效过滤掉不需要的行。
考虑到使用频率,建议在创建联合索引加上选择性低的列,如sex。
如果查询没有该列可以绕过前缀,在查询条件中新增and sex in(m,f)来让MySQL选择该索引。这样写和没有这个条件的返回结果相同。但是如果列有太多值就不行了。
基本原则:考虑表上的所有选项。当设计索引时,不要只为现有的查询考虑需要哪些索引,还需要考虑对查询进行优化。
如果发现某些查询需要创建新索引,但该索引会降低其他查询的效率,应想下能否优化原来的查询。
需要考虑其他常见的where条件的组合,并需要了解哪些组合在没有合适索引的情况下会很慢。
如果想重用索引而不是建立大量组合索引,可使用in来避免。
尽可能将需要范围查询的列放在索引后面,以便让优化器使用尽可能多的索引列。
每增加一个In,优化器需要做的组合会以指数增长,会降低查询效率。
对于范围查询,无法使用范围列后面的其他索引,in相当于多个等值查询。

你可能感兴趣的:(创建高性能索引)