我进行了几次测试。事实证明,该EXISTS
变体要快得多——正如我所预料的那样,与@Tometzky 发布的相反。
在 PostgreSQL 9.1.2 上进行 10.000 行的测试床,设置不错:
CREATE TEMP TABLE test (
a serial
,b int NOT NULL
,c int NOT NULL
);
INSERT INTO test (b,c)
SELECT (random()* 100)::int AS b, (random()* 100)::int AS c
FROM generate_series(1, 10000);
ALTER TABLE test ADD CONSTRAINT a_pk PRIMARY KEY (a);
在第一轮和第二轮测试之间,我跑了:
ANALYZE test;
当我最终应用 DELETE 时,删除了 3368 个重复项。如果您有更多或更少的重复项,性能可能会有所不同。
我对每个查询运行了几次EXPLAIN ANALYZE
并获得了最好的结果。一般来说,最好的与第一个或最差的几乎没有区别。
裸露SELECT
的(没有DELETE
)显示类似的结果。
1. CTE 与rank()
总运行时间:150.411 ms
总运行时间:149.853 ms -- ANALYZE 之后
WITH x AS (
SELECT a
,rank() OVER (PARTITION BY b, c ORDER BY a) AS rk
FROM test
)
DELETE FROM test
USING x
WHERE x.a = test.a
AND rk > 1;
2. CTE 与row_number()
总运行时间:148.240 ms
总运行时间:147.711 ms -- ANALYZE 之后
WITH x AS (
SELECT a
,row_number() OVER (PARTITION BY b, c ORDER BY a) AS rn
FROM test
)
DELETE FROM test
USING x
WHERE x.a = test.a
AND rn > 1;
3.row_number()
在子查询中
总运行时间:134.753 ms
总运行时间:134.298 ms -- ANALYZE 之后
DELETE FROM test
USING (
SELECT a
,row_number() OVER (PARTITION BY b, c ORDER BY a) AS rn
FROM test
) x
WHERE x.a = test.a
AND rn > 1;
4.EXISTS
半连接
总运行时间:143.777 毫秒
总运行时间:69.072 毫秒 ——在 ANALYZE 之后
DELETE FROM test t
WHERE EXISTS (
SELECT 1
FROM test t1
WHERE t1.a < t.a
AND (t1.b, t1.c) = (t.b, t.c)
);
第二次运行的不同之处在于切换到Hash Semi Join
而不是额外的Sort + Merge Semi Join。
结果
EXISTS
显然以最新的表统计信息获胜。
- 在子查询中使用过时的统计信息
row_number()
是最快的。
rank()
是最慢的变体。
- CTE 比子查询慢。
ANALYZE
(更新的统计数据)有助于提高性能并且可以提供很多帮助。Autovacuum(默认)应该或多或少地自动处理这个问题 - 除了临时表或对表进行重大更改之后。在此处或此处阅读更多信息。
测试 100.000 行
我用 100.000 行和 63045 个重复重复了测试。类似的结果,只是EXISTS
速度较慢,即使在ANALYZE
.
- 总运行时间:1648.601 毫秒
- 总运行时间:1623.759 毫秒
- 总运行时间:1568.893 毫秒
- 总运行时间:1692.249 毫秒
将统计目标提高到 1000,然后提高到最大值 10000(在现实生活中过大),另一个ANALYZE
将所有查询加快了约 1%,但查询规划器仍然使用Sort + Merge Semi Join for EXISTS
。
ALTER TABLE test ALTER COLUMN b SET STATISTICS 10000;
ALTER TABLE test ALTER COLUMN c SET STATISTICS 10000;
ANALYZE test;
只有在我强迫规划器避免合并连接之后,规划器才使用哈希半连接再次占用一半时间:
SET enable_mergejoin = off
- 总运行时间:850.615 毫秒
更新
从那时起,查询计划器得到了改进。在 PostgreSQL 9.1.7 的重新测试中直接使用Hash Semi Join。