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

含空结果子查询的SQL语句执行缓慢原因咨询

问题分析:为何包含空结果子查询的OR条件导致全表扫描变慢?

首先,我们来拆解你的问题核心:单独执行子查询速度极快且返回空,替换子查询为NULL后主查询也瞬间完成,但原查询却走了全表扫描,耗时21ms。这背后是PostgreSQL查询优化器对OR条件结合子查询的处理逻辑导致的。

执行计划对比关键差异

先看原查询的执行计划:

Aggregate (cost=6583.49..6583.50 rows=1 width=32) (actual time=21.702..21.702 rows=1 loops=1)
  -> Seq Scan on adverts (cost=6.31..6533.88 rows=19842 width=4) (actual time=0.462..21.684 rows=44 loops=1)
        Filter: ((discarded_at IS NULL) AND visible AND ((type)::text = 'Businesses::Restaurant'::text) AND ((city_location_id = 56) OR (hashed SubPlan 1)))
        Rows Removed by Filter: 46217

这里优化器选择了全表扫描(Seq Scan),扫描了4万多行数据,只筛选出44行,效率极低。

再看替换子查询为NULL后的执行计划:

Aggregate (cost=162.66..162.67 rows=1 width=32) (actual time=0.309..0.310 rows=1 loops=1)
  -> Bitmap Heap Scan on adverts (cost=4.72..162.55 rows=42 width=4) (actual time=0.082..0.278 rows=44 loops=1)
        Recheck Cond: (city_location_id = 56)
        Filter: ((discarded_at IS NULL) AND visible AND ((type)::text = 'Businesses::Restaurant'::text))
        Heap Blocks: exact=42
        -> Bitmap Index Scan on index_adverts_on_city_location_id_and_visible (cost=0.00..4.71 rows=42 width=0) (actual time=0.043..0.044 rows=44 loops=1)
              Index Cond: ((city_location_id = 56) AND (visible = true))

这里优化器识别到city_location_id IN (NULL)永远为false,所以条件简化为city_location_id=56,直接使用了index_adverts_on_city_location_id_and_visible索引,效率大幅提升。

为什么会出现这种差异?

问题出在PostgreSQL优化器的规划阶段评估逻辑:

  • 当你使用OR条件结合子查询时,优化器默认不会提前执行子查询来判断结果是否为空(即使子查询是不相关的)。它会假设子查询可能返回数据,因此认为OR条件需要匹配更多行,最终选择了全表扫描而非索引扫描。
  • 而当你直接写IN (NULL)时,优化器能立刻识别这个条件恒为假,从而简化整个WHERE子句,自然会选择最优的索引路径。

解决方法

你可以通过以下几种方式帮助优化器做出正确的选择:

1. 用CTE提前执行子查询

CTE会强制优化器先执行子查询,获取空结果后,主查询的OR条件会被自动简化:

WITH subquery_result AS (
    SELECT "city_locations"."id" FROM "city_locations" 
    WHERE "city_locations"."type" IN ('Arrondissement') 
    AND "city_locations"."arrondissement_city_id" = 56
)
SELECT AVG("adverts"."price") 
FROM "adverts" 
WHERE "adverts"."type" IN ('Businesses::Restaurant') 
AND "adverts"."discarded_at" IS NULL 
AND "adverts"."visible" = true 
AND (
    "adverts"."city_location_id" = 56 
    OR "adverts"."city_location_id" IN (SELECT id FROM subquery_result)
);

2. 用UNION ALL拆分OR条件

将OR拆分为两个独立的查询,让每个部分都能利用索引扫描,再合并结果:

SELECT AVG(price) FROM (
    SELECT "adverts"."price" 
    FROM "adverts" 
    WHERE "adverts"."type" IN ('Businesses::Restaurant') 
    AND "adverts"."discarded_at" IS NULL 
    AND "adverts"."visible" = true 
    AND "adverts"."city_location_id" = 56
    UNION ALL
    SELECT "adverts"."price" 
    FROM "adverts" 
    WHERE "adverts"."type" IN ('Businesses::Restaurant') 
    AND "adverts"."discarded_at" IS NULL 
    AND "adverts"."visible" = true 
    AND "adverts"."city_location_id" IN (
        SELECT "city_locations"."id" FROM "city_locations" 
        WHERE "city_locations"."type" IN ('Arrondissement') 
        AND "city_locations"."arrondissement_city_id" = 56
    )
) AS combined;

3. 用物化子查询强制优化器评估结果

通过内层子查询强制优化器先获取子查询的空结果,再优化主查询:

SELECT AVG("adverts"."price") 
FROM "adverts" 
WHERE "adverts"."type" IN ('Businesses::Restaurant') 
AND "adverts"."discarded_at" IS NULL 
AND "adverts"."visible" = true 
AND (
    "adverts"."city_location_id" = 56 
    OR "adverts"."city_location_id" IN (
        SELECT id FROM (
            SELECT "city_locations"."id" FROM "city_locations" 
            WHERE "city_locations"."type" IN ('Arrondissement') 
            AND "city_locations"."arrondissement_city_id" = 56
        ) AS mat_subquery
    )
);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:01:44