SQL PostgreSQL parallel_workers 设置与 CPU 核数匹配经验公式

2次阅读

parallel_workers 安全起点为 min(4, (cpu核数−2)/2),需兼顾cpu核数、并发度与负载类型;设过高易致线程争抢和oom,应结合work_mem调优并以explain验证实际执行计划。

SQL PostgreSQL parallel_workers 设置与 CPU 核数匹配经验公式

parallel_workers 设多少才不浪费 CPU

postgresqlparallel_workers 不是设得越高越好,它受实际可用 CPU 核数、查询并发度和工作负载类型三重限制。盲目设成 32 或等于物理核数,反而可能触发大量线程争抢、上下文切换开销暴涨,查询变慢。

  • 单个查询最多启动 parallel_workers + 1 个进程(1 主 + N 工作进程),所以设 8 意味着单次并行扫描最多用 9 个 CPU 时间片
  • 默认值为 0,表示“由优化器决定”,但多数场景下它过于保守(常只用 2 个 worker)
  • 安全起点:取 min(4, (CPU 核数 - 2) / 2) —— 留出 2 核给系统和其他连接,再对半分给并行任务
  • 若服务器长期 CPU 使用率 6 或 8;一旦观察到 pg_stat_activity.state = 'active'wait_event = 'ClientRead' 频繁出现,说明 worker 在等 I/O 或锁,再加没意义

如何在表级别覆盖全局 parallel_workers

全局 max_parallel_workers_per_gather 是总闸门,但真正起作用的是每个表的 parallel_workers 存储参数。没显式设置,优化器就按数据量估算,往往不准。

  • ALTER table orders SET (parallel_workers = 4); 直接生效,无需重启
  • 该设置只影响后续对该表的顺序扫描(Seq Scan)、位图扫描(Bitmap Heap Scan)等支持并行的操作,索引扫描(Index Scan)默认不并行
  • 若表有分区,需对每个子表单独设置;父表上的设置不会继承
  • 执行 EXPLAIN (ANALYZE, BUFFERS) select count(*) FROM orders; 后看输出里是否出现 Gather 节点及下面的 Workers Launched 数字,这是唯一验证方式

为什么开了 parallel_workers 却没走并行计划

常见错觉是“设了就一定并行”,其实 PostgreSQL 有一串硬性门槛,缺一不可。

  • max_parallel_workers_per_gather 必须 > 0(全局配置,默认为 2)
  • 查询必须能使用顺序扫描或位图扫描——带高选择性条件的 WHERE id = ? 几乎永远走索引扫描,不触发并行
  • 目标表大小要超过 min_parallel_table_scan_size(默认 8MB),小表强制并行反而更慢
  • 当前事务隔离级别不能是 serializable,该级别下并行被禁用
  • 如果查询里含不支持并行的函数(如 pg_backend_pid()current_user),整个计划降级为串行

并行 worker 吃光内存导致 OOM 怎么办

每个 worker 进程会独立申请 work_mem 内存,设 parallel_workers = 4work_mem = 64MB,单个查询最高可能占用 (4 + 1) × 64MB = 320MB,多个大查询并发就容易触发 linux OOM Killer 杀掉 backend。

  • 优先调低 work_mem(比如从 64MB 改为 16MB),比减少 parallel_workers 更治本
  • 监控 pg_stat_statementsmax_exec_timetotal_plan_time 突增的查询,它们往往是并行失控的源头
  • pg_stat_progress_parallel 视图实时看哪些查询正在用几个 worker、运行多久、是否卡在 barrier 等待
  • 不要依赖 shared_buffers 缓解——worker 进程不共享这块内存,它只用于缓存页,不影响 worker 内存分配

复杂点在于:同一张表,在 OLAP 大宽表聚合时需要高并行,在 OLTP 点查场景下又必须关闭并行。没有一刀切的数值,得靠 EXPLAIN 看真实执行计划,而不是凭核数拍脑袋。

text=ZqhQzanResources