查询优化器的局限
MySQL的子查询实现得非常糟糕。最糟糕的一类查询是WHERE条件中包含IN()的子查询语句。
例子:
SELECT * FROM sakila.film
-> WHERE film_id IN(
-> SELECT film_id FROM sakila.film_actor WHERE actor_id = 1);
重写方式一:
SELECT film.* FROM sakila.film
-> INNER JOIN sakila.film_actor USING(film_id)
-> WHERE actor_id = 1;
重写方式二:
SELECT * FROM sakila.film
-> WHERE EXISTS(
-> SELECT * FROM sakila.film_actor WHERE actor_id = 1
-> AND film_actor.film_id = film.film_id);
提示:需对实际数据做具体的测试,重写的方式不一定最优。
子查询与关联查询需要实际测试来判断哪种查询更优。
MySQL无法利用多核特性来并行执行查询。很多其他的关系型数据库能够提供这个特性,但是MySQL做不到。
MySQL并不支持松散索引扫描,也就无法按照不连续的方式扫描一个索引。MySQL 5.0之后的版本,在某些特殊的场景下是可以使用松散索引扫描的,例如,在一个分组查询中需要找到分组的最大值和最小值:
SELECT actor_id, MAX(film_id)
-> FROM sakila.film_actor
-> GROUP BY actor_id\G
在EXPLAIN中的Extra字段显示“Using index for group-by”,表示这里将使用松散索引扫描。
在MySQL很好地支持松散索引扫描之前,一个简单的绕过问题的办法就是给前面的列加上可能的常数值。
对于MIN()和MAX()查询,MySQL的优化做得并不好。一个例子:
SELECT MIN(actor_id) FROM sakila.actor WHERE first_name='PENELOPE';
因为在first_name字段上并没有索引,因此MySQL将会进行一次全表扫描。如果MySQL能够进行主键扫描,那么理论上,当MySQL读到第一个满足条件的记录的时候,就是我们需要找的最小值了,因为主键是严格按照actor_id字段的大小顺序排列的。
一个曲线的优化办法是移除MIN(),然后使用LIMIT来将查询重写如下:
SELECT actor_id FROM sakila.actor USE INDEX(PRIMARY)
-> WHERE first_name = 'PENELOPE' LIMIT 1;
查询优化器的提示
DELAYED
这个提示对INSERT和REPLACE有效。MySQL会将使用该提示的语句立即返回给客户端,并将插入的行数据放入到缓冲区,然后在表空闲时批量将数据写入。日志系统使用这样的提示非常有效,或者是其他需要写入大量数据但是客户端却不需要等待单条语句完成I/O的应用。这个用法有一些限制:并不是所有的存储引擎都支持这样的做法;并且该提示会导致函LAST_INSERT_ID()无法正常工作。
STRAIGHT_JOIN
这个提示可以放置在SELECT语句的SELECT关键字之后,也可以放置在任何两个关联表的名字之间。第一个用法是让查询中所有的表按照在语句中出现的顺序进行关联。第二个用法则是固定其前后两个表的关联顺序。
SQL_BUFFER_RESULT
这个提示告诉优化器将查询结果放入到一个临时表,然后尽可能快地释放表锁。这和前面提到的由客户端缓存结果不同。当你没法使用客户端缓存的时候,使用服务器端的缓存通常很有效。带来的好处是无须在客户端上消耗太多的内存,还可以尽可能快地释放对应的表锁。代价是,服务器端将需要更多的内存。
SQL_CACHE和SQL_NO_CACHE
这个提示告诉MySQL这个结果集是否应该缓存在查询缓存中。
FOR UPDATE和LOCK IN SHARE MODE
这两个提示主要控制SELECT语句的锁机制,但只对实现了行级锁的存储引擎有效。使用该提示会对符合查询条件的数据行加锁。对于INSERT...SELECT语句是不需要这两个提示的。
这两个提示会让某些优化无法正常使用,例如索引覆盖扫描。InnoDB不能在不访问主键的情况下排他地锁定行,因为行的版本信息保存在主键中。
这两个提示经常被滥用,很容易造成服务器的锁争用问题。应该尽可能地避免使用这两个提示,通常都有其他更好的方式可以实现同样的目的。
USE INDEX、IGNORE INDEX和FORCE INDEX
这几个提示会告诉优化器使用或者不使用哪些索引来查询记录。