WHERE 子句中不同的比较列导致意外的速度变慢

问题描述 投票:0回答:1

(以下所有标识符均已匿名。)

我在 Postgres 中有以下查询:

SELECT id, dt
FROM table1 t1
WHERE status is not null
    AND (
        (NOT EXISTS (SELECT 1 FROM table2 t2 WHERE t2.big_int_id = t1.big_int_id and t2.count is not null)
            AND t1.dt > NOW() - INTERVAL '15 minutes'
        ) OR
        (NOT EXISTS
            (SELECT 1 FROM table3 t3
                JOIN table2 t2 ON t2.another_id = t3.another_id AND
                    t2.big_int_id = t3.big_int_id
            )
            AND t1.dt BETWEEN NOW() - INTERVAL '15 minutes' AND NOW()
        )
    )

执行大约需要1秒。

然后我改变了:

(SELECT 1 FROM table2 t2 WHERE t2.big_int_id = t1.big_int_id and t2.count is not null)

至:

(SELECT 1 FROM table2 t2 WHERE t2.text_id = t1.text_id and t2.count is not null)

现在查询需要 10 分钟以上才能执行。我对 SQL 的经验有限,不知道为什么这会导致速度变慢。

从比较

big_int_id
(大整数类型的列)到
text_id
text
类型的列,但文本的形式为
"XYZ-#####"
,其中 ##### 表示某个整数值)的变化1 到 10 亿之间)似乎会导致 2 个变化:

  1. 比较

    text
    而不是
    bigint
    - 不知道会慢多少?

  2. t1
    中,每个不同的
    t1.big_int_id
    恰好有 2 行,但每个不同的
    t2.text_id
    恰好有 1 行,并且 98% 的行将
    text_id
    设为
    null
    (尽管可能会发生变化)将来)。我认为从
    SELECT
    语句创建的笛卡尔积可能会大很多?

未修改查询的查询计划:

Seq Scan on table t1  (cost=0.00..7362242.49 rows=1 width=24)
  Filter: ((statis is null) AND (((NOT (SubPlan 1)) AND (dt > (now() - '00:15:00'::interval))) OR ((NOT (SubPlan 2)) AND (dt >= (now() - '00:15:0
0'::interval)) AND (dt <= now()))))
  SubPlan 1
    ->  Index Scan using idx_t2_QRID on table2 t2  (cost=0.56..80.66 rows=17 width=0)
          Index Cond: (big_int_id = t1.big_int_id)
          Filter: (count IS NOT NULL)
  SubPlan 2
    ->  Nested Loop  (cost=0.99..239.84 rows=2 width=0)
          ->  Index Only Scan using idx_t2_QRID on table2 q_1  (cost=0.56..22.64 rows=38 width=8)
                Index Cond: (big_int_id = t1.big_int_id)
          ->  Index Only Scan using idx_table3_qid_rt on table3 t3  (cost=0.42..5.71 rows=1 width=8)
                Index Cond: (big_int_id = q_1.big_int_id)

修改查询的查询计划:

Seq Scan on table t1  (cost=0.00..2318157244.23 rows=1 width=24)
  Filter: ((statis is null) AND (((NOT (SubPlan 1)) AND (dt > (now() - '00:15:00'::interval))) OR ((NOT (SubPlan 2)) AND (dt >= (now() - '00:15:0
0'::interval)) AND (dt <= now()))))
  SubPlan 1
    ->  Seq Scan on table2 t2  (cost=0.00..671602.70 rows=17 width=0)
          Filter: ((count IS NOT NULL) AND (text_id = t1.text_id))
  SubPlan 2
    ->  Nested Loop  (cost=0.99..239.84 rows=2 width=0)
          ->  Index Only Scan using idx_t2_QRID on table2 q_1  (cost=0.56..22.64 rows=38 width=8)
                Index Cond: (big_int_id = t1.big_int_id)
          ->  Index Only Scan using idx_table3_qid_rt on table3 t3  (cost=0.42..5.71 rows=1 width=8)
                Index Cond: (big_int_id = q_1.big_int_id)

t1
的索引:

CREATE INDEX t1_text_id     ON t1 (text_id)
CREATE INDEX t1_id          ON t1 (id)
CREATE UNIQUE INDEX t1_pkey ON t1 (id10)

t2
的索引:

CREATE UNIQUE INDEX idx_t2_text_id ON t2 (text_id)
CREATE INDEX idx_t2_QRID           ON t2 (big_int_id, id5)
CREATE UNIQUE INDEX t2_pkey        ON t2 (id5)

t3
的索引:

CREATE INDEX idx_table3_qid_rt  ON t3 (big_int_id, response_type)
CREATE UNIQUE INDEX table3_pkey ON t3 (big_int_id)
sql postgresql query-optimization postgresql-performance
1个回答
0
投票

第一步,我将查询整理为:

SELECT t1.id, t1.dt
FROM   table1 t1
WHERE  t1.status IS NOT NULL
AND    t1.dt > now() - interval '15 minutes'   
AND   (
       NOT EXISTS (SELECT FROM table2 t2 WHERE t2.big_int_id = t1.big_int_id AND t2.count IS NOT NULL)
--                                       WHERE t2.text_id = t1.text_id
    OR NOT EXISTS (SELECT FROM table2 t2 JOIN table3 t3 USING (another_id, big_int_id)
       AND t1.dt <= now()
      )

最重要的是,您可以从

t1.dt > now() - interval '15 minutes'
分支中提取过滤器
OR
。这应该会使计划更简单/查询本身更快一些。

此外,您的原始版本中存在一个隐藏的极端情况问题。

BETWEEN
包括下限(和上限),而您的另一个分支具有用于下限的运算符
>
。通常,您希望避免使用时间戳值
BETWEEN

text
bigint
的原始比较通常会稍微贵一些,原因有两个:(1)
text
通常占用更多空间。 (2)
text
通常涉及排序规则。一开始就将整数包装在
text
中,这听起来像是一个设计错误。但这是一个旁注。不是主要问题。

主要问题是在桌子

Index Scan
上从
Seq Scan
切换到
t2
t2(text_id)
上有索引,但没有使用,这可能有多种原因。也许它与我修改后的查询一起使用。也许只是跑步的问题

ANALYZE t2;

或者:

REINDEX INDEX idx_t2_text_id;

或者:

VACUUM FULL t2;

后者具有侵入性,会在持续时间内阻止并发访问。也许一些非阻塞等价物......
无论哪种方式,所有这些都是无聊的维护。更有趣的细节是这样的:

98% 的行的

text_id
为空

虽然该百分比不会大幅下降,但部分索引是一个有趣的候选者(作为现有索引的补充或替代):

CREATE UNIQUE INDEX t2_text_id_notnull_idx ON t2 (text_id) WHERE text_id IS NOT NULL;

更小的索引应该更容易使用(也更便宜)。

© www.soinside.com 2019 - 2024. All rights reserved.