【问题标题】:Postgres not using index with LEFT JOIN or with OR in wherePostgres 不使用带有 LEFT JOIN 或 OR in where 的索引
【发布时间】:2024-04-21 02:00:02
【问题描述】:

我遇到了一些与我有些相关的问题(例如12)。但他们都没有真正回答这个问题。

基本上我的问题是 Postgres 拒绝在某些看似简单的查询中使用索引。我设法将我的复杂查询归结为以下一些示例:

-- SETUP

CREATE TEMP TABLE test ( 
    id BIGSERIAL PRIMARY KEY,
    DATA INT 
);

INSERT INTO test(DATA)
SELECT RANDOM() FROM GENERATE_SERIES(1, 100000);

-- just in case the stats are outdated
VACUUM ANALYSE test;

-- QUERIES

-- correct but does a full table scan
EXPLAIN SELECT
    id,
    DATA
FROM
    test
WHERE
    (FALSE
    OR id IN (
    SELECT
        UNNEST(ARRAY[100]) 
    ));



-- simpler than above but still does a table scan
EXPLAIN SELECT
    id,
    DATA
FROM
    test
WHERE
    FALSE
    OR id IN (SELECT 100);

解释的输出:

QUERY PLAN                                                
----------------------------------------------------------
Seq Scan on test  (cost=0.01..1791.01 rows=50000 width=12)
  Filter: (hashed SubPlan 1)                              
  SubPlan 1                                               
    ->  Result  (cost=0.00..0.01 rows=1 width=4)          
-- uses PRIMARY KEY index correctly but limited to 1 ID
-- NOTE that it uses "=" instead of "IN"
EXPLAIN SELECT
    id,
    DATA
FROM
    test
WHERE
    FALSE
    OR id = (SELECT 100 LIMIT 1);
QUERY PLAN                                                           
---------------------------------------------------------------------
Index Scan using test_pkey on test  (cost=0.30..8.32 rows=1 width=12)
  Index Cond: (id = $0)                                              
  InitPlan 1 (returns $0)                                            
    ->  Limit  (cost=0.00..0.01 rows=1 width=4)                      
          ->  Result  (cost=0.00..0.01 rows=1 width=4)               

正确使用 PRIMARY KEY 索引,但我需要 LEFT JOIN 而不是 INNER JOIN。 -- 见下一个例子。

EXPLAIN
WITH ids AS (
SELECT
    UNNEST(ARRAY[100]) AS ids )
SELECT
    id,
    DATA
FROM
    test
INNER JOIN ids ON
    id = ids.ids;
QUERY PLAN                                                                 
---------------------------------------------------------------------------
Nested Loop  (cost=0.29..8.34 rows=1 width=12)                             
  ->  ProjectSet  (cost=0.00..0.02 rows=1 width=4)                         
        ->  Result  (cost=0.00..0.01 rows=1 width=0)                       
  ->  Index Scan using test_pkey on test  (cost=0.29..8.31 rows=1 width=12)
        Index Cond: (id = (unnest('{100}'::integer[])))                    

我需要一个左连接,但这会进行全表扫描

EXPLAIN WITH ids AS (
  SELECT 100 AS id 
)
SELECT
    test.id,
    DATA
FROM
    test
LEFT JOIN ids ON
    test.id = ids.id;
QUERY PLAN                                                 
-----------------------------------------------------------
Seq Scan on test  (cost=0.00..1541.00 rows=100000 width=12)

所以我的问题:

  1. 为什么FALSE OR ... IN ... 阻止使用索引,而FALSE OR ... = ... 没问题?
  2. 为什么在我的示例中 INNER JOIN 使用索引而 LEFT JOIN 没有?
  3. 是否有某种方法可以在不使用 UNION 或复制整个查询的情况下重写第一个查询? (我的实际查询要复杂得多)

【问题讨论】:

  • 您必须使用UNION 重写查询。 OR is a performance killer.
  • 左连接不限制从表 test 返回的行,因此需要该表中的所有行,而 Seq Scan 是最有效的方法。
  • @LaurenzAlbe 您发布的链接非常有帮助。它提到了一种使用 ANY 重写 OR IN 部分的更有效方法

标签: sql postgresql performance indexing sql-execution-plan


【解决方案1】:
  1. 将指定的选择移到表格附近,例如:

    SELECT
      id, DATA
    FROM
      test, (SELECT UNNEST(ARRAY[100]) as id1) as sel
    WHERE
      (FALSE OR id = sel.id1);
    
  2. 使用 UNION ALL 的中断或子句如下:

    SELECT
      id, DATA
    FROM
      test
    WHERE
      FALSE -- first or predicate
    UNION ALL
    SELECT
      id, DATA
    FROM
      test
    WHERE id in (select UNNEST(ARRAY[100]);
    

【讨论】:

  • WHERE id in (select UNNEST(ARRAY[100]); 最好写成where id = any(array[...])
  • @a_horse_with_no_name 是的,这就是我在 LaurenzAlbe 发布的链接中找到的内容
  • 解决方案 1 似乎等同于我认为的 INNER JOIN,如果数组为空或 null。
  • @a_horse_with_no,该构造适用于原始选择 ...(.... where (false or id = any(array[...]) ... 但我认为选择是写为另一个多行选择的样本..... :)