我面临着一个奇怪且令人恼火的问题,即我的 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
,但无论如何,查询规划器仍然使用顺序扫描,导致我假设它不知道如何使用索引。
窗口函数
row_number()
必须处理整个表,因此一开始就使用索引是没有好处的。因此,查询计划下降为顺序扫描。
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
哈希就足够了?参见: