只查询需要的数据
永远不要使用select *
除了count(*)
用缓存等手段减少重复数据访问
关注查询的响应时间,扫描行数,返回行数
如果发现某个查询扫描行数远大于返回行数,可以从以下角度优化
根据实际情况选择,需要权衡
delete from log where created_at < DATE_SUB(NOW(), Interval 3 MONTH);
-- 替换成如下 降低因为锁表带来的并发问题,降低资源占用等
rows = 0;
do {
rows = do_query("delete from log where created_at < DATE_SUB(NOW(), Interval 3 MONTH) limit 10000");
} while rows > 0
select * from tag
join tag_post tp on tp.tag_id=tag.id
join post on post.id=tp.post_id
where tag.tag = 'mysql';
-- 替换为如下查询
select * from tag where tag = 'mysql';
select * from tag_post where tag_id=1234;
select * from post where id in (1, 2, 3, 4);
分解关联查询的优势:
作用: 统计行数,统计某列的数量(列值非NULL)
根据实际情况,可反向统计(仅限MyISAM引擎)
select count(*) from article where id > 5;
-- 改写为如下,利用MyISAM引擎的全表统计的特性,减少扫描的行数,前提是两种查询方式扫描行数有差距
select (select count(*) from article) - count(*) from article where id <= 5;
同一查询返回同一列不同值的统计
-- 查询文章表中分类分别为1和3的文章总数,以下两种方式皆可
select sum(cid = 1), sum(cid = 3) from article;
select count(if(cid = 1, 1, null)), count(if(cid = 3, 1, null)) from article;
使用近似值
有些统计查询对精度要求不高,可近似的使用 explain
的扫描行数
ON
, USING
子句中的列上有索引,一般来说除非有其他理由,否则只需要在关联顺序中的第二个表的相应列上创建索引,没有用到的索引只会带来负担。GROUP BY
和 ORDER BY
中的表达式只涉及一个表中的列,这样才有可能使用索引优化尽量使用关联查询代替,>= 5.6 版本的则无所谓。
分组查询的时候,通常使用标识列(id)分组效果更好
如果开启了 ONLY_FULL_GROUP_BY
,如果分组列不是标识列(id),那 select
后面跟随的列必须是分组列中的或者是聚合函数计算过的。
如果没有通过ORDER BY
显式的指定排序列,结果集会按照分组的字段进行排序,如果不关心结果集的顺序,而这种默认排序需要文件排序,可以用ORDER BY NULL
让 MySQL不再进行文件排序。
延后关联,子查询使用覆盖索引
select * from people where sex=1 limit 10000, 20;
-- 改写为如下
select * from people inner join (
select id from people where sex=1 limit 10000, 20
) using (id);
记录边界值,通过边界值做条件
汇总表、冗余表等手段
尽量将所有条件下推到每一个UNION
子查询,临时表中不能很好的使用索引
除非确实需要服务器消除重复行,否则一定使用 UNION ALL
,不携带 ALL
关键字会执行去重操作,成本很高
使用自定义变量的限制
:=
(等同于 =
) ,优先级非常低,赋值表达式应该使用明确的括号-- 使用自定义变量计算排名
set @curr_cnt := 0, @prev_cnt := 0, @rank := 0;
select actor_id ,
@curr_cnt := cnt as cnt,
@rank := if (@prev_cnt <> @curr_cnt, @rank + 1, @rank),
@prev_cnt := @curr_cnt as dummy from
(
select actor_id, count(*) as cnt
from actor_film
group by actor_id
order by cnt desc
limit 10
) as t1
-- 编写偷懒的UNION
select id from users where id = 123
union all
select id from users_archived where id = 123;
-- 正常的union语句 如果第一条sql查询出结果,第二条也会查询,此sql适用于只需要有结果,而不是需要全部的结果
select greatest(@found := -1, id) as id , 'users' as t1 from users where id = 1
union all
select id , 'users_archived' from users_archived where id = 1 and @found is null
union all
select 1, 'reset' from dual where (@found:=null) is not null