PostgreSQL:加入大型表的小子集的最佳方式

时间:2014-11-22 23:30:03

标签: postgresql join correlated-subquery

我在Windows机器上使用PostgreSQL 9.3。 假设我有一个包含400万条记录的客户表和一个包含2500万条记录的订单表。 但是,我只对我的纽约客户感兴趣。只有5,000名纽约客户订购了15,000个订单,即一个非常小的子集。

检索客户ID的最佳方法是什么,以及纽约客户的订单总数是多少?

是一个相关的子查询,如

Select c.clientid
, ( select count(orders.clientid) from orders where orders.clientid = c.clientid) as NumOrders

From clients c
WHERE c.city = 'New York'

比像

这样的连接更快
Select c.clientid
,coalesce(o.NumOrders,0) as NumOrders

From clients c

Left outer join
( select clientid, count(*) as NumOrders from orders group by clientid ) o
on c.clientid = o.clientid

WHERE c.city = 'New York'

因为后者花费大部分时间来计算记录,然后因为它们与纽约客户无关而被丢弃? 或者有更好的方法吗?

谢谢!

PS是的,我知道,我应该看一下执行计划,但我是在家写这个,而且我没有一个包含数百万条记录的数据库来测试它。

2 个答案:

答案 0 :(得分:2)

正如您所提到的,真正知道的唯一方法是比较执行计划。实际上,最好的方法是使用EXPLAIN ANALYZE,以便它实际执行查询并将结果插入带有估计值的输出中,这样您就可以了解查询计划程序与现实。

但是,一般来说,在这种情况下我会做的可能是为客户端子集创建临时表,然后为JOIN创建orders表。您可以选择使用WITH代替一次查询。

所以,比如:

CREATE TEMP TABLE tmp_clients AS
SELECT c.clientid
FROM clients c
WHERE c.city = 'New York'
ORDER BY c.clientid;

SELECT *
FROM orders AS o
JOIN tmp_clients AS c ON (o.clientid = c.clientid)
ORDER BY o.clientid;

这样,tmp_clients只包含纽约客户 - 约5K行 - 而且该表将加入订单表。

您还可以进一步优化,在临时表(在clientid上)创建一个索引,然后在ANALYZE之前JOIN创建一个索引,以确保JOIN完全在索引上完成。您需要检查每种情况下的查询计划以查看相对差异(或者只要记住JOIN并不如您所希望的那么快)。

对@poshest评论的回复:

听起来像临时表正在堆叠,这会增加内存占用,而且对于长时间运行的连接,功能似乎是内存泄漏。

在这种情况下,它不会是真正的泄漏,因为临时表的作用域是连接。它们会自动消失,但直到连接结束后才会消失。但是,当你完成它们时,你可以让它们立即消失。简单DROP表格就像你完成任何其他表格一样,我怀疑你能够多次调用这个函数 - 在同一个连接上 - 没有相同的类型单调内存占用增加。

答案 1 :(得分:0)

在第一种情况下,您使用的子查询必须每个匹配的客户端ID号至少执行一次。它表现不佳。

在第二种情况下,您在子查询上使用左外连接,这是 little 奇数。它将为没有订单的客户保留价值。这有点像谈论从未购买任何东西的顾客 - 他们真的是顾客吗?我要说“不”,因为这是我的答案,我可以弥补我喜欢的任何东西。但我已经看到了 有意义地讨论你从未为之工作的客户的情况。 (律师事务所有时会让客户没有收费时间。)

无论如何。 。

通过简单,直接地表达您想要计算的内容,您几乎肯定会更好。

select o.clientid, count(*)
from orders o
inner join clients c on c.clientid = o.clientid
       and c.city = 'New York'
group by o.clientid;

你需要一个关于“城市”的索引。


我使用随机(ish)数据构建,填充和索引了一些表。简单直接的查询速度提高了一个数量级。

explain analyze
Select c.clientid
      ,coalesce(o.NumOrders,0) as NumOrders
From clients c
Left outer join
    ( select clientid, count(*) as NumOrders from orders group by clientid ) o
on c.clientid = o.clientid
WHERE c.city = 'New York';
"Merge Right Join  (cost=4813633.08..5022834.83 rows=5200 width=12) (actual time=105751.121..117901.326 rows=5000 loops=1)"
"  Merge Cond: (orders.clientid = c.clientid)"
"  ->  GroupAggregate  (cost=4799762.91..4996891.51 rows=962770 width=4) (actual time=105702.262..117735.305 rows=1000000 loops=1)"
"        ->  Sort  (cost=4799762.91..4862263.21 rows=25000120 width=4) (actual time=105688.877..113148.881 rows=25000000 loops=1)"
"              Sort Key: orders.clientid"
"              Sort Method: external merge  Disk: 342176kB"
"              ->  Seq Scan on orders  (cost=0.00..360621.20 rows=25000120 width=4) (actual time=14.866..35814.499 rows=25000000 loops=1)"
"  ->  Sort  (cost=13870.18..13883.18 rows=5200 width=4) (actual time=39.536..40.241 rows=5000 loops=1)"
"        Sort Key: c.clientid"
"        Sort Method: quicksort  Memory: 427kB"
"        ->  Bitmap Heap Scan on clients c  (cost=144.73..13549.22 rows=5200 width=4) (actual time=29.556..30.638 rows=5000 loops=1)"
"              Recheck Cond: ((city)::text = 'New York'::text)"
"              ->  Bitmap Index Scan on clients_city_idx  (cost=0.00..143.43 rows=5200 width=0) (actual time=29.538..29.538 rows=5000 loops=1)"
"                    Index Cond: ((city)::text = 'New York'::text)"
"Total runtime: 118027.256 ms"
explain analyze
select o.clientid, count(*)
from orders o
inner join clients c on c.clientid = o.clientid
       and c.city = 'New York'
group by o.clientid;
"GroupAggregate  (cost=595747.05..596315.80 rows=32500 width=4) (actual time=9167.518..9179.855 rows=1250 loops=1)"
"  ->  Sort  (cost=595747.05..595828.30 rows=32500 width=4) (actual time=9167.504..9174.135 rows=31336 loops=1)"
"        Sort Key: o.clientid"
"        Sort Method: external merge  Disk: 432kB"
"        ->  Hash Join  (cost=13614.22..593311.47 rows=32500 width=4) (actual time=3.055..9123.568 rows=31336 loops=1)"
"              Hash Cond: (o.clientid = c.clientid)"
"              ->  Seq Scan on orders o  (cost=0.00..360621.20 rows=25000120 width=4) (actual time=0.041..3833.387 rows=25000000 loops=1)"
"              ->  Hash  (cost=13549.22..13549.22 rows=5200 width=4) (actual time=2.915..2.915 rows=5000 loops=1)"
"                    Buckets: 1024  Batches: 1  Memory Usage: 176kB"
"                    ->  Bitmap Heap Scan on clients c  (cost=144.73..13549.22 rows=5200 width=4) (actual time=0.672..1.769 rows=5000 loops=1)"
"                          Recheck Cond: ((city)::text = 'New York'::text)"
"                          ->  Bitmap Index Scan on clients_city_idx  (cost=0.00..143.43 rows=5200 width=0) (actual time=0.658..0.658 rows=5000 loops=1)"
"                                Index Cond: ((city)::text = 'New York'::text)"
"Total runtime: 9180.291 ms"