{"Entry":{"collection":"plan","key":"gather-merge","name":"Gather Merge","aliases":["Gather Merge","GatherMerge","T_GatherMerge"],"metadata":{"aliases":["Gather Merge","GatherMerge","T_GatherMerge"],"category":"Parallelism","content_hash":"60a783e74d1850b9432b5b97c5799704bd4e9d917faa54bb6fb742f37bf78f8d","derived_content_hash":"d444561c3b7cd698a7cbe68538401d7475fe4958abbc15efaede535d4cd7f2c2","imported_at":"2026-09-30T00:40:44.19904+08:00","name":"Gather Merge","name_zh":"GatherMerge","slug":"gather-merge","summary":"Merges ordered tuple streams from parallel workers and any participating leader execution."}},"Definition":{"Collection":"plan","Key":"gather-merge","SourceDatabase":"center","Version":"18","SourceTable":"plan_node","SourceKey":"gather-merge","SourceRevision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","Facts":{"comparison_data":{"initializer":"ExecInitGatherMerge","memory_mechanism":"unclassified","node_tag":"T_GatherMerge","parallel_callbacks":[],"partial_modes":[],"strategies":[],"text_names":["Gather Merge"]},"comparison_hash":"7a1c97656626af0791de9743def719924b5c292f47f0634adc2b0e6e4fd2ad21","description":["Merges ordered tuple streams from parallel workers and any participating leader execution."],"description_zh":"归并并行工作进程以及参与执行的领导进程的有序输出流。","description_zh_html":"\u003cp\u003e归并并行工作进程以及参与执行的领导进程的有序输出流。\u003c/p\u003e\u003cp\u003e核心节点标识：\u003ccode\u003eT_GatherMerge\u003c/code\u003e；EXPLAIN 名称：\u003ccode\u003eGather Merge\u003c/code\u003e。\u003c/p\u003e\u003cp\u003e本版本源码中的并行执行或观测回调：该节点实现中未提取到此类回调。回调的存在不表示该节点必然支持共享并行扫描；在并行工作进程中执行也不等于具有并行感知能力。\u003c/p\u003e\u003ch3\u003e同版本中文手册节选\u003c/h3\u003e\u003cp\u003e\n\u003cspan class=\"productname\"\u003ePostgreSQL\u003c/span\u003e 通过分两个阶段进行聚合来支持并行聚合。首先，每个参与查询并行部分的进程执行一个聚合步骤，为该进程所见到的每个分组产生一个部分结果。这在计划中体现为一个 \u003ccode class=\"literal\"\u003ePartial Aggregate\u003c/code\u003e 节点。然后，部分结果通过 \u003ccode class=\"literal\"\u003eGather\u003c/code\u003e 或 \u003ccode class=\"literal\"\u003eGather Merge\u003c/code\u003e 传送给领导者。最后，领导者会把来自所有工作进程的结果再次聚合，以产生最终结果。这在计划中体现为一个 \u003ccode class=\"literal\"\u003eFinalize Aggregate\u003c/code\u003e 节点。\n  \u003c/p\u003e\u003cp\u003e\n    由于 \u003ccode class=\"literal\"\u003eFinalize Aggregate\u003c/code\u003e 节点运行在领导者进程上，因此对于那些相对于输入行数会产生较多分组的查询，查询规划器会认为它不太有利。例如，在最坏情况下，\u003ccode class=\"literal\"\u003eFinalize Aggregate\u003c/code\u003e 节点看到的分组数可能与所有工作进程在 \u003ccode class=\"literal\"\u003ePartial Aggregate\u003c/code\u003e 阶段看到的输入行数一样多。对于这种情况，使用并行聚合显然不会带来性能收益。查询规划器会在规划过程中考虑这一点，因此在这种场景下不太可能选择并行聚合。\n  \u003c/p\u003e\u003cp\u003e\n    并行聚合并非在所有情况下都受支持。每个聚合都必须是\u003ca class=\"link\" href=\"/zh/docs/18/parallel-safety.html\"\u003e并行安全\u003c/a\u003e的，并且必须具有合并函数。如果该聚合具有类型为 \u003ccode class=\"literal\"\u003einternal\u003c/code\u003e 的转移状态，那么它还必须具有序列化和反序列化函数。更多细节请参见 \u003ca class=\"xref\" href=\"/zh/docs/18/sql-createaggregate.html\"\u003e\u003cspan class=\"refentrytitle\"\u003eCREATE AGGREGATE\u003c/span\u003e\u003c/a\u003e。如果任何聚合函数调用包含 \u003ccode class=\"literal\"\u003eDISTINCT\u003c/code\u003e 或 \u003ccode class=\"literal\"\u003eORDER BY\u003c/code\u003e 子句，则不支持并行聚合。对于有序集聚合，或者当查询涉及 \u003ccode class=\"literal\"\u003eGROUPING SETS\u003c/code\u003e 时，也不支持并行聚合。只有当查询涉及的所有连接也都属于计划并行部分时，才能使用并行聚合。\n  \u003c/p\u003e\u003cp\u003e\u003ca href=\"/zh/docs/18/parallel-plans.html#PARALLEL-AGGREGATION\"\u003e查看完整中文手册与上下文\u003c/a\u003e。\u003c/p\u003e\u003cp\u003e本页开头的中文说明为 PG.CENTER 对编辑说明的翻译；手册节选保留对应版本中文文档原文。源码信息及手册示例不代表重新进行过运行测试。\u003c/p\u003e","evidence_kind":"source and documentation","explain_names":["Gather Merge"],"explain_prefixes":["Parallel","Async"],"facts":[{"label":"Core node tag","value":"T_GatherMerge"},{"label":"Structured EXPLAIN Node Type","value":"Gather Merge"},{"label":"Inputs","value":"One ordered parallel child plan"},{"label":"Output","value":"Merged ordered tuples"},{"label":"Executor initializer","value":"ExecInitGatherMerge"},{"label":"Memory mechanism","value":"unclassified"}],"facts_zh":[{"label":"核心节点标签","value":"T_GatherMerge"},{"label":"结构化 EXPLAIN 节点类型","value":"Gather Merge"},{"label":"输入","value":"一个有序并行子计划"},{"label":"输出","value":"按顺序归并的元组"},{"label":"执行器初始化函数","value":"ExecInitGatherMerge"},{"label":"内存机制","value":"unclassified"}],"memory":{"description":"This extraction does not assign a universal memory limit or spill policy to this node. Inspect the same-build implementation and its expressions or provider.","evidence":[{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/nodeGatherMerge.c","path":"src/backend/executor/nodeGatherMerge.c","sha256":"5d37ccad1d10d72a567a134bc92f044c584d794c09c9f5820e3ce8e0744d0b1a","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"}],"mechanism":"unclassified","source_notes":[]},"node_tag":"T_GatherMerge","parallel_callbacks":[],"partial_modes":[],"related":[{"label":"EXPLAIN","url":"/wiki/sql/explain/?v=18"},{"label":"Using EXPLAIN","url":"/docs/18/using-explain.html"},{"label":"Parallel plans","url":"/docs/18/parallel-plans.html"},{"label":"enable_gathermerge","url":"/wiki/guc/enable_gathermerge/?v=18"},{"label":"max_parallel_workers_per_gather","url":"/wiki/guc/max_parallel_workers_per_gather/?v=18"}],"release":{"channel":"stable","label":"18.6","major":"18","ref":"PostgreSQL 18.6 source archive","revision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","source_snapshot_utc":"","source_url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},"runtime_verified":false,"sections":[{"paragraphs":["Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.","Text names recorded by this source: Gather Merge.","Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node."],"title":"EXPLAIN names and attributes"},{"paragraphs":["This extraction does not assign a universal memory limit or spill policy to this node. Inspect the same-build implementation and its expressions or provider."],"title":"Memory and temporary storage"},{"paragraphs":["The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.","Callbacks in this build: none extracted from this node implementation."],"title":"Parallel execution and instrumentation"},{"paragraphs":["PostgreSQL supports parallel aggregation by aggregating in two stages. First, each process participating in the parallel portion of the query performs an aggregation step, producing a partial result for each group of which that process is aware. This is reflected in the plan as a Partial Aggregate node. Second, the partial results are transferred to the leader via Gather or Gather Merge . Finally, the leader re-aggregates the results across all workers in order to produce the final result. This is reflected in the plan as a Finalize Aggregate node."],"title":"Same-version manual discussion"},{"paragraphs":["nodeGatherMerge.c Scan a plan in multiple workers, and do order-preserving merge.","When we read tuples from workers, it's a good idea to read several at once for efficiency when possible: this minimizes context-switching overhead. But reading too many at a time wastes memory without improving performance. We'll read up to MAX_TUPLE_STORE tuples (in addition to the first one).","Pending-tuple array for each worker. This holds additional tuples that we were able to fetch from the worker, but can't process yet. In addition, this struct holds the \"done\" flag indicating the worker is known to have no more tuples. (We do not use this struct for the leader; we don't keep any pending tuples for the leader, and the need_to_scan_locally flag serves as its \"done\" indicator.)","GatherMerge doesn't support checking a qual (it's always more efficient to do it in the child node).","Leader may access ExecProcNode result directly (if need_to_scan_locally), or from workers via tuple queue. So we can't trivially rely on the slot type being fixed for expressions evaluated within this node."],"title":"Executor implementation notes"},{"code":"case T_GatherMerge:\n\t\t\tpname = sname = \"Gather Merge\";\n\t\t\tbreak;","title":"EXPLAIN identity in core source"}],"sections_zh":[{"paragraphs":["结构化格式使用上述 Node Type。文本格式名称还可能包含操作、策略、连接类型、扫描方向或聚合阶段属性。","此源码记录的文本名称：Gather Merge.","并行感知与并行安全是不同的计划属性。在并行工作进程内运行的节点不一定是并行感知节点。"],"title":"EXPLAIN 名称与属性"},{"paragraphs":["本次抽取不为此节点设定统一的内存上限或落盘策略。请查看同一构建的实现、相关表达式或提供方。"],"title":"内存与临时存储"},{"paragraphs":["以下源码回调可以协调执行或收集工作进程的测量数据。回调存在不代表该节点普遍支持共享并行扫描或共享状态。","此构建的回调：none extracted from this node implementation."],"title":"并行执行与运行信息采集"},{"paragraphs":["PostgreSQL 通过分两个阶段进行聚合来支持并行聚合。首先，每个参与查询并行部分的进程执行一个聚合步骤，为该进程所见到的每个分组产生一个部分结果。这在计划中体现为一个 Partial Aggregate 节点。然后，部分结果通过 Gather 或 Gather Merge 传送给领导者。最后，领导者会把来自所有工作进程的结果再次聚合，以产生最终结果。这在计划中体现为一个 Finalize Aggregate 节点。"],"title":"同版本手册说明"},{"paragraphs":["nodeGatherMerge.c：在多个工作进程中扫描计划并保持顺序地归并。","从工作进程读取元组时，尽可能一次读取多个可降低上下文切换开销。但一次读取过多会浪费内存而无助于性能。因此除第一个元组外，最多再读取 MAX_TUPLE_STORE 个。","每个工作进程的待处理元组数组，保存已从工作进程读取但尚不能处理的额外元组。此结构还保存 done 标志，表示已知该工作进程不再有元组。（领导进程不使用此结构：不为其保存待处理元组，并用 need_to_scan_locally 标志表示是否完成。）","GatherMerge 不支持检查条件，因为在子节点中检查总是更高效。","领导进程可能直接访问 ExecProcNode 的结果（need_to_scan_locally 为真时），也可能通过元组队列接收工作进程的结果。因此，不能简单假定在此节点内计算表达式时，元组槽类型始终固定。"],"title":"执行器实现说明"},{"code":"case T_GatherMerge:\n\t\t\tpname = sname = \"Gather Merge\";\n\t\t\tbreak;","title":"核心源码中的 EXPLAIN 标识"}],"source_inventory":{"executor":"src/backend/executor/execProcnode.c","explain":"src/backend/commands/explain.c","implementation":"src/backend/executor/nodeGatherMerge.c"},"sources":[{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/commands/explain.c:1441","line":1441,"path":"src/backend/commands/explain.c","sha256":"34c86d6070224a0e981efef51f79101d6d505e5874f1684ace183034bab14bb4","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/execProcnode.c:360","line":360,"path":"src/backend/executor/execProcnode.c","sha256":"f8a06a3f539077249b20664b2812433db6d7bd12b2c0ca633525db43d06f112a","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/nodeGatherMerge.c","path":"src/backend/executor/nodeGatherMerge.c","sha256":"5d37ccad1d10d72a567a134bc92f044c584d794c09c9f5820e3ce8e0744d0b1a","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/include/nodes/plannodes.h","path":"src/include/nodes/plannodes.h","sha256":"52422b327a8049fbbb20d8b96008a0fc0a6fafa60f7eff3c695d5b2e83830120","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"label":"PostgreSQL 18.6 · parallel-plans","path":"parallel-plans.html","sha256":"62207d207bead82b01b59dc119c4f95856f08655cc11d4699a05a40867ed2070","url":"/docs/18/parallel-plans.html#PARALLEL-AGGREGATION"}],"strategies":[],"tables":[{"columns":[{"key":"label","label":"Text-format label"},{"key":"identity","label":"Structured node identity"}],"key":"explain-labels","rows":[{"identity":"Gather Merge","label":"Gather Merge"}],"title":"EXPLAIN labels in this source build"}],"tables_zh":[{"columns":[{"key":"label","label":"文本格式标签"},{"key":"identity","label":"结构化节点标识"}],"key":"explain-labels","rows":[{"identity":"Gather Merge","label":"Gather Merge"}],"title":"本构建中的 EXPLAIN 标签"}],"zh_from":{"authored_translation":"authored-translation-of-reviewed-guidance","body_sha256":"f6e22c79a4a8a884239035d0b3f3ec0d48f7c3308eaeed8bd286dd4e384bffbd","kind":"same-version-chinese-manual-with-authored-translation-of-reviewed-guidance","manual":{"anchor":"PARALLEL-AGGREGATION","build_id":107,"extraction":"concept-introduction","fragment_sha256":"f882e19de96b3173fd88cb5de956b736c655192b92b7cdbf544225b9bedc6662","locale":"zh-Hans","page_key":"parallel-plans.html","source_hash":"1ef9f9a3740440f8dcdf04c2a2dfe2f7d48c66f2e0b102d15df580bff22a6857","source_revision":"unknown:rendered-translation-baseline","translated_from_revision":null,"translation_revision":"git:b1b500221dbe5437cfca004d6b04d3f6e422765d;html-sha256:08e987a66af27b98444a8057c256314eafa13611a3fb9e888214fc436824244b","url":"/zh/docs/18/parallel-plans.html#PARALLEL-AGGREGATION","version":"18"},"runtime_rerun":false,"source_facts_revision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}},"ManualEvidence":{"release":{"channel":"stable","label":"18.6","major":"18","ref":"PostgreSQL 18.6 source archive","revision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","source_snapshot_utc":"","source_url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},"sources":[{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/commands/explain.c:1441","line":1441,"path":"src/backend/commands/explain.c","sha256":"34c86d6070224a0e981efef51f79101d6d505e5874f1684ace183034bab14bb4","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/execProcnode.c:360","line":360,"path":"src/backend/executor/execProcnode.c","sha256":"f8a06a3f539077249b20664b2812433db6d7bd12b2c0ca633525db43d06f112a","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/nodeGatherMerge.c","path":"src/backend/executor/nodeGatherMerge.c","sha256":"5d37ccad1d10d72a567a134bc92f044c584d794c09c9f5820e3ce8e0744d0b1a","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/include/nodes/plannodes.h","path":"src/include/nodes/plannodes.h","sha256":"52422b327a8049fbbb20d8b96008a0fc0a6fafa60f7eff3c695d5b2e83830120","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"label":"PostgreSQL 18.6 · parallel-plans","path":"parallel-plans.html","sha256":"62207d207bead82b01b59dc119c4f95856f08655cc11d4699a05a40867ed2070","url":"/docs/18/parallel-plans.html#PARALLEL-AGGREGATION"}]},"MeasuredEvidence":{"runtime_verified":false}},"Text":{"Collection":"plan","Key":"gather-merge","SourceDatabase":"center","Version":"18","Locale":"en","Title":"Gather Merge","Summary":"Merges ordered tuple streams from parallel workers and any participating leader execution.","BodyHTML":"\u003cp\u003eMerges ordered tuple streams from parallel workers and any participating leader execution.\u003c/p\u003e","SourceRevision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","ContentHash":"5a00589cae67dc24467b71da3df8aee805659578ac0a508a29746affca1d6b6f","Payload":{"description":["Merges ordered tuple streams from parallel workers and any participating leader execution."],"related":[{"label":"EXPLAIN","url":"/wiki/sql/explain/?v=18"},{"label":"Using EXPLAIN","url":"/docs/18/using-explain.html"},{"label":"Parallel plans","url":"/docs/18/parallel-plans.html"},{"label":"enable_gathermerge","url":"/wiki/guc/enable_gathermerge/?v=18"},{"label":"max_parallel_workers_per_gather","url":"/wiki/guc/max_parallel_workers_per_gather/?v=18"}],"sections":[{"paragraphs":["Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.","Text names recorded by this source: Gather Merge.","Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node."],"title":"EXPLAIN names and attributes"},{"paragraphs":["This extraction does not assign a universal memory limit or spill policy to this node. Inspect the same-build implementation and its expressions or provider."],"title":"Memory and temporary storage"},{"paragraphs":["The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.","Callbacks in this build: none extracted from this node implementation."],"title":"Parallel execution and instrumentation"},{"paragraphs":["PostgreSQL supports parallel aggregation by aggregating in two stages. First, each process participating in the parallel portion of the query performs an aggregation step, producing a partial result for each group of which that process is aware. This is reflected in the plan as a Partial Aggregate node. Second, the partial results are transferred to the leader via Gather or Gather Merge . Finally, the leader re-aggregates the results across all workers in order to produce the final result. This is reflected in the plan as a Finalize Aggregate node."],"title":"Same-version manual discussion"},{"paragraphs":["nodeGatherMerge.c Scan a plan in multiple workers, and do order-preserving merge.","When we read tuples from workers, it's a good idea to read several at once for efficiency when possible: this minimizes context-switching overhead. But reading too many at a time wastes memory without improving performance. We'll read up to MAX_TUPLE_STORE tuples (in addition to the first one).","Pending-tuple array for each worker. This holds additional tuples that we were able to fetch from the worker, but can't process yet. In addition, this struct holds the \"done\" flag indicating the worker is known to have no more tuples. (We do not use this struct for the leader; we don't keep any pending tuples for the leader, and the need_to_scan_locally flag serves as its \"done\" indicator.)","GatherMerge doesn't support checking a qual (it's always more efficient to do it in the child node).","Leader may access ExecProcNode result directly (if need_to_scan_locally), or from workers via tuple queue. So we can't trivially rely on the slot type being fixed for expressions evaluated within this node."],"title":"Executor implementation notes"},{"code":"case T_GatherMerge:\n\t\t\tpname = sname = \"Gather Merge\";\n\t\t\tbreak;","title":"EXPLAIN identity in core source"}],"tables":[{"columns":[{"key":"label","label":"Text-format label"},{"key":"identity","label":"Structured node identity"}],"key":"explain-labels","rows":[{"identity":"Gather Merge","label":"Gather Merge"}],"title":"EXPLAIN labels in this source build"}]}},"RequestedLocale":"en","Fallback":false,"Versions":["10","11","12","13","14","15","16","17","18","19","20"],"Locales":["en","zh-Hans"],"Signatures":null,"Spellings":null,"SQLState":null,"Evidence":null}
