特定情况下无法使用索引

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

我面临着一个奇怪且令人恼火的问题,即我的 PostgreSQL 10 数据库在特定情况下不使用索引。我已经创建了其中一列的 md5 哈希值的索引,并且该索引在正常情况下似乎工作正常:

EXPLAIN
SELECT * FROM variants_table WHERE md5(variant_id) = md5('1_9746683_A_G');

QUERY PLAN
Index Scan using idx_variant_id_md5 on variants_table  (cost=0.71..50470.36 rows=12473 width=748)
   Index Cond: (md5(variant_id) = '00fa85f0ea97a4aa2898f838f427e560'::text)
(2 rows)

但是,当我尝试使用此查询删除重复项时:

EXPLAIN
WITH Ranked AS (
   SELECT
     md5(variant_id) as variant_id_md5,
     consscore,
     ROW_NUMBER() OVER (PARTITION BY md5(variant_id) ORDER BY consscore DESC) as rn
   FROM
     variants_table
 )
DELETE FROM variants_table
WHERE (md5(variant_id), consscore) IN (
   SELECT variant_id_md5, consscore
   FROM Ranked
   WHERE rn > 1
 );

QUERY PLAN
 Delete on variants_table  (cost=3647060725.61..4520666023.49 rows=2949470436 width=70)
   CTE ranked
     ->  WindowAgg  (cost=3096491910.93..3361944250.15 rows=11797881743 width=48)
           ->  Sort  (cost=3096491910.93..3125986615.29 rows=11797881743 width=40)
                 Sort Key: (md5(variants_table_1.variant_id)), variants_table_1.consscore DESC
                 ->  Seq Scan on variants_table variants_table_1  (cost=0.00..800237220.79 rows=11797881743 width=40)
   ->  Hash Join  (cost=285116475.46..1158721773.34 rows=2949470436 width=70)
         Hash Cond: ((md5(variants_table.variant_id) = ranked.variant_id_md5) AND (variants_table.consscore = ranked.consscore))
         ->  Seq Scan on variants_table  (cost=0.00..770742516.43 rows=11797881743 width=46)
         ->  Hash  (cost=285115875.46..285115875.46 rows=40000 width=104)
               ->  HashAggregate  (cost=285115475.46..285115875.46 rows=40000 width=104)
                     Group Key: ranked.variant_id_md5, ranked.consscore
                     ->  CTE Scan on ranked  (cost=0.00..265452339.22 rows=3932627248 width=104)
                           Filter: (rn > 1)
(14 rows)

默认为顺序扫描。

我已经尝试了一切来调试它,包括设置

enable_seqscan = off
,但无论如何,查询规划器仍然使用顺序扫描,导致我假设它不知道如何使用索引。

sql postgresql indexing postgresql-10 postgresql-performance
1个回答
1
投票

问题

窗口函数

row_number()
必须处理整个表,因此一开始就使用索引是没有好处的。因此,查询计划下降为顺序扫描。

更好的查询

Postgres 15

row_number()
带来了一些优化,即将推出的 Postgres 16 带来了更多优化,但我不确定您的
DELETE
查询可以获利多少。

尽管如此,这个更简单的查询在任何情况下都应该表现更好:

DELETE FROM variants_table d
WHERE  EXISTS (
   SELECT FROM variants_table v
   WHERE  md5(v.variant_id) = md5(d.variant_id)
   AND    v.consscore > d.consscore
   );

阅读:
“删除同一个表中存在具有相同

md5(variant_id)
且更大
consscore
的另一行的行!”

如果

(md5(variant_id), consscore)
上可以存在完整的重复项,则此查询将保留具有最高
consscore
的所有重复项 - 与原始查询不同。但是您的原始查询保留了任意获胜者,这通常是糟糕的设计,在这种情况下您确实应该添加一个确定性的决胜局 - 您可以在我的查询中相应地使用它。

如果任意选择足够好,您可以在行值比较中使用系统列

ctid

DELETE FROM variants_table d
WHERE  EXISTS (
   SELECT FROM variants_table v
   WHERE  md5(v.variant_id) = md5(d.variant_id)
   AND    (v.consscore, v.ctid) > (d.consscore, d.ctid)
   );

关于行值比较:

请注意,我的直接比较对于

null
值的作用与原始值不同。通常,如果
variant_id
consscore
可以是
null
,您就不想使用我的查询。参见:

如果可以涉及

null
值,您必须准确定义所需的行为。

如果这将删除大多数行,其他策略可能更有效。就像将少数幸存者复制到一张原始的新桌子上一样。参见:

但我假设你正在寻找一些重复的东西。清理完毕后考虑制作该表达式索引

UNIQUE
,以防止更多的骗子潜入。如果可能的话。

旁白

使用数据类型

uuid
而不是文本优化表达式索引后,您的查询应该会更快。参见:

或者也许

bigint
哈希就足够了?参见:

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