还有EXISTS
:
SELECT count(*) AS post_ct
FROM posts p
WHERE EXISTS (SELECT FROM votes v WHERE v.post_id = p.id);
在 Postgres 中,在n侧有多个条目,就像您可能拥有的那样,它通常比count(DISTINCT post_id)
:
SELECT count(DISTINCT p.id) AS post_ct
FROM posts p
JOIN votes v ON v.post_id = p.id;
中每个帖子的行数越多votes
,性能差异就越大。用 测试EXPLAIN ANALYZE
。
count(DISTINCT post_id)
必须读取所有行,对它们进行排序或散列,然后只考虑每个相同集合的第一行。EXISTS
只会扫描votes
(或者,最好是 上的索引post_id
),直到找到第一个匹配项。
如果保证每个post_id
invotes
都存在于表中posts
(通过外键约束强制执行参照完整性),则此短形式等效于较长形式:
SELECT count(DISTINCT post_id) AS post_ct
FROM votes;
实际上可能比每个帖子没有或很少条目EXISTS
的查询更快。
您的查询也以更简单的形式工作:
SELECT count(*) AS post_ct
FROM (
SELECT FROM posts
JOIN votes ON votes.post_id = posts.id
GROUP BY posts.id
) sub;
基准
为了验证我的说法,我在资源有限的测试服务器上运行了一个基准测试。全部在一个单独的架构中:
测试设置
伪造典型的帖子/投票情况:
CREATE SCHEMA y;
SET search_path = y;
CREATE TABLE posts (
id int PRIMARY KEY
, post text
);
INSERT INTO posts
SELECT g, repeat(chr(g%100 + 32), (random()* 500)::int) -- random text
FROM generate_series(1,10000) g;
DELETE FROM posts WHERE random() > 0.9; -- create ~ 10 % dead tuples
CREATE TABLE votes (
vote_id serial PRIMARY KEY
, post_id int REFERENCES posts(id)
, up_down bool
);
INSERT INTO votes (post_id, up_down)
SELECT g.*
FROM (
SELECT ((random()* 21)^3)::int + 1111 AS post_id -- uneven distribution
, random()::int::bool AS up_down
FROM generate_series(1,70000)
) g
JOIN posts p ON p.id = g.post_id;
以下所有查询都返回相同的结果(9107 个帖子中有 8093 个有投票)。
我用EXPLAIN ANALYZE
ant 进行了 4 次测试,在Postgres 9.1.4上用三个查询中的每一个进行了 5 次测试,并附加了生成的总运行时间。
照原样。
后 ..
ANALYZE posts;
ANALYZE votes;
后 ..
CREATE INDEX foo on votes(post_id);
后 ..
VACUUM FULL ANALYZE posts;
CLUSTER votes using foo;
count(*) ... WHERE EXISTS
- 253 毫秒
- 220 毫秒
- 85 毫秒 ——获胜者(对帖子进行序列扫描,对投票进行索引扫描,嵌套循环)
- 85 毫秒
count(DISTINCT x)
- 带连接的长表格
- 354 毫秒
- 358 毫秒
- 373 毫秒——(对帖子进行索引扫描,对投票进行索引扫描,合并连接)
- 330 毫秒
count(DISTINCT x)
- 没有连接的简短形式
- 164 毫秒
- 164 毫秒
- 164 毫秒——(总是 seq 扫描)
- 142 毫秒
有问题的原始查询的最佳时间:
对于简化版:
@wildplasser 的 CTE 查询使用与长表单相同的计划(对帖子的索引扫描、对投票的索引扫描、合并连接)加上一点 CTE 的开销。最好的时间:
即将推出的 PostgreSQL 9.2 中的仅索引扫描可以改进每个查询的结果,尤其是对于EXISTS
.
Postgres 9.5 相关的更详细的基准测试(实际上是检索不同的行,而不仅仅是计数):