我试图在PostgreSQL 9.3中进行一些设置操作。
我有两张桌子,为简单起见,我们称之为table_a
和table_b
:
create table table_a(id varchar primary key);
create table table_b(id varchar primary key);
我有一个简单的查询(最简单的表述,虽然它是实践中插入的来源):
(select id from table_a) except (select id from table_b);
在我开始使用PostgreSQL之前,我做了这样的操作:
set-diff table_a.csv table_b.csv > table_c.csv
set-diff的外观大致如下:
while (not eof(a)) and (not eof(b)):
line_a <- peek_line(a)
line_b <- peek_line(b)
if line_a < line_b:
output read_line(a)
else if line_a == line_b:
read_line(a)
else:
read_line(b)
while not eof(a):
output read_line(a)
这根本不需要很长时间,具有无关紧要的内存要求,并且最大限度地提高了顺序磁盘I / O的使用效率。这很重要,因为这台机器没有大量的内存 - 它不能适应RAM中的所有数据。
然而,PostgreSQL提出了这种计划(来自一些实际的表格):
QUERY PLAN
----------------------------------------------------------------------------------
SetOp Except (cost=3184554.28..3238904.44 rows=9434298 width=51)
-> Sort (cost=3184554.28..3211729.36 rows=10870032 width=51)
Sort Key: "*SELECT* 1".id
-> Append (cost=0.00..428039.64 rows=10870032 width=51)
-> Subquery Scan on "*SELECT* 1" (cost=0.00..345707.96 rows=9434298 width=54)
-> Seq Scan on table_a (cost=0.00..251364.98 rows=9434298 width=54)
-> Subquery Scan on "*SELECT* 2" (cost=0.00..82331.68 rows=1435734 width=32)
-> Seq Scan on table_b (cost=0.00..67974.34 rows=1435734 width=32)
查询时间太长 - 几分钟。
我确信PostgreSQL可以使用我上面概述的相同类型的合并策略,单独使用索引扫描,而不使用排序。相反,似乎是连接两个表扫描并对它们进行排序,有点像这个命令行,尽管没有读取table_b两次:
sort table_a.csv table_b.csv table_b.csv | uniq -u
这涉及到相当多的额外工作 - 对于一个,当一部分内容不适合内存时,会有一小部分log(n)次I / O.
涉及的列是btree索引。从查询中选择的唯一列与索引并正在合并的列相同。 Locale到处都是C.
在我使用大量文本文件和一些自定义索引工具之前。我试图使用数据库来获得额外的查询灵活性,并避免维护自定义索引。然而,性能令人震惊,以至于我考虑在数据库之外进行合并和大多数其他大规模更新操作,通过csv对数据进行四舍五入。
我错过了什么?
答案 0 :(得分:5)
初步想法:
EXCEPT
表示EXCEPT DISTINCT
,这意味着它会从结果中删除重复的行。如果可以,请使用EXCEPT ALL
,应更快。EXPLAIN
开始,您似乎也应用了订购,这也需要更多时间(特别是在合并查询时)。我9.2
上的结果:
<强> EXCEPT
强>
explain select id from table_a except (select id from table_b);
结果:
HashSetOp Except (cost=0.00..947.00 rows=20000 width=5)
-> Append (cost=0.00..872.00 rows=30000 width=5)
-> Subquery Scan on "*SELECT* 1" (cost=0.00..563.00 rows=20000 width=5)
-> Seq Scan on table_a (cost=0.00..363.00 rows=20000 width=5)
-> Subquery Scan on "*SELECT* 2" (cost=0.00..309.00 rows=10000 width=4)
-> Seq Scan on table_b (cost=0.00..209.00 rows=10000 width=4)
EXCEPT
ORDER BY
explain select id from table_a except (select id from table_b) order by id;
结果:
Sort (cost=2375.77..2425.77 rows=20000 width=5)
Sort Key: "*SELECT* 1".id
-> HashSetOp Except (cost=0.00..947.00 rows=20000 width=5)
-> Append (cost=0.00..872.00 rows=30000 width=5)
-> Subquery Scan on "*SELECT* 1" (cost=0.00..563.00 rows=20000 width=5)
-> Seq Scan on table_a (cost=0.00..363.00 rows=20000 width=5)
-> Subquery Scan on "*SELECT* 2" (cost=0.00..309.00 rows=10000 width=4)
-> Seq Scan on table_b (cost=0.00..209.00 rows=10000 width=4)
使用JOIN
ORDER BY
explain select table_a.id from table_a
left outer join table_b on table_a.id = table_b.id
where table_b.id is null order by table_a.id;
和
explain select id from table_a
where not exists (select * from table_b where table_b.id = table_a.id) order by id;
结果(相同):
Merge Anti Join (cost=0.57..1213.57 rows=10000 width=5)
Merge Cond: ((table_a.id)::text = (table_b.id)::text)
-> Index Only Scan using table_a_pkey on table_a (cost=0.29..688.29 rows=20000 width=5)
-> Index Only Scan using table_b_pkey on table_b (cost=0.29..350.29 rows=10000 width=4)
NOT IN
ORDER BY
explain select id from table_a where id not in (select id from table_b) order by id;
结果(我的赢家):
Seq Scan on table_a (cost=234.00..647.00 rows=10000 width=5)
Filter: (NOT (hashed SubPlan 1))
SubPlan 1
-> Seq Scan on table_b (cost=0.00..209.00 rows=10000 width=4)
用于
create table table_a(id varchar primary key, rnd float default random());
create table table_b(id varchar primary key, rnd float default random());
do language plpgsql $$
begin
for i in 1 .. 10000 loop
insert into table_a(id) values (i);
insert into table_b(id) values (i);
end loop;
for i in 10001 .. 20000 loop
insert into table_a(id) values (i);
end loop;
end;
$$;
答案 1 :(得分:1)
这些变体如何做?
select id
from table_a a
where not exists (select 1 from table_b b where b.id = a.id);
或:
select id
from table_a left outer join
table_b b
on a.id = b.id
where b.id is null;
如果这些表现更好,那就是没有那么多的努力来优化except
作为该语言的其他组成部分。
答案 2 :(得分:0)
你把桌子弄脏了吗?来自relallvisible
的{{1}}是什么?如果索引扫描还必须访问每行的表,那么索引扫描是不利的,因为它是分散的IO。
使用vacuumumed表,我得到两个仅索引扫描之间的合并连接。
我认为你对数据库的作用抱有不切实际的期望。您总是可以编写自己的代码,这些代码可以比数据库更快地完成任何给定的操作,特别是如果您在此过程中忽略ACID。
例如,如果您对数据进行排序,则可以避免进行排序(前提是您只需要排序一个键)。但是,您无法再进行有效的更新或插入。