【发布时间】:2014-11-25 10:17:14
【问题描述】:
考虑下表:
foo | bar
-----+-----
3 | 1
8 | 1
2 | 1
8 | 5
6 | 5
5 | 5
4 | 5
5 | 7
4 | 7
foo 列包含任何内容。 bar 列几乎 是有序的,常见的 bar 值行彼此跟随。表总共包含约 170 万行,每个不同的 bar 值大约有 15 行。
我发现 PARTITION BY 很慢,想知道是否可以做些什么来提高它的性能?
我尝试CREATE INDEX bar_idx ON foobar(bar),但它对性能没有影响(IRL 表的另一列上已经有一个主键)。我正在使用 PostgreSQL 9.3.5。
这里是EXPLAIN ANALYZE 用于一个简单的查询,有和没有PARTITION BY:
> EXPLAIN ANALYZE SELECT count(foo) OVER (PARTITION BY bar) FROM foobar;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
WindowAgg (cost=262947.92..293133.35 rows=1724882 width=8) (actual time=2286.082..3504.372 rows=1724882 loops=1)
-> Sort (cost=262947.92..267260.12 rows=1724882 width=8) (actual time=2286.068..2746.047 rows=1724882 loops=1)
Sort Key: bar
Sort Method: external merge Disk: 27176kB
-> Seq Scan on foobar (cost=0.00..37100.82 rows=1724882 width=8) (actual time=0.019..441.827 rows=1724882 loops=1)
Total runtime: 3606.695 ms
(6 lignes)
> EXPLAIN ANALYZE SELECT foo FROM foobar;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------
Seq Scan on foobar (cost=0.00..37100.82 rows=1724882 width=4) (actual time=0.014..385.931 rows=1724882 loops=1)
Total runtime: 458.776 ms
(2 lignes)
第一次改进,增加 work_mem:
在大多数情况下,按照 hbn 的建议增加 work_mem 应该会有所帮助。就我而言,我正在使用 SSD,因此切换到 RAM(将 work_mem 增加到 1 GB)只会将处理时间减少 1.5:
> EXPLAIN (ANALYZE, BUFFERS) SELECT foo OVER (PARTITION BY bar) FROM foobar;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
WindowAgg (cost=215781.92..245967.35 rows=1724882 width=8) (actual time=933.575..1931.656 rows=1724882 loops=1)
Buffers: shared hit=2754 read=17098
-> Sort (cost=215781.92..220094.12 rows=1724882 width=8) (actual time=933.558..1205.314 rows=1724882 loops=1)
Sort Key: bar
Sort Method: quicksort Memory: 130006kB
Buffers: shared hit=2754 read=17098
-> Seq Scan on foobar (cost=0.00..37100.82 rows=1724882 width=8) (actual time=0.023..392.446 rows=1724882 loops=1)
Buffers: shared hit=2754 read=17098
Total runtime: 2051.494 ms
(9 lignes)
第二次改进,使用CLUSTER:
我尝试了this post 的一些建议——增加统计数据对我的情况没有显着影响。唯一有帮助或尚未激活的是“按照索引的物理顺序重写表”,使用CLUSTER(您可能更喜欢pg_repack,阅读原帖):
> CLUSTER foobar USING bar_idx;
CLUSTER
> EXPLAIN (ANALYZE, BUFFERS) SELECT count(foo) OVER (PARTITION BY bar) FROM foobar;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
WindowAgg (cost=0.43..150079.25 rows=1724882 width=8) (actual time=0.031..1372.416 rows=1724882 loops=1)
Buffers: shared hit=64 read=24503
-> Index Scan using bar_idx on foobar (cost=0.43..124206.02 rows=1724882 width=8) (actual time=0.018..581.665 rows=1724882 loops=1)
Buffers: shared hit=64 read=24503
Total runtime: 1484.974 ms
(5 lignes)
第三次改进,表格子集:
在我的情况下,我最终需要在此表和另一个表上进行选择,因此将表的子集创建为自己的表似乎是有意义的:
CREATE TABLE subfoobar AS (SELECT * FROM foobar WHERE bar IN (SELECT DISTINCT bar FROM othertable) ORDER BY bar);
新表只有 70 万行而不是 170 万行,而且查询时间(在 bar 上重新创建索引后)大致成正比,因此收益显着:
> EXPLAIN (ANALYZE, BUFFERS) SELECT count(foo) OVER (PARTITION BY bar) FROM subfoobar;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------
WindowAgg (cost=0.42..37455.61 rows=710173 width=8) (actual time=0.025..543.437 rows=710173 loops=1)
Buffers: shared hit=10290
-> Index Scan using bar_sub_idx on subfoobar (cost=0.42..26803.02 rows=710173 width=8) (actual time=0.015..222.211 rows=710173 loops=1)
Buffers: shared hit=10290
Total runtime: 590.063 ms
(5 lignes)
第四项改进,汇总表:
由于 IRL 窗口函数在查询中涉及多次,查询本身将执行多次(数据挖掘),并且在分区上聚合的结果将始终相同,我决定选择更多有效的方法:我将所有这些值提取到一个新的“汇总表”中(不确定我的定义是否与“官方”定义匹配?)。
在我们的简单示例中,这将给出
CREATE TABLE summary_foobar AS SELECT DISTINCT ON (bar) count(foo) OVER (PARTITION BY bar) AS cfoo, bar FROM foobar;
实际上,正如 cmets 中的 hbn 所建议的那样,创建一个 MATERIALIZED VIEW 而不是一个新表会更好,这样我们就可以随时使用 REFRESH MATERIALIZED VIEW summary_foobar; 对其进行更新:
CREATE MATERIALIZED VIEW summary_foobar AS SELECT DISTINCT ON (bar) count(foo) OVER (PARTITION BY bar) AS cfoo, bar FROM foobar;
然后,将初始查询应用于我的真实案例表:
> EXPLAIN (ANALYZE, BUFFERS) SELECT cfoo FROM subfoobar,summary_foobar WHERE subfoobar.bar=summary_foobar.bar;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=1254.64..28939.67 rows=424685 width=73) (actual time=9.893..268.704 rows=370393 loops=1)
Hash Cond: (subfoobar.bar = summary_foobar.bar)
Buffers: shared hit=8916
-> Seq Scan on subfoobar (cost=0.00..15448.73 rows=710173 width=4) (actual time=0.003..70.850 rows=710173 loops=1)
Buffers: shared hit=8347
-> Hash (cost=873.73..873.73 rows=30473 width=77) (actual time=9.872..9.872 rows=30473 loops=1)
Buckets: 4096 Batches: 1 Memory Usage: 3347kB
Buffers: shared hit=569
-> Seq Scan on summary_foobar (cost=0.00..873.73 rows=30473 width=77) (actual time=0.003..4.569 rows=30473 loops=1)
Buffers: shared hit=569
Total runtime: 286.910 ms [~550 ms if using foobar instead of subfoobar]
(11 lignes)
总而言之,对于我的实际案例查询,我从每个查询 5000+ 毫秒下降到大约 150 毫秒(由于WHERE 子句,少于示例)。
【问题讨论】:
-
很高兴您设法找到优化事物的方法。如果您使用 >=9.3,您可能会发现为汇总表使用物化视图很有用。使用
CREATE MATERIALIZED summary_foobar AS SELECT DISTINCT ON (bar) count(foo) OVER (PARTITION BY bar) AS cfoo, bar FROM foobar;之类的内容创建。你可以用同样的方式查询,但是用REFRESH MATERIALIZED VIEW summary_foobar;刷新内容很容易,而不是删除和重新创建/截断和重新插入。 -
抱歉,我在创建命令中错过了“VIEW” - s/b
CREATE MATERIALIZED VIEW summary_foobar AS SELECT DISTINCT ON (bar) count(foo) OVER (PARTITION BY bar) AS cfoo, bar FROM foobar; -
很好的提示,谢谢!我会用它来更新答案。
标签: postgresql query-optimization database-performance window-functions