MySQL左连接查询忽略start_time索引致性能低下问题排查
咱们一步步拆解这个问题,先搞懂为啥会出现索引失效,再给你可行的解决思路:
一、为什么左连接会跳过start_time索引?
你的场景很典型:call_session有50多万行数据,而continue_reason只有1行。MySQL的查询优化器是靠成本估算来选择执行计划的,这里它犯了个“判断偏差”:
当你用LEFT JOIN时,优化器看到continue_reason数据量极小,会觉得:「先扫这个小表(成本几乎为0),再用它的id去匹配call_session的continue_reason_id,最后排序取最新的一条」——但它没算明白,call_session有50w行,排序这么多数据的成本,比直接走start_time索引取最后一条的成本高太多了!
至于你说FORCE INDEX没用?那是因为优化器觉得就算强制走start_time索引,整体执行成本还是比它选的路径高(这是它的估算错误),所以直接忽略了你的强制指令。
而你的子查询写法为啥正常?因为那是相关子查询:优化器会先通过start_time索引快速拿到call_session里最新的1条数据,再去continue_reason里查对应的name,全程只需要两次小查询,成本低到离谱,优化器当然会选这条最优路径。
二、可行的解决办法
1. 继续使用相关子查询(已验证有效)
你现在用的子查询写法已经是最优方案之一了,逻辑清晰,执行效率拉满,完全没必要改。
2. 重写左连接语句,引导优化器走索引
如果一定要用左连接的语法,可以把取最新数据的逻辑提前,用子查询先锁定目标行,再关联小表:
SELECT cs.id, cr.name FROM ( -- 先通过索引拿到最新的1条call_session数据 SELECT id, continue_reason_id FROM call_session ORDER BY start_time DESC LIMIT 1 ) cs LEFT JOIN continue_reason cr ON cr.id = cs.continue_reason_id;
这种写法和子查询逻辑一致,内层子查询必然走start_time索引,外层关联小表也没成本,优化器一眼就能看出来这是最优路径。
3. 更新表统计信息
MySQL优化器的成本估算依赖表的统计信息,如果call_session的统计信息过时了,优化器就会算错成本。你可以执行这条语句更新统计信息:
ANALYZE TABLE call_session;
更新完之后再试原来的左连接写法,说不定优化器就能正确判断,自动走start_time索引了。
4. 调整优化器参数(谨慎使用)
如果上面的方法都不行,可以尝试调整优化器的参数,比如关闭join_cache_level或者调整成本模型,但这是全局设置,可能影响其他查询的执行计划,除非万不得已不推荐用。
最后补充
其实本质上就是优化器的成本估算偏差——因为小表的存在,它误以为先连接再排序更划算,但忽略了LIMIT 1这个关键条件,走索引只需要取1条数据,成本比全表处理低几个数量级。
内容的提问来源于stack exchange,提问作者yeya

