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

为何外层查询加DISTINCT后,含子查询的SQLite查询耗时暴增3万倍?

问题原因分析与解决方案

核心根源:执行顺序的差异导致子查询执行次数暴增

SQLite处理带外层DISTINCT的查询时,执行逻辑和你预期的完全相反——它会先对主表每一行执行子查询,再对结果集去重,而非先去重name再执行子查询。这直接导致子查询被执行15000次(对应主表所有行),而非预期的249次(对应唯一name数量),这就是性能暴跌的关键。


分情况拆解执行逻辑

1. 移除外层DISTINCT时的高效执行

当没有外层DISTINCT时,SQLite的优化器能精准识别:两个子查询的结果仅依赖name字段,相同name的行对应的子查询结果完全一致。

  • 优化器会自动启用相关子查询缓存:同一个name值的子查询只执行一次,后续重复出现的name直接复用缓存结果,实际子查询仅执行249次。
  • 甚至会直接将查询重写为等价的GROUP BY逻辑,只需扫描一次表就能完成所有聚合计算,这也是耗时仅1-3ms的核心原因。

2. 保留外层DISTINCT时的低效执行

加上外层DISTINCT后,SQLite优化器没有触发上述重写,而是按直观路径执行:

  1. 扫描主表s1的全部15000行;
  2. 对每一行执行两个子查询(计算section_count和document_count);
  3. 将15000行的结果(包含大量重复的name和相同聚合值)去重,最终得到249行。
  • 这里的致命开销是:子查询被执行了15000×2=30000次。尤其是第二个带DISTINCT的子查询,每次执行都需要扫描对应name的所有行、去重计数,重复执行15000次的累积开销直接拉满耗时。

3. 单独SELECT DISTINCT name快的原因

单独的去重查询仅需利用name索引快速遍历并去重,不需要执行任何子查询,所以耗时仅1-3ms。但这个高效逻辑无法和带子女查询的SELECT DISTINCT复用,因为优化器没有关联两者的执行计划。


验证方法

可以用EXPLAIN QUERY PLAN查看两种查询的执行计划:

  • 无DISTINCT的查询会显示SCAN sections + USE TEMP B-TREE FOR GROUP BY(或类似分组逻辑);
  • 有DISTINCT的查询会显示SCAN sections s1,然后重复执行CORRELATED SCAN sections s2和CORRELATED SCAN sections s3共15000次,最后USE TEMP B-TREE FOR DISTINCT。

最优解决方案

直接改用GROUP BY替代外层DISTINCT,这是最贴合业务需求且性能最优的写法:

SELECT name,
    COUNT(name) AS section_count,
    COUNT(DISTINCT document_key) AS document_count
FROM sections
GROUP BY name

这个写法让SQLite明确按name分组聚合,只需扫描一次表就能完成计算,性能和无DISTINCT的查询一致,同时自动得到唯一的name结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 23:12:03