You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL SELECT语句执行缓慢原因排查求助

解决MySQL SELECT语句执行缓慢的问题

兄弟,我一眼就看到几个导致你查询慢到超时的关键问题,尤其是逻辑运算符优先级和索引使用的坑,咱们一步步拆解:

核心问题1:WHERE子句的逻辑优先级混乱

这是最致命的问题!你的查询里混用了AND和OR,但OR的优先级比AND低,导致MySQL把你的条件解析成了:

(所有AND条件) OR (f.id = 9779432)

也就是说,只要family ID是9779432,不管它是否满足companyid=1170、交易条件等,都会被纳入结果。这种模糊的逻辑会让优化器无法有效利用索引——因为它要同时处理两种完全不同的过滤规则,大概率会触发全表扫描。

修复方案:用括号明确分组逻辑,比如如果你希望OR的两端都满足交易相关条件,应该写成:

WHERE 
  (f.companyid = 1170 AND f.status = 'A' AND f.id = t.familyid AND f.id = c.familyid AND t.transactiontype = 'P' AND t.taxdeductible = 'Y' AND YEAR(t.date) = 2017)
  OR 
  (f.id = 9779432 AND f.id = t.familyid AND f.id = c.familyid AND t.transactiontype = 'P' AND t.taxdeductible = 'Y' AND YEAR(t.date) = 2017)

如果特定family ID不需要满足其他条件,就把OR的后半部分单独括起来,但这样可能还是会影响索引效率,后面会给替代方案。

核心问题2:非SARGable条件导致索引失效

你用了YEAR(t.date) = 2017,这个函数包装了索引列date,会让MySQL无法直接使用transactions.date的索引——它必须逐行计算每个日期的年份,相当于全表扫描符合其他条件的交易记录。而你的transactions表有98万行,这绝对是性能杀手。

修复方案:把函数条件改成范围查询,让它变成SARGable(可以利用索引):

t.date BETWEEN '2017-01-01' AND '2017-12-31'

如果你的date字段包含时间,就改成'2017-12-31 23:59:59'来覆盖全年所有时间点。

核心问题3:单字段索引无法满足多条件查询

你创建的都是单字段索引,但你的查询是多条件组合过滤+关联,单字段索引的效率极低,优化器可能直接放弃使用,选择全表扫描。比如:

  • transactions表需要同时过滤familyid、transactiontype、taxdeductible、date,单字段索引无法覆盖这些条件的组合
  • families表需要过滤companyid+status,还要关联id、排序name,单字段索引做不到

修复方案:创建针对性的复合索引:

  1. transactions表:创建复合索引(familyid, transactiontype, taxdeductible, date)——把关联字段放最前面,然后是过滤字段,最后是范围字段,这样可以覆盖所有交易相关的过滤条件,避免回表
  2. families表:创建复合索引(companyid, status, id, name)——覆盖过滤条件、关联字段和排序字段,消除Using filesort
  3. children表:创建复合索引(familyid, firstname)——覆盖关联字段和需要聚合的firstname,避免回表

优化后的完整查询语句

改用显式JOIN语法(比隐式逗号JOIN更清晰,优化器更容易生成好的执行计划),加上所有修复点:

SELECT f.id, f.name, GROUP_CONCAT(DISTINCT c.firstname) AS children
FROM families f
INNER JOIN children c ON f.id = c.familyid
INNER JOIN transactions t ON f.id = t.familyid
WHERE 
  (f.companyid = 1170 AND f.status = 'A' AND t.transactiontype = 'P' AND t.taxdeductible = 'Y' AND t.date BETWEEN '2017-01-01' AND '2017-12-31')
  OR 
  (f.id = 9779432 AND t.transactiontype = 'P' AND t.taxdeductible = 'Y' AND t.date BETWEEN '2017-01-01' AND '2017-12-31')
GROUP BY f.id, f.name -- 加上f.name符合ONLY_FULL_GROUP_BY模式,避免语法错误
ORDER BY f.name;

额外的性能优化建议

如果OR的两个分支逻辑差异很大,优化器还是可能无法高效利用索引,这时候可以把查询拆成两个独立查询用UNION ALL合并:

-- 第一个分支:符合公司条件的家庭
SELECT f.id, f.name, GROUP_CONCAT(DISTINCT c.firstname) AS children
FROM families f
INNER JOIN children c ON f.id = c.familyid
INNER JOIN transactions t ON f.id = t.familyid
WHERE f.companyid = 1170 AND f.status = 'A' AND t.transactiontype = 'P' AND t.taxdeductible = 'Y' AND t.date BETWEEN '2017-01-01' AND '2017-12-31'
GROUP BY f.id, f.name

UNION ALL

-- 第二个分支:特定家庭
SELECT f.id, f.name, GROUP_CONCAT(DISTINCT c.firstname) AS children
FROM families f
INNER JOIN children c ON f.id = c.familyid
INNER JOIN transactions t ON f.id = t.familyid
WHERE f.id = 9779432 AND t.transactiontype = 'P' AND t.taxdeductible = 'Y' AND t.date BETWEEN '2017-01-01' AND '2017-12-31'
GROUP BY f.id, f.name

-- 最后统一排序
ORDER BY name;

这样每个子查询都能完美利用我们创建的复合索引,合并结果的开销远低于单个带OR的查询。

验证优化效果

执行EXPLAIN命令查看执行计划,重点看:

  • type列:应该是ref或range,而不是ALL(全表扫描)
  • key列:应该显示我们创建的复合索引
  • Extra列:不要出现Using filesort或Using temporary(除非无法避免)

内容的提问来源于stack exchange,提问作者Vincent

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:39:44