高性能的索引策略
独立的列
以下两个错误的语句,需要始终将索引列单独放在比较符号的一侧
SELECT actor_id FROM dskils.actor WHERE actor.id + 1 = 5;
SELECT ... WHERE TO_DAYS(CURRENT_DATE) - TO_DAYS(date_col) <= 10;
前缀索引和索引的选择性
有时候需要索引很长的字符列,这会让索引变得大且慢.一个策略是前面提到过的模拟哈希索引.但有时候还不够
通常可以索引开始的部分字符,这样可以大大节约索引空间,提高索引效率.但是这样也会降低索引的选择性(cardinality).索引的选择性越高查询效率越高,唯一索引的选择性最高为1.
一般情况下,某个列前缀的选择性也是足够高的,足以满足查询性能.但是对于 BLOB,TEXT 或者很长的 VARCHAR 类型的列,必须使用前缀索引,因为MySQL 不允许索引这些列的完整长度.
诀窍在于要选择足够长的前缀索引以保证较高的选择性,同事又不能太长(节约空间).前缀应该足够长,以使得前缀索引的选择性接近于索引的整个列.
计算合适的前缀长度的另外一个方法就是计算完整列的选择性,并使前缀的选择性接近于完整列的选择性。下面展示如何计算完整列的选择性:
多列索引
选择合适的索引列顺序
当不需要考虑排序和分组时,最好将选择性最高的列放到索引的最前列.具体的可以测试得出.
聚簇索引
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式.InnoDB 的聚簇索引实际上在同一结构中保存了 B-Tree 索引和数据行.
当表有聚簇索引时,他的数据行实际上存放在索引的叶子页中.术语"聚簇"表示数据行和相邻的兼职紧凑的存储在一起.因为无法同时把数据行存放在两个不同的地方,所以一个表只有一个聚簇索引(不过覆盖索引可以模拟多个聚簇索引的情况).
聚集的数据有以下重要的优点:
- 可以把相关的数据保存在一起.
- 数据访问更快.因为聚簇索引将索引和数据存放在用一个 B-Tree 中.
- 使用覆盖索引扫描的查询可以直接使用节点中的主键值
也有以下缺点:
- 聚簇索引可以提高 I/O 的性能.如果将数据全部放在内存中将没有使用聚簇索引的必要.
- 插入速度严重依赖插入顺序.
- 更新聚簇索引的代价很高.
- 基于聚簇索引的表在插入新行,或者主键被更新需要移动行的时候可能面临叶分裂的问题.叶分裂会导致表占用更多的磁盘空间
- 聚簇索引可能导致全表扫描变慢
- 二级索引可能比想象的更大
- 二级索引访问需要两次索引查找,而不是一次
覆盖索引
如果索引的叶子节点中已经包含要查询的数据,那么还有什么必要再回表查询呢?如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称为覆盖索引.
覆盖索引有以下优点:
- 索引条目通常远小于数据行大小,所以如果只需要读取索引,就极大的减少数据访问量。这对MyISAM尤其正确,因为MyISAM能压缩索引以变得更小.
- 因为索引是按照顺序存储的(单页),所以对于I/O密集型的范围查询会比随机从磁盘读取每一行数据的I/O要少很多
- 由于InnoDB的聚簇索引,覆盖索引对InnoDB特别有用。InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询
覆盖索引必须要存储索引列的值,而哈希索引、空间索引、和全文索引都不能存储列的值,所以MySQL只能使用B-Tree索引做覆盖索引.
当发起一个索引覆盖的查询时,在EXPLAIN的Extra列可以看到Usingindex的信息,另外需要注意触发覆盖索引的条件
使用索引来做排序
MySQL 有两种方式可以生成有序的结果:通过排序操作;或者按索引顺序扫描;如果 EXPLAIN 出来的 type 的值为 index, 则说明 MYSQLS使用了索引扫描来做排序.
- 排序操作: 将查找出来的结果使用排序算法进行排序
- 按索引顺序扫描:ORDER BY语句后跟着一个被索引的列,如此一来索引的顺序就是索引对应记录的顺序,这样直接顺着索引一直往下读取记录即可得到有序的结果。
- 随机IO操作会大大拖慢执行速度,导致按照索引扫描的执行速度反而要比排序操作要慢。因此,在考虑使用按照索引扫描的方式去获得有序结果,那么设计索引时必须要考虑索引覆盖的情况
只有当索引的列顺序和 ORDER BY 字句的顺序完全一致,并且所有列的排列方向(倒序或者正序)都一样, MySQL 才能够使用索引来对结果做排序.如果查询需要关联多张表时,则只有当 ORDER BY 子句引用的字段全部为第一个表时,才能使用索引做排序.ORDER BY子句和查找型查询的限制是一样的:需要满足索引的最左前的要求;否则, MySQL 都需要执行排序操作,从而无法利用索引排序.
有一种例外,ORDER BY后跟的字段可以不满足最左前缀原则:当前导量为常量的时候。这样可以弥补索引的不足.
压缩(前缀)索引
MyISAM 使用前缀压缩来减少索引的大小,从而让更多的索引可以放入内存,默认只压缩字符串.
MyISAM 压缩每一个索引块的方法是先保存索引块中的第一个值,然后将其他值和第一个值进行比较得到相同前缀的字节数和剩余的不同后缀部分,把这部分存储起来即可.
压缩快可以使用更少的空间,代价是某些操作可能更慢.
冗余和重复索引
MySQL 允许在相同的列上创建多个索引,无论是有意的还是无意的.MySQL 需要单独维护重复的索引,并且优化器在优化查询的时候也需要逐个的进行查询考虑,这会影响性能.
重复索引是指在相同的列上按照相同的顺序创建的相同类型的索引.
冗余索引通常发生在为表添加新索引的时候.例如有索引(A,B),又添加(A),或者(A,ID),因为 InnoDB 主键列已经包含在二级索引中.
大多数时候都不需要冗余索引,但是如果扩展索引会导致已有的索引变得太大从而影响查询性能,可以新建索引.
未使用的索引
可以使用工具帮助定位未使用的索引.例如 Percona Toolkit中的 pt-index-usage,该公布工具不仅可以读取查询日志并且对日志中的每条查询进行 EXPALIN 操作.
索引和锁
索引可以让查询锁定更少的行.虽然 InnoDB 的行锁效率很高,内存使用很少,但是锁定行的时候任然会带来额外的开销;其次锁定超过需要的行会增加锁争用并减少并发性.
InnoDB 只有在访问行的时候才会对其加锁,而索引能减少 InnoDB 访问的行数,从而减少锁的数量.但着只有当 InnoDB 在存储引擎层能够过滤掉所有不需要的行时才有效.否则数据传输到服务器层以后, MySQL服务器才能应用 WHERE 子句,这是已经无法避免行锁定了,只有到适当的时候才会释放.
InnoDB 在二级索引上使用共享(读)锁,但访问主键索引需要排他(写)锁.这消除了使用覆盖索引的可能性,并且使得 SELECT FOR UPDATE 比 LOCK IN SHARE MODE或非锁定查询要慢得多.