优化数据访问
-
只查询需要的数据
-
永远不要使用
select *
除了count(*)
-
用缓存等手段减少重复数据访问
-
关注查询的响应时间,扫描行数,返回行数
如果发现某个查询扫描行数远大于返回行数,可以从以下角度优化
- 使用覆盖索引
- 改变表库结构,使用汇总表等
- 改进查询方式
应用where的三种方式,性能从高到低
- 在索引中使用where条件过滤不匹配的内容,存储引擎层完成;
- 使用索引覆盖扫描(Extra列中出现 Using Index),直接从索引中过滤并返回结果,服务层执行,但是无须回表操作;
- 从数据表中返回数据,然后过滤不满足的记录(Extra 中出现 Using Where) ,服务层完成。
重构查询
一个复杂查询还是多个简单查询
根据实际情况选择,需要权衡
切分查询
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);
分解关联查询的优势:
- 让缓存的效率更高,如果tag表已被应用程序缓存,则减少一次查询
- 查询分解后,单个查询可以减少锁竞争
- 在应用层做管理,可以更容易对数据库进行拆分,更容易做到高性能和可扩展
- 查询本身的效率可能会有所提升
- 可以减少冗余记录的查询,应用层做关联,所有的数据只会查询一次,数据库中做关联可能会重复扫描一部分数据,可能会减少内存占用
优化特定类型查询
优化count() 查询
作用: 统计行数,统计某列的数量(列值非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 版本的则无所谓。
优化GROUP BY 和 DISTINCT
分组查询的时候,通常使用标识列(id)分组效果更好
如果开启了 ONLY_FULL_GROUP_BY
,如果分组列不是标识列(id),那 select
后面跟随的列必须是分组列中的或者是聚合函数计算过的。
如果没有通过ORDER BY
显式的指定排序列,结果集会按照分组的字段进行排序,如果不关心结果集的顺序,而这种默认排序需要文件排序,可以用ORDER BY NULL
让 MySQL不再进行文件排序。
优化 LIMIT
-
延后关联,子查询使用覆盖索引
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
子查询,临时表中不能很好的使用索引
除非确实需要服务器消除重复行,否则一定使用 UNION ALL
,不携带 ALL
关键字会执行去重操作,成本很高
使用自定义变量
使用自定义变量的限制
- 使用自定义变量的查询,无法使用查询缓存
- 不能在使用常量或者标识符的地方使用自定义变量,如表名,limit子句中
- 自定义变量生命周期是一个连接内有效,无法夸连接通信
- MySQL优化器可能会在某些情况下将这些变量优化掉,这可能导致代码不按预想的方式运行
- 赋值的顺序和赋值的时间点不是固定的,这依赖于优化器的决定
- 赋值符号
:=
(等同于=
) ,优先级非常低,赋值表达式应该使用明确的括号 - 使用未定义的变量不会产生任何语法错误,如果没有意识到这一点,非常容易犯错
-- 使用自定义变量计算排名
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