人们常常缺乏对多列索引的理解。 常见的错误是为许多列设置独立索引,或者对索引列使用错误的顺序。 我们将在下一篇文章中讨论索引列顺序的问题。 首先我们看一下多列独立索引的情况。 以下面的表结构为例:
CREATE TABLE test (
c1 INT,
c2 INT,
c3 INT,
KEY(c1),
KEY(c2),
KEY(c3),
);
这种索引策略的使用通常是一些权威建议的结果(例如为 WHERE 条件中使用的条件列添加索引)。 事实上,这是完全错误的。 如果要评价的话,只能给1星。 以这种方式建立的索引比真正优化的索引慢几个数量级。 有时,当您无法设计三颗星以上的索引时,最好专注于优化行顺序或创建覆盖索引,而不是忽略 WHERE 条件。
覆盖索引(index)是指执行一条查询语句时只能从索引中获取,而不从数据表中读取。 也可以称为实现索引覆盖。 当一条查询语句满足覆盖索引条件时,MySQL只需要使用该索引即可返回查询所需的数据。 这样就避免了找到索引然后返回表的操作,减少了I/O,提高了效率。 例如表e中有一个公共索引(key1,key2)。 当我们传递SQL语句:key2 from e where key1 = '';时,我们就可以通过覆盖索引进行查询,而不需要从数据表中查找数据行。
在许多情况下,在许多列上创建独立索引并不能帮助 MySQL 提高性能。 MySQL 5.0及更新版本可以使用索引合并策略稍微优化这种类型的设计——这种方法允许对具有多列索引的表进行查询,以限制使用索引来定位所需的数据行。
索引合并就是对多个索引进行条件扫描,然后合并各自的结果(/union)
早期版本的MySQL只能使用一个索引,因此在没有索引辅助的情况下,MySQL通常会进行全表扫描。 例如,表上有一个 and 索引,但在 WHERE 条件中使用两个索引并不是一个好的选择:
SELECT film_id, actor_id FROM film_actor WHERE actor_id = 1 OR film_id = 1;
在早期版本的 MySQL 中,此查询将导致全表扫描,除非您像以下语句一样连接两个查询。
SELECT film_id, actor_id FROM film_actor WHERE actor_id = 1 UNION ALL
SELECT film_id, actor_id FROM film_actor WHERE film_id = 1 AND actor_id <> 1;
在MySQL 5.0之后的版本中,查询将使用两个索引并合并最终结果。 实现此过程需要算法的三种变体:
使用 OR 条件来获取并集数据。 使用 AND 条件获取交集数据。 将上述两步的数据进行交集,然后求并集。
上面的内容有点混乱。 其实应该是分布使用单个条件(为了使用索引)找到所有数据,然后将数据组合起来。 使用下面的方法检查一下。
EXPLAIN SELECT `film_id`,`actor_id` FROM `film_actor` WHERE `actor_id`=1 OR `film_id`=1
可以看到查询方式是全表扫描,但是使用了Extra进行优化。 MySQL 在处理负责任的查询时使用这种技术,因此您可能会在 Extra 中看到嵌套操作。 这种索引合并策略有时可以很好地发挥作用,但更多时候它应该被视为索引使用不良的指标:
当服务器使用交集索引(通常使用AND条件)时,通常意味着您需要一个包含所有相关列的索引,而不是组合单独的索引列。 当服务器使用联合索引(通常使用OR条件)时,有时缓存、排序和合并操作会占用大量CPU和内存资源,特别是当索引并不总是可过滤时,这会导致扫描返回大量数字用于合并操作的数据行。 请记住,优化器不承担这些成本 - 它只是优化随机页面读取的数量。 这将使查询“无价”,导致全表扫描实际上更慢。 高 CPU 和内存使用率会影响并发查询,但单独运行查询语句时不会出现这些影响。 因此,有时最好像 MySQL 4.1 中那样重写使用 UNION 的查询。
当您在使用分析时看到索引合并时,您应该检查查询和表结构,看看这是否是最佳方法。 您可以通过使用(优化开关)禁用索引合并来检查这一点。
然后将索引改为联合索引(删除原来的两列独立索引和),看看效果。 可以看到此时避免了全表查询。
ALTER TABLE film_actor ADD INDEX `sindex` (`film_id`,`actor_id`);