二月 25, 2025
摘要:在本教程中,您将了解 PostgreSQL 中并行查询不起作用的主要原因。
目录
介绍
PostgreSQL 并行执行查询的能力是一项强大的功能,可以显著提高查询性能,尤其是在大型数据集上。但是,与所有资源一样,并行工作进程的数量是有限的。当没有足够可用的工作进程时,PostgreSQL 可能会将并行查询降级为串行(非并行)执行。这听起来很合理,除非降级查询的执行时间远超过应用程序所需的响应时间。
本文探讨了并行查询的工作原理、触发降级的原因,以及如何监控和优化并行工作进程的使用以防止性能瓶颈。我们还将探索并行查询降级的解决方案。
并行查询的工作原理
当 PostgreSQL 并行执行一个查询时,它会将一个或多个计划节点(任务)的工作划分到多个进程(称为并行工作进程)中。这些工作进程会协作同步地处理部分数据,从而减少扫描、连接和聚合等操作的查询时间。数据库分配并行工作进程,直到达到 max_parallel_workers 参数设置定义的最大值。如果无法分配并行工作进程,则查询会降级为串行执行。
并行查询降级的原因
并行查询可能会降级的主要原因有以下几个:
- 工作进程池耗尽:PostgreSQL 对它可以派生出的并行工作进程总数有限制,由 max_parallel_workers 参数控制。如果达到此限制,新的并行查询将无法获取所需的工作进程,从而可能会回退到串行执行。
- 每个查询的工作进程数限制:即使有可用的工作进程,每个查询也受 max_parallel_workers_per_gather 设置的约束。如果达到或超过此阈值,则必须降低并行度或降级为串行执行,来运行其他查询。
- 繁忙的工作负载:在有许多查询请求并行工作进程的繁忙系统中,资源竞争可能会导致 PostgreSQL 降级某些查询,以避免系统过载。
- 优化器统计信息:表和索引的统计信息可以引导优化器选择串行执行路径而不是并行执行路径。
模拟问题
下面的 SQL 会创建一个大表,该表将会被查询,以模拟并行查询的优势和查询降级时的影响。
CREATE TABLE large_table AS
SELECT generate_series(1, 10000000) AS id,
random() * 1000 AS value;
运行并行查询:
SET max_parallel_workers_per_gather = 4;
EXPLAIN (ANALYZE)
SELECT * FROM large_table WHERE value > 500 ORDER BY id DESC;
上面有 4 个并行工作进程的查询在不到 3 秒的时间内运行完了。下面是用 EXPLAIN ANALYZE 返回的执行计划:
Gather Merge (cost=233180.33..827680.90 rows=4965151 width=12) (actual time=1683.415..2367.503 rows=5000337 loops=1)
Workers Planned: 4
Workers Launched: 4
-> Sort (cost=232180.27..235283.49 rows=1241288 width=12) (actual time=1653.855..1743.384 rows=1000067 loops=5)
Sort Key: id DESC
Sort Method: external merge Disk: 25640kB
Worker 0: Sort Method: external merge Disk: 25136kB
Worker 1: Sort Method: external merge Disk: 25616kB
Worker 2: Sort Method: external merge Disk: 25496kB
Worker 3: Sort Method: external merge Disk: 25536kB
-> Parallel Seq Scan on large_table (cost=0.00..85327.28 rows=1241288 width=12) (actual time=0.014..191.271 rows=1000067 loops=5)
Filter: (value > '500'::double precision)
Rows Removed by Filter: 999933
Planning Time: 0.215 ms
Execution Time: 2511.247 ms
(15 rows)
再次执行查询,但这次禁用并行查询,以模拟查询的降级。
SET max_parallel_workers_per_gather = 0;
EXPLAIN (ANALYZE)
SELECT * FROM large_table WHERE value > 500 ORDER BY id DESC;
在禁用并行查询后,查询响应时间现在刚好超过了 10 秒。在这个简单的例子中,7 秒看起来没什么大不了的。但是,假设实际示例的响应时间不是 7 秒,而是 7 分钟呢?这正是我们要防止出现的性能下降。下面的输出显示了非并行执行的计划。
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Sort (cost=816132.49..828545.37 rows=4965151 width=12) (actual time=10314.880..10699.870 rows=5000337 loops=1)
Sort Key: id DESC
Sort Method: external merge Disk: 127368kB
-> Seq Scan on large_table (cost=0.00..179069.14 rows=4965151 width=12) (actual time=0.012..691.074 rows=5000337 loops=1)
Filter: (value > '500'::double precision)
Rows Removed by Filter: 4999663
Planning Time: 0.199 ms
Execution Time: 10872.894 ms
(8 rows)
如何检测降级的并行查询
当并行查询降级为串行执行时,可能会导致查询时间更长。幸运的是,PostgreSQL 提供了几种识别此类降级的方法。
使用 EXPLAIN (ANALYZE)
查看上述执行计划的输出,当查询降级时,该计划没有了并行化的计划节点。
如果查询是串行的,则输出将如下所示:
Seq Scan on large_table (cost=0.00..179069.14 rows=4965151 width=12) (actual time=0.012..691.074 rows=5000337 loops=1)
但是并行查询会包含如下的条目:
Parallel Seq Scan on large_table (cost=0.00..85327.28 rows=1241288 width=12) (actual time=0.014..191.271 rows=1000067 loops=5)
监控正在运行的并行工作进程
您可以查询 pg_stat_activity 视图,以查看正在运行的并行工作进程的数量:
SELECT COUNT(1) AS running_workers
FROM pg_stat_activity
WHERE backend_type = 'parallel worker';
此外,您还可以将其与允许的工作进程总数进行比较:
SELECT current_setting('max_parallel_workers')::int AS max_workers;
如果running_workers
达到或超过max_workers
,则新的并行查询可能会降级。
并行查询降级解决方案
一种可行的解决方案是一个重试函数。该函数将在执行所需语句之前检查:有没有一定比例的并行工作进程可用。如果没有可用的工作进程,该函数将在指定的时间段内休眠并重新检查。可在此处找到这个过程的示例。
结论
PostgreSQL 中的并行查询提供了巨大的性能优势,但它们依赖于并行工作进程的可用性。当工作进程供不应求时,数据库会正常降级查询以确保系统稳定性,但会以牺牲性能为代价。通过了解并行查询降级是如何发生的,您可以更好地管理并行工作负载,并最大限度地减少对系统的影响。
用心的调优、监控和查询优化,可以帮助您充分利用 PostgreSQL 的并行查询功能,而不会遇到意外的降速。