两个单独执行快速的SQL查询合并后运行极慢的问题排查
为什么合并查询变慢?以及优化方案
咱们先从你的执行计划入手分析问题核心:
你的原始查询(不管用IN还是INNER JOIN),MySQL的执行逻辑是这样的:
- 先从
joinedgroupstable里通过userid=1快速取出该用户的所有群组(执行计划显示是2个群组) - 把这2个群组和
posttable做关联,捞取所有属于这些群组的帖子(执行计划预估每个群组匹配60条,实际可能更多,毕竟posttable有5万多行) - 把所有匹配到的帖子放到临时表里做全局排序(
ORDER BY p.ID DESC),最后取前25条
这就是慢的关键:你需要先把所有符合条件的帖子都捞出来再排序——哪怕你只需要25条最新的。而单独执行SELECT * FROM posttable ORDER BY ID DESC LIMIT 25之所以快,是因为ID是主键,主键本身是有序的,MySQL直接从主键索引的末尾取25条就行,完全不需要额外排序操作。
优化方案:优先取最新帖子,再筛选群组
既然目标是最新的25条帖子,我们可以反过来操作:先从posttable里取足够多的最新帖子(比如1000条,远大于25),再筛选这些帖子是否属于用户1的群组,最后排序取25条。这样能避免对大量数据做排序。
方案1:子查询预取最新帖子
SELECT p.* FROM ( -- 利用主键索引快速获取最新的1000条帖子 SELECT * FROM posttable ORDER BY id DESC LIMIT 1000 ) p -- 筛选帖子所属群组是否在用户的群组列表中 WHERE p.groupid IN (SELECT groupid FROM joinedgroupstable WHERE userid=1) ORDER BY p.id DESC LIMIT 25;
这里的LIMIT 1000可以根据实际情况调整——只要保证用户1的群组里的最新25条帖子都在这1000条里就行,这个子查询几乎是瞬间完成的。
方案2:用EXISTS+强制主键索引引导执行计划
你也可以用EXISTS关联,同时强制MySQL优先扫描posttable的主键索引,从最新的帖子开始逐个检查是否属于用户的群组,找到25条就停止:
SELECT p.* FROM posttable p FORCE INDEX (PRIMARY) WHERE EXISTS ( SELECT 1 FROM joinedgroupstable jg WHERE jg.userid = 1 AND jg.groupid = p.groupid ) ORDER BY p.id DESC LIMIT 25;
这个写法让MySQL从posttable的最后一条记录开始,每条都通过joinedgroupstable的userid+groupid唯一索引检查匹配,一旦凑够25条就终止查询,不需要处理所有符合条件的帖子。
验证优化效果
你可以用EXPLAIN查看优化后的执行计划,应该不会再出现Using temporary; Using filesort,而且rows列的数值会大幅降低,查询耗时会降到毫秒级。
内容的提问来源于stack exchange,提问作者Waseem
相关产品推荐
相关产品推荐

