提高 Postgres 性能
Posted
技术标签:
【中文标题】提高 Postgres 性能【英文标题】:Improve Postgres performance 【发布时间】:2021-12-18 01:13:43 【问题描述】:我是 Postgres 的新手,我确信我做错了什么。 所以我只是想知道是否有人经历过与我在下面的经历类似的事情,或者可以为我指出正确的方向来提高 Postgres 的性能。
我最初的目标是通过从 MS SQL Server 迁移到 Postgres 来加速我在各种仪表板中的数据集市的分析处理。 为了获得一个示例查询来比较速度,我在 MS SQL Server 上运行了查询分析器,同时引用了一个 BI 仪表板,它产生了类似的东西(我知道子查询中有冗余列):
SELECT COUNT(*)
FROM (
SELECT
BM.Key_Date, BM.[Actual Date], BM.[Month]
,BM.[Month Number], BM.[Month Year], BM.[No of Working Days]
,SDI.Key_Delivery, SDI.[Order Number], SDI.[Quantity SKU]
,SDI.[Quantity Sales Unit], SDI.[FactSales - GBP], SDI.[NNSA Capsules]
,SFI.[Ship-to], SFI.[Sold-to], SFI.[Sales Force Type], SFI.Region
,SFI.[Top Level Account], SFI.[Customer Organisation]
,EX.Rate
,PDI.[Product Description], PDI.[Product Type Group], PDI.[Product Type],
PDI.[Main Product Categories], PDI.Section, PDI.Family
FROM Fact.SalesDataInvoiced AS SDI
JOIN Dimension.SalesforceInvoiced AS SFI
ON SDI.[Key_Ship-to]=SFI.[Key_Ship-to]
JOIN Dimension.BillingMonth AS BM
ON SDI.[Key_Billing Month]=BM.Key_Date
JOIN Dimension.ProductDataInvoiced AS PDI
ON SDI.[Key_Product Code]=PDI.[Key_Product Code]
CROSS JOIN Dimension.Exchange AS EX
WHERE BM.[Actual Date] BETWEEN '20160101' AND '20211001'
) AS a
GROUP BY [Product Type], [Product Type Group],[Main Product Categories]
然后我在不同的相同笔记本电脑上安装了 Postgres 14(在 Centos 8 上)和 MS SQL Server Developer 2017(在 Windows 10 上),并从相同的 csv 数据文件创建了一个数据库和表,以启用上述查询的复制。
使用索引运行 Postgres 查询的速度比不使用索引的 MS SQL 慢很多。 向 MS SQL 添加索引几乎可以立即产生结果。
由于处理时间的差异,我什至安装了带有 Postgres14 的 Citus,并将 Fact.SalesDataInvoiced 创建为柱状表(这使处理时间变得更糟)。 我在 postgresql.conf 中使用过内存设置,但似乎没有任何东西可以实现与 MSSQL 相媲美的速度。
解释分析表明,尽管有索引,但它总是对所有表进行顺序扫描。强制索引扫描对处理时间没有任何影响。
我认为 Postgres 使用集群和分区会表现得更好吗?即使是这种情况,像我试图在单机上运行的简单查询肯定会更快?
表格详情
Dimension.BillingMonth 记录 120, 主键是 KeyDate, KeyDate 上的聚集唯一索引
Dimension.Exchange 记录 1
Dimension.ProductDataInvoiced 记录 275563, 主键是 KeyProduct, KeyProduct 上的聚集唯一索引
Dimension.SalesforceInvoiced 记录 377414, 主键是 KeyShipTo, KeyShipTo 上的聚集唯一索引
Fact.SalesDataInvoiced 记录 43807943, KeyShipTo、KeyProduct、KeyBillingMonth 上的非聚集唯一索引
如前所述,任何帮助将不胜感激,我确定我一定遗漏了一些明显的东西。
非常感谢。
大卫
感谢您的回复。我在下面放置了其他信息。
忘记添加我的 postgres 性能问题是在我执行完全真空和重新索引之后。在导入数据并创建索引后,我执行了这些维护任务。
查询 pg_indexes 后的输出
tablename | indexname | indexdef |
---|---|---|
BillingMonth | BillingMonth_pkey | CREATE UNIQUE INDEX BillingMonth_pkey ON public.BillingMonth USING btree (KeyDate) |
ProductDataInvoiced | ProductDataInvoiced_pkey | CREATE UNIQUE INDEX ProductDataInvoiced_pkey ON public.ProductDataInvoiced USING btree (KeyProductCode) |
SalesforceInvoiced | SalesforceInvoiced_pkey | CREATE UNIQUE INDEX SalesforceInvoiced_pkey ON public.SalesforceInvoiced USING btree (KeyShipTo) |
SalesDataInvoiced | CI_SalesData | CREATE INDEX CI_SalesData ON public.SalesDataInvoiced USING btree (KeyShipTo, KeyProductCode, KeyBillingMonth) |
运行 EXPLAIN (ANALYZE, BUFFERS) 后的输出
Finalize GroupAggregate (cost=1435439.30..1435565.71 rows=480 width=53) (actual time=25960.468..25973.326 rows=31 loops=1)
Group Key: pdi."ProductType", pdi."ProductTypeGroup", pdi."MainProductCategories"
Buffers: shared hit=71246 read=859119
-> Gather Merge (cost=1435439.30..1435551.31 rows=960 width=53) (actual time=25960.458..25973.282 rows=89 loops=1)
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=71246 read=859119
-> Sort (cost=1434439.28..1434440.48 rows=480 width=53) (actual time=25956.982..25956.989 rows=30 loops=3)
Sort Key: pdi."ProductType", pdi."ProductTypeGroup", pdi."MainProductCategories"
Sort Method: quicksort Memory: 28kB
Buffers: shared hit=71246 read=859119
Worker 0: Sort Method: quicksort Memory: 29kB
Worker 1: Sort Method: quicksort Memory: 29kB
-> Partial HashAggregate (cost=1434413.10..1434417.90 rows=480 width=53) (actual time=25956.878..25956.895 rows=30 loops=3)
Group Key: pdi."ProductType", pdi."ProductTypeGroup", pdi."MainProductCategories"
Batches: 1 Memory Usage: 49kB
Buffers: shared hit=71230 read=859119
Worker 0: Batches: 1 Memory Usage: 49kB
Worker 1: Batches: 1 Memory Usage: 49kB
-> Parallel Hash Join (cost=62124.74..1327935.46 rows=10647764 width=45) (actual time=285.864..19240.004 rows=14602648 loops=3)
Hash Cond: (sdi."KeyShipTo" = sfi."KeyShipTo")
Buffers: shared hit=71230 read=859119
-> Hash Join (cost=19648.48..1257508.51 rows=10647764 width=49) (actual time=204.794..12862.063 rows=14602648 loops=3)
Hash Cond: (sdi."KeyProductCode" = pdi."KeyProductCode")
Buffers: shared hit=32264 read=859119
-> Hash Join (cost=3.67..1091456.95 rows=10647764 width=8) (actual time=0.143..7076.104 rows=14602648 loops=3)
Hash Cond: (sdi."KeyBillingMonth" = bm."KeyDate")
Buffers: shared hit=197 read=859119
-> Parallel Seq Scan on "SalesData_Invoiced" sdi (cost=0.00..1041846.10 rows=18253310 width=12) (actual
time=0.071..2585.596 rows=14602648 loops=3)
Buffers: shared hit=194 read=859119
-> Hash (cost=2.80..2.80 rows=70 width=4) (actual time=0.049..0.050 rows=70 loops=3)
Hash Cond: (sdi."KeyBillingMonth" = bm."KeyDate")
Buffers: shared hit=197 read=859119
-> Parallel Seq Scan on "SalesData_Invoiced" sdi (cost=0.00..1041846.10 rows=18253310 width=12) (actual
time=0.071..2585.596 rows=14602648 loops=3)
Buffers: shared hit=194 read=859119
-> Hash (cost=2.80..2.80 rows=70 width=4) (actual time=0.049..0.050 rows=70 loops=3)
Buckets: 1024 Batches: 1 Memory Usage: 11kB
Buffers: shared hit=3
-> Seq Scan on "BillingMonth" bm (cost=0.00..2.80 rows=70 width=4) (actual time=0.012..0.028
rows=70 loops=3)
Filter: (("ActualDate" >= '2016-01-01'::date) AND ("ActualDate" <= '2021-10-01'::date))
Rows Removed by Filter: 50
Buffers: shared hit=3
-> Hash (cost=16200.27..16200.27 rows=275563 width=49) (actual time=203.237..203.238 rows=275563 loops=3)
Buckets: 524288 Batches: 1 Memory Usage: 26832kB
Buffers: shared hit=32067
-> Nested Loop (cost=0.00..16200.27 rows=275563 width=49) (actual time=0.034..104.143 rows=275563 loops=3)
Buffers: shared hit=32067
-> Seq Scan on "Exchange" ex (cost=0.00..1.01 rows=1 width=0) (actual time=0.024..0.024 rows=
1 loops=3)
Buffers: shared hit=3
-> Seq Scan on "ProductData_Invoiced" pdi (cost=0.00..13443.63 rows=275563 width=49) (actual
time=0.007..48.176 rows=275563 loops=3)
Buffers: shared hit=32064
-> Parallel Hash (cost=40510.56..40510.56 rows=157256 width=4) (actual time=79.536..79.536 rows=125805 loops=3)
Buckets: 524288 Batches: 1 Memory Usage: 18912kB
Buffers: shared hit=38938
-> Parallel Seq Scan on "Salesforce_Invoiced" sfi (cost=0.00..40510.56 rows=157256 width=4) (actual time=
0.011..42.968 rows=125805 loops=3)
Buffers: shared hit=38938
Planning:
Buffers: shared hit=426
Planning Time: 1.936 ms
Execution Time: 25973.709 ms
(55 rows)
【问题讨论】:
您是否在运行查询之前运行了VACUUM ANALYZE
(在首次数据导入之后或创建新索引之后)?你的 PostgreSQL 索引是什么?
1) 主键 1a) 自然键 2) 外键 2a) 支持 FK 的索引 3) 可能是特定查询的一些附加索引 4) 服务器配置常量。
检查查询的EXPLAIN (ANALYZE, BUFFERS)
输出。
感谢您的回复。我现在在我的帖子中添加了其他信息
【参考方案1】:
首先,请记住在重建索引后运行VACUUM ANALYZE
,或者有时在导入大量数据后运行。 (VACUUM FULL
主要用于操作系统回收磁盘空间,后期还需要分析,尤其是重建索引后。)
从您的查询看来,您的主表是 SalesDataInvoiced
(SDI
),并且如果可能的话,您希望在 KeyBillingMonth
上使用索引(因为这是您放置的主要限制)。通常,您还需要索引,至少在用于连接的列上的其他表上。
正如multi-column indexes in PostgreSQL 的文档所说:
多列 B 树索引可用于涉及索引列的任何子集的查询条件,但当前导(最左侧)列存在约束时,索引效率最高。确切的规则是前导列上的等式约束,加上没有等式约束的第一列上的任何不等式约束,将用于限制扫描的索引部分。在索引中检查这些列右侧的列的约束,因此它们可以正确保存对表的访问,但不会减少必须扫描的索引部分。例如,给定 (a, b, c) 上的索引和查询条件 WHERE a = 5 AND b >= 42 AND c = 77 的索引条目将被跳过,但仍然必须扫描它们。 这个索引原则上可以用于对 b 和/或 c 有约束而对 a 没有约束的查询——但是必须扫描整个索引,所以在大多数情况下,规划器更喜欢顺序表扫描使用索引。
在您的示例中,您要在 (KeyBillingMonth
) 上使用约束的主列位于第三位,因此不太可能使用。
CREATE INDEX CI_SalesData ON public.SalesDataInvoiced
USING btree (KeyShipTo, KeyProductCode, KeyBillingMonth)
创建它应该使它更有可能被使用:
CREATE INDEX ON SalesDataInvoiced(KeyBillingMonth);
然后,运行 VACUUM ANALYZE
并再次尝试查询。
您可能还希望在BillingMonth(ActualDate)
上建立索引,但这并不一定有用,因为似乎行数很少(而且大多数行都在您的查询中返回)。
尚不清楚BillingMonth
表的用途。如果它基本上是关于将ActualDate
截断为每月的第一天,例如,您可以摆脱BillingMonth
上的连接并直接使用SalesDataInvoiced.KeyBillingMonth
上的约束。例如... WHERE SDI.KeyBillingMonth BETWEEN '2016-01-01' AND '2021-10-01' ...
。
作为旁注,据我所知,BETWEEN
包含其上限。我想这样的查询是为了代表一些月度统计数据,因此可能不应该包括 2021 年 10 月 1 日(但不包括该月剩余时间)的内容。
【讨论】:
谢谢布鲁诺,我有一些好的地方可以跟进。我的初始查询只是与仪表板交互时从 Power BI 触发的众多查询之一。 BillingMonth 表是我的 Dates 表的缩减版本,包含一个月中的工作日数等信息。该表的主键是 KeyDate,它是一个整数值。 ActualDate 保存为日期,用于 Power BI 中的日期计算。 嗨 Bruno,我已经尝试了为 BillingMonth 表(将 ActualDate 添加为索引)和 SalesData_Invoiced 表建议的索引(首先尝试仅在 BillingMonth 上建立索引,然后按照 KeyBillingMonth 的顺序创建非聚集索引, KeyProductCode、KeyShipTo)。不幸的是,这些更改都没有提高查询速度。 VACUUM ANALYZE 在为每次更改重新索引后运行。如果您有任何其他想法,我非常愿意尝试一下:) 您似乎在谈论“集群/非集群”索引。 AFAIK,这在 SQL Server 和 PostgreSQL 中没有相同的含义。 (在 PostgreSQL 中,主索引不能确定数据的页面位置。)我不确定您是否真的使用过“CLUSTERED”,但我一开始会避免使用。我会尝试简化查询,首先是摆脱 CROSS JOIN (这是无用的),并且可能一次添加一个连接以查看何时使用索引而不使用索引。也许尝试使用已知的 KeyBillingMonth 值(可能只是一个或“IN”条件),看看它是否会改变行为。以上是关于提高 Postgres 性能的主要内容,如果未能解决你的问题,请参考以下文章