MySQL 5.6中子查询嵌套关联查询后性能骤降的问题求助
解决MySQL 5.6中子查询/视图加Limit性能暴跌的问题
这问题我太熟悉了,MySQL 5.6对派生表的优化确实有不少局限,尤其是当你需要把复杂联查询包装成视图供其他应用调用时,很容易碰到这种“套个壳就变慢”的情况。先给你拆解下原因,再给几个可行的解决方案:
为什么会变慢?
从你给出的EXPLAIN结果来看,核心问题是MySQL 5.6默认不会合并派生表:
- 当你直接写
select * from A join B ... limit 0,1000时,优化器会把limit逻辑推到各个join步骤中,提前终止数据扫描,只取前1000条结果,所以快。 - 但套上子查询(视图本质就是封装的子查询)后,优化器会先执行内层的完整联查询,生成一个包含150多万行的临时派生表
<derived2>,然后再在外层对这个无索引的临时表做全表扫描(type: ALL),还要额外做临时表和文件排序,自然就慢到离谱。
可行的解决方案
1. 开启派生表合并优化(最简单的方法)
MySQL 5.6提供了derived_merge优化开关,开启后优化器会尝试把派生表合并到外层查询中,让执行计划和直接写联查询一致。你可以通过以下方式开启:
- 会话级别(仅当前连接生效):
SET optimizer_switch='derived_merge=on'; - 全局级别(重启后依然生效,需要权限):
注意:如果你的子查询包含SET GLOBAL optimizer_switch='derived_merge=on';DISTINCT、GROUP BY、LIMIT等特殊逻辑,优化器可能无法合并,但你的场景是简单联查询套子查询,大概率可以生效。
2. 重写SQL为延迟关联(Lazy Join)
如果派生表无法合并,那我们可以手动把limit的逻辑提前到内层,减少临时表的数据量。比如把原视图的SQL改成:
CREATE VIEW your_view AS SELECT A.*, B.*, ... -- 这里写你需要的所有字段 FROM ( -- 内层只查询关联主键,先做limit过滤 SELECT A.aid FROM A JOIN B ON A.aid = B.aid ... -- 原联查询逻辑 ) tmp JOIN A ON tmp.aid = A.aid JOIN B ON tmp.aid = B.aid ... -- 其他关联表 LIMIT 0, 1000; -- 或留空让应用在外层自行添加limit
这种写法的核心是:先通过小范围的主键查询拿到目标数据的标识,再关联其他表获取完整字段,避免生成百万级的临时表,自然就能提升性能。
3. 确保原表的关联字段有合适的索引
不管用哪种方法,原表的关联字段(比如A.aid、B.aid)一定要有索引!如果这些字段没有索引,内层的联查询本身就会很慢,即使优化器做了合并,性能也会打折扣。你可以用以下语句检查并创建索引:
-- 检查A表的aid索引 SHOW INDEX FROM A WHERE Key_name = 'aid_idx'; -- 如果没有,创建索引 CREATE INDEX aid_idx ON A(aid); -- 对B表执行同样的操作 CREATE INDEX aid_idx ON B(aid);
4. 改用临时表+视图的组合(适合非实时数据场景)
如果以上方法都不生效,你可以考虑用存储过程定期生成带索引的临时表,然后让视图基于这个临时表查询。比如:
-- 创建存储过程,定期刷新临时表 DELIMITER // CREATE PROCEDURE refresh_temp_data() BEGIN DROP TABLE IF EXISTS temp_join_result; CREATE TABLE temp_join_result ENGINE=InnoDB AS SELECT * FROM A JOIN B ON A.aid = B.aid ...; -- 原联查询逻辑 -- 给临时表加索引 CREATE INDEX aid_idx ON temp_join_result(aid); END // DELIMITER ; -- 然后创建视图基于临时表 CREATE VIEW your_view AS SELECT * FROM temp_join_result;
之后定期调用存储过程刷新临时表即可,这样应用查询视图时就会基于有索引的临时表,性能会大幅提升。不过这种方法适合数据不需要实时更新的场景。
内容的提问来源于stack exchange,提问作者Steven Chou




