select * from tast_user where id in (select id from user where name like '王%');
我们以为这个sql会解析成下面的形式
select * from tast_user where id in (1,2,3,4,5);
实际上MySQL是这样解析的
select * from tast_user where exists
(select id from user where name like '王%' and tast_user.id = user.id);
MySQL会将相关的外层表压缩到子查询中,它认为这样可以更高效的查找到数据行。
select tast_user.* from tast_user inner join user using(tast_user.id) where user.name like '王%'
另一个优化的办法就是使用group_concat()在in中构造一个由逗号分隔的列表。有时这比上面使用关联改写更快。因为使用in()加子查询,性能通常会非常糟糕。所以通常建议使用exists()等效的改写查询来获取更好的效率。
(select first_name,last_name from actor order by last_name) union all
(select first_name,last_name from customer order by last_name) limit 20;
这条查询会将actor中的记录和customer表中的记录全部取出来放在一个临时表中,然后在取前20条,可以通过在两个子查询中分别加上一个limit 20来减少临时表中的数据。
select min(actor_id) from actor where first_name = 'wang'
因为在first_name字段上并没有索引,因此MySQL将会进行一次全表扫描。如果MySQL能够进行主键扫描,那么理论上,当MySQL读到第一个太满足条件的记录的时候就是我们需要的最小值了,因为主键是严哥按照actor_id字段的大小排序的。但是MySSQL这时只会做全表扫描,我们可以通过show status的全表扫描计数器来验证这一点。一个区县优化办法就是移除min()函数,然后使用limit 1来查询。
update table set cnt = (select count(*) from table as tb where tb.type = table.type);
这个sql虽然符合标准单无法执行,我们可以通过使用生成表的形式绕过上面的限制,因为MySQL只会把这个表当做一个临时表来处理。
update table inner join
(select type,count(*) as cnt from table group by type) as tb using(type)
set table.cnt = tb.cnt;
实际上这执行了两个查询:一个是子查询中的select语句,另一个是夺标关联update,只是关联的表时一个临时表。子查询会在update语句打开表之前就完成,所以会正常执行。