在PostgreSQL 9.2中,我有一个用户评分的物品表:
id | userid | itemid | rating | timestamp | !update_time
--------+--------+--------+---------------+---------------------+------------------------
522241 | 3991 | 6887 | 0.1111111111 | 2005-06-20 03:13:56 | 2013-10-11 17:50:24.545
522242 | 3991 | 6934 | 0.1111111111 | 2005-04-05 02:25:21 | 2013-10-11 17:50:24.545
522243 | 3991 | 6936 | -0.1111111111 | 2005-03-31 03:17:25 | 2013-10-11 17:50:24.545
522244 | 3991 | 6942 | -0.3333333333 | 2005-03-24 04:38:02 | 2013-10-11 17:50:24.545
522245 | 3991 | 6951 | -0.5555555556 | 2005-06-20 03:15:35 | 2013-10-11 17:50:24.545
... | ... | ... | ... | ... | ...
我希望执行一个非常简单的查询:对于每个用户,在数据库中选择评级的总数。
我使用以下简单的方法:
SELECT userid, COUNT(*) AS rcount
FROM ratings
GROUP BY userid
表格包含1000万条记录。查询需要大约2到3分钟的时间。说实话,我对此不太满意,我认为对于这么长的查询,1000万并不是一个很大的数字。(或者它确实是吗..??) |
因此,我要求PostgreSQL向我显示执行计划: |
EXPLAIN SELECT userid, COUNT(*) AS rcount
FROM ratings
GROUP BY userid
这将导致:
GroupAggregate (cost=1756177.54..1831423.30 rows=24535 width=5)
-> Sort (cost=1756177.54..1781177.68 rows=10000054 width=5)
Sort Key: userid
-> Seq Scan on ratings (cost=0.00..183334.54 rows=10000054 width=5)
我将其理解为:首先,整个表从磁盘中读取(顺序扫描)。其次,通过
n*log(n)
进行userid排序(排序)。最后,在线性时间内逐行读取排序后的表并进行汇总。我认为这不是最优算法,如果我自己要实现它,我会使用哈希表并在第一遍构建结果。没关系。看起来是按
userid
排序花费了很长时间。因此添加了一个索引:CREATE INDEX ratings_userid_index ON ratings (userid)
很遗憾,这并没有改善性能,我肯定自己不是高级用户,我相信我做错了一些基本的事情,但这就是我卡住的地方。我希望有任何想法可以让查询在合理的时间内执行。还有一点需要注意的是:在执行过程中,PostgreSQL 工作进程占用了我的一个 CPU 内核的 100%,这表明磁盘访问不是主要的瓶颈。
编辑
按 @a_horse_with_no_name 的要求。哇,对我来说相当高级:
EXPLAIN (analyze on, buffers on, verbose on)
SELECT userid,COUNT(userid) AS rcount
FROM movielens_10m.ratings
GROUP BY userId
输出:
GroupAggregate (cost=1756177.54..1831423.30 rows=24535 width=5) (actual time=110666.899..127168.304 rows=69878 loops=1)
Output: userid, count(userid)
Buffers: shared hit=906 read=82433, temp read=19358 written=19358
-> Sort (cost=1756177.54..1781177.68 rows=10000054 width=5) (actual time=110666.838..125180.683 rows=10000054 loops=1)
Output: userid
Sort Key: ratings.userid
Sort Method: external merge Disk: 154840kB
Buffers: shared hit=906 read=82433, temp read=19358 written=19358
-> Seq Scan on movielens_10m.ratings (cost=0.00..183334.54 rows=10000054 width=5) (actual time=0.019..2889.583 rows=10000054 loops=1)
Output: userid
Buffers: shared hit=901 read=82433
Total runtime: 127193.524 ms
编辑2
@a_horse_with_no_name的评论解决了该问题。 我很高兴分享我的发现:
SET work_mem = '1MB';
EXPLAIN SELECT userid,COUNT(userid) AS rcount
FROM movielens_10m.ratings
GROUP BY userId
产生与上面相同的结果:
GroupAggregate (cost=1756177.54..1831423.30 rows=24535 width=5)
-> Sort (cost=1756177.54..1781177.68 rows=10000054 width=5)
Sort Key: userid
-> Seq Scan on ratings (cost=0.00..183334.54 rows=10000054 width=5)
然而,
SET work_mem = '10MB';
EXPLAIN SELECT userid,COUNT(userid) AS rcount
FROM movielens_10m.ratings
GROUP BY userId
提供
HashAggregate (cost=233334.81..233580.16 rows=24535 width=5)
-> Seq Scan on ratings (cost=0.00..183334.54 rows=10000054 width=5)
现在查询只需要大约3.5秒就能完成。
explain (analyze on, buffers on, verbose on) ..
的输出结果。 - user330315work_mem='250MB'
(甚至更高),如果你有足够的内存的话。 - user330315