【PostgreSQL 内核学习:平衡 K 路归并(Balanced k-way Merge)】

平衡 K 路归并(Balanced k-way Merge)

  • 一、引言
    • 提交信息
    • 优化目的与背景
      • [1. 历史背景](#1. 历史背景)
      • [2. 现代硬件现实](#2. 现代硬件现实)
      • [3. 新旧算法核心对比](#3. 新旧算法核心对比)
      • [4. 主要代码变更点](#4. 主要代码变更点)
        • [4.1 数据结构变化](#4.1 数据结构变化)
        • [4.2 关键概念解释](#4.2 关键概念解释)
        • [4.3 关键函数调整](#4.3 关键函数调整)
    • 源码解读
      • [mergeruns 函数](#mergeruns 函数)
        • [1. 函数整体说明](#1. 函数整体说明)
        • [2. 带中文注释的完整源码](#2. 带中文注释的完整源码)
        • [3. 详细功能分析](#3. 详细功能分析)
      • [init_slab_allocator 函数](#init_slab_allocator 函数)
        • [1. 函数整体说明](#1. 函数整体说明)
        • [2. 带中文注释的完整源码](#2. 带中文注释的完整源码)
        • [3. 修改点标记](#3. 修改点标记)
        • [4. 详细功能分析](#4. 详细功能分析)
      • [mergeonerun 函数](#mergeonerun 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [beginmerge 函数](#beginmerge 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [mergereadnext 函数](#mergereadnext 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [tuplesort_heap_insert 函数](#tuplesort_heap_insert 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [tuplesort_heap_delete_top 函数](#tuplesort_heap_delete_top 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [tuplesort_heap_replace_top 函数](#tuplesort_heap_replace_top 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [tuplesort_puttuple_common 函数](#tuplesort_puttuple_common 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [inittapes 函数](#inittapes 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [dumptuples 函数](#dumptuples 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [inittapestate 函数](#inittapestate 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [selectnewtape 函数](#selectnewtape 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [worker_nomergeruns 函数](#worker_nomergeruns 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [leader_takeover_tapes 函数](#leader_takeover_tapes 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
      • [LogicalTapeSetCreate 函数](#LogicalTapeSetCreate 函数)
        • [1. 带中文注释的完整源码](#1. 带中文注释的完整源码)
        • [2. 详细功能解读](#2. 详细功能解读)
        • [3. 流程图](#3. 流程图)
    • 整体执行逻辑总结
    • [📗 推荐进一步阅读](#📗 推荐进一步阅读)
      • [1. 《PostgreSQL 中的查询:排序与合并》(外部排序最经典中文综述)](#1. 《PostgreSQL 中的查询:排序与合并》(外部排序最经典中文综述))
      • [2. PostgreSQL Sort 模块代码分析](#2. PostgreSQL Sort 模块代码分析)
      • [3. 并行排序原理(PostgreSQL 11+)](#3. 并行排序原理(PostgreSQL 11+))

声明 :本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。

本文主要参考了 postgresql-15.0 的开源代码和《PostgresSQL数据库内核分析》一书

一、引言

  PostgreSQL 外部排序分为内存生成 Run (dumptuples) + 磁盘多路归并 (mergeruns) 两大阶段PG14 及更早版本排序归并采用 Polyphase 多相归并(斐波那契归并),算法诞生于物理磁带机稀缺年代,为节约物理磁带驱动器设计,依赖斐波那契数列分配 DummyRun、磁带轮换挪数据,现代磁盘 + 逻辑磁带架构下频繁产生多余磁盘 IO、代码晦涩难维护。

  Heikki Linnakangascommit:65014000b351d5725eb00d133416ab1b4f8245b1提交补丁,彻底移除多相归并,改用平衡 K 路归并算法,简化归并逻辑、减少中间数据落地读写,大数据多轮归并场景排序性能明显提升。

提交信息

  下面为本次优化的提交信息,hash 值为:65014000b351d5725eb00d133416ab1b4f8245b1。对应的描述信息如下所示:

bash 复制代码
commit 65014000b351d5725eb00d133416ab1b4f8245b1
Author: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date:   Mon Oct 18 14:33:42 2021 +0300

    Replace polyphase merge algorithm with a simple balanced k-way merge.

    The advantage of polyphase merge is that it can reuse the input tapes as
    output tapes efficiently, but that is irrelevant on modern hardware, when
    we can easily emulate any number of tape drives. The number of input tapes
    we can/should use during merging is limited by work_mem, but output tapes
    that we are not currently writing to only cost a little bit of memory, so
    there is no need to skimp on them.

    This makes sorts that need multiple merge passes faster.

    Discussion: https://www.postgresql.org/message-id/420a0ec7-602c-d406-1e75-1ef7ddc58d83%40iki.fi
    Reviewed-by: Peter Geoghegan, Zhihong Yu, John Naylor

优化目的与背景

1. 历史背景

  PostgreSQL 的外部排序(external sort)长期使用 Knuth计算机程序设计艺术 Vol.3 中的经典算法:

  • Run Generation :早期使用 Replacement Selection(优先树/堆),后来改为 Quicksort 生成初始有序 runs
  • Merge 阶段 :使用 Polyphase MergeKnuth Algorithm 5.4.2D),基于斐波那契数分布的"多相归并"。

Polyphase Merge 的设计假设

  • 磁带机(tape drives)非常昂贵且数量有限。
  • 需要尽量让所有磁带机保持忙碌,减少空闲时间。
  • 通过巧妙的"dummy runs"和斐波那契分布,实现接近最优的合并轮数。

2. 现代硬件现实

  • 磁盘/SSD 随机访问代价已大幅降低。
  • 内存相对廉价 ,"磁带机"在代码中只是 LogicalTape + 少量缓冲区(几 KB)。
  • 可以轻松模拟任意多个逻辑磁带,而不需要担心硬件限制。
  • Polyphase 的复杂逻辑(dummy runs、斐波那契数、tape reassign 等)带来的维护成本和 CPU 开销,在现代硬件上已不值得。

优化目标

  用简单、直接的 Balanced k-way Merge 替换 Polyphase Merge,提升多趟合并场景的性能,同时显著简化代码。

3. 新旧算法核心对比

维度 Polyphase Merge (旧) Balanced k-way Merge (新)
核心思想 斐波那契分布 + dummy runs,动态复用磁带 固定 k 路归并,每趟均匀分配 runs 到输出磁带
磁带利用 尽量保持所有磁带忙碌 可以有更多输出磁带,成本低
代码复杂度 高(tp_fib, tp_dummy, tp_tapenum 等) 低(inputTapes / outputTapes 数组)
多趟合并性能 较好(理论最优轮数) 更好 (现代硬件下 I/O 模式更友好)
单趟合并 支持 支持(当 runs ≤ maxTapes 时)
可理解性 较难 清晰

4. 主要代码变更点

4.1 数据结构变化

旧版 Polyphase Merge 相关结构:

c 复制代码
// 旧
int maxTapes;           // Knuth's T:总的逻辑磁带数量(包括输出磁带)
int tapeRange;          // maxTapes-1:实际用于输入的磁带数量上限(Knuth's P)
LogicalTape **tapes;    // 所有逻辑磁带的数组指针(统一管理读写)
bool *mergeactive;      // 标记哪些输入磁带当前仍有有效 run 可供合并
int *tp_fib;            // 目标斐波那契数序列(Fibonacci numbers),用于决定 run 分布
int *tp_runs;           // 每条磁带上真实 run 的数量
int *tp_dummy;          // 每条磁带上"虚构的 dummy runs"数量,用于算法平衡
int *tp_tapenum;        // 实际磁带编号映射(TAPE[]),实现磁带复用
int Level;              // 当前归并层级(Knuth's l),表示已完成的归并轮数
int destTape;           // 当前输出磁带的逻辑编号(Knuth's j-1)
int activeTapes;        // 当前合并过程中活跃的输入磁带数量

新版 Balanced k-way Merge 相关结构:

c 复制代码
// 新
int maxTapes;                    // 每趟归并允许的最大输入磁带数(由 work_mem 计算得出,控制归并路数 k)
LogicalTape **inputTapes;        // 当前归并 pass 的所有输入磁带数组
int nInputTapes;                 // 当前 pass 中实际参与合并的输入磁带数量
int nInputRuns;                  // 当前 pass 中待合并的输入 runs 总数
LogicalTape **outputTapes;       // 当前 pass 用于写入新 runs 的输出磁带数组
int nOutputTapes;                // 当前 pass 中已创建的输出磁带数量
int nOutputRuns;                 // 当前 pass 中已产生的输出 runs 数量
LogicalTape *destTape;           // 当前正在写入的输出磁带指针(简化了旧版的 destTape 逻辑)
size_t tape_buffer_mem;          // 为所有输入 + 输出磁带预分配的缓冲区总内存(bytes)

4.2 关键概念解释

  Dummy Runs(虚构运行 / 哑运行)Polyphase Merge 算法中的一个重要概念:

  Dummy run 是指逻辑上存在、但实际上没有数据的"空运行"。在 Polyphase 算法中,当某条磁带上的真实 runs 数量不足以满足斐波那契分布要求时,算法会"假装 "这条磁带上有一些 dummy runs。这些 dummy runs 不占用实际磁盘空间,但在计算下一轮归并的 run 分布和磁带切换时被当作真实 runs 来处理。

  其主要作用是平衡各磁带上的 run 数量,让归并过程更接近理论最优的合并轮数,同时尽量保持所有磁带驱动器处于忙碌状态。这是经典磁带排序时代为了充分利用昂贵硬件而设计的技巧。

  在新算法(Balanced k-way Merge中,已经 完全摒弃了 dummy runs 的概念。新的算法采用简单直接的"每趟将输入 runs 均匀分配到输出磁带"的策略,不再需要复杂的斐波那契数列和虚构运行来辅助调度,从而极大简化了代码逻辑。

其他相关概念简释:

  • Run :一次外部排序中生成的有序段 (已排序的 tuple 序列)。
  • Merge Pass :一轮将多个 runs 归并成更少、更长的 runs 的过程。
  • k-way Merge :每次同时从 k 条输入磁带读取数据进行归并。
  • Balanced Merge :每轮归并中输入和输出的 run 数量尽可能均衡,结构清晰、易于实现。

4.3 关键函数调整
  • tuplesort_merge_order() :计算 merge orderk 值)

    • 新公式:mOrder = allowedMem / (2 * TAPE_BUFFER_OVERHEAD + MERGE_BUFFER_SIZE)
    • 考虑输入和输出磁带的缓冲区开销。
  • merge_read_buffer_size() (新增):动态计算每输入磁带的读缓冲大小,考虑当前 pass 的输出磁带数量。

  • inittapes() / inittapestate() :简化初始化,移除大量 Polyphase 变量。

  • selectnewtape()

    • maxTapesruns 使用新磁带。
    • 之后轮询(round-robin)追加到现有输出磁带。
  • mergeruns():核心循环完全重写

    • 循环结构改为"while 需要新 pass" → 切换 input/output tapes
    • 更清晰的 pass-by-pass 管理。
  • beginmerge() / mergereadnext() / mergeonerun() :大幅简化,移除 dummy runs 处理。

  • dumptuples() :生成初始 runs 时,使用 selectnewtape()

源码解读

mergeruns 函数

1. 函数整体说明
  • 函数名称mergeruns
  • 作用 :将所有已生成的初始有序 runs 进行多路归并,最终生成一个完整的有序结果。
  • 算法Balanced k-Way Merge (平衡 k 路归并)

这是本次 Patch 中修改最大、最核心的函数之一

主要修改点标记 (与旧 Polyphase 版本对比):

  • 完全重写 :旧版基于 Knuth Algorithm D(包含 Leveltp_dummytp_fib 等复杂逻辑),新版采用清晰的 pass-by-pass 循环结构。
  • 删除大量 Polyphase 专用变量处理。
  • 新增 inputTapes / outputTapes 切换逻辑,支持多趟归并。
  • 内存缓冲区动态分配更合理。
  • 增加对最终 on-the-fly 合并的优化判断。

2. 带中文注释的完整源码
c 复制代码
/*
 * mergeruns -- 合并所有已经生成的有序初始Run(归并阶段主函数)
 *
 * 【Patch 修改】这里实现的是:平衡K路归并算法(Balanced k-Way Merge)
 * 老版本:实现多相归并(Polyphase Merge / 斐波那契)
 * 新版本:全部替换为简单直接的平衡多路归并
 *
 * 所有输入数据已经通过 dumptuples 写到磁盘的有序Run中(磁带)
 */
static void
mergeruns(Tuplesortstate *state)
{
	int			tapenum;

	/* 校验状态:当前正在构建有序Run阶段(BUILD RUNS) */
	Assert(state->status == TSS_BUILDRUNS);
	/* 校验:内存中没有残留元组 */
	Assert(state->memtupcount == 0);

	/* ========================================================================
	 * 【原有逻辑】关闭缩写键优化(abbreviated key)
	 * 原因:从磁盘读回的元组不携带缩写key,必须关闭,用原始比较器
	 * ========================================================================
	 */
	if (state->sortKeys != NULL && state->sortKeys->abbrev_converter != NULL)
	{
		/*
		 * 注释说明:
		 * 如果需要归并多个Run,从磁盘读取元组时,缩写键不会被存储
		 * 也不需要重新生成,所以从现在开始禁用缩写键
		 */
		state->sortKeys->abbrev_converter = NULL;
		/* 切换回完整字符串/数值比较器 */
		state->sortKeys->comparator = state->sortKeys->abbrev_full_comparator;

		/* 清理无用指针,不是必须,但代码更整洁 */
		state->sortKeys->abbrev_abort = NULL;
		state->sortKeys->abbrev_full_comparator = NULL;
	}

	/* ========================================================================
	 * 【原有逻辑】重置元组内存上下文
	 * 之前的元组已经全部释放,后续使用 slab 分配器
	 * ========================================================================
	 */
	MemoryContextResetOnly(state->tuplecontext);

	/*
	 * 不再需要大容量的 memtuples 数组(之前用于内存排序)
	 * 稍后会为最小堆分配一个更小的数组
	 *
	 * 【Patch 修改】老代码这里会保留大数组,新代码直接释放
	 */
	FREEMEM(state, GetMemoryChunkSpace(state->memtuples));
	pfree(state->memtuples);
	state->memtuples = NULL;

	/* ========================================================================
	 * 【原有逻辑】初始化 Slab 分配器(固定槽位内存池)
	 * 作用:复用元组内存,避免频繁 malloc/free,减少内存碎片
	 *
	 * 【Patch 修改】槽数量 = 输出磁带数 + 1(平衡归并固定规则)
	 * 老代码:槽数量 = 活动输入磁带数(复杂计算)
	 * ========================================================================
	 */
	if (state->tuples)
		/* 每个输入磁带一个槽 + 最后返回元组一个槽 */
		init_slab_allocator(state, state->nOutputTapes + 1);
	else
		/* 不需要存元组(比如只排序Datum),则不分配 */
		init_slab_allocator(state, 0);

	/*
	 * 分配新的 memtuples 数组,给【最小堆】使用
	 * 作用:存储每个输入磁带当前最前面的一个元组
	 *
	 * 【Patch 修改】数组大小 = 输出磁带数(平衡归并固定)
	 * 老代码:动态计算活动磁带数
	 */
	state->memtupsize = state->nOutputTapes;
	state->memtuples = (SortTuple *) MemoryContextAlloc(state->maincontext,
														state->nOutputTapes * sizeof(SortTuple));
	USEMEM(state, GetMemoryChunkSpace(state->memtuples));

	/* ========================================================================
	 * 【Patch 新增】全局磁带缓冲区总内存管理
	 * 把所有剩余内存统一交给 tape_buffer_mem 管理
	 * 每一轮归并再动态分配给输入/输出磁带
	 * ========================================================================
	 */
	state->tape_buffer_mem = state->availMem;
	USEMEM(state, state->availMem);

#ifdef TRACE_SORT
	/* 打印日志:磁带缓冲区总内存 */
	if (trace_sort)
		elog(LOG, "worker %d using " INT64_FORMAT " KB of memory for tape buffers",
			 state->worker, state->tape_buffer_mem / 1024);
#endif

	/* ========================================================================
	 * 【Patch 重写】平衡K路归并主循环(核心!)
	 * 老代码:多相归并,Level、斐波那契、Dummy Run、磁带轮换
	 * 新代码:简单循环:上轮输出 → 本轮输入
	 * ========================================================================
	 */
	for (;;)
	{
		/*
		 * 【Patch 核心逻辑】
		 * 如果输入Run已经读完(nInputRuns == 0)
		 * 说明要开始新一轮归并:
		 * 把上一轮的【输出磁带】全部变成【输入磁带】
		 */
		if (state->nInputRuns == 0 && !WORKER(state))
		{
			int64		input_buffer_size;

			/* 关闭上一轮已经读完的输入磁带,释放内存 */
			if (state->nInputTapes > 0)
			{
				for (tapenum = 0; tapenum < state->nInputTapes; tapenum++)
					LogicalTapeClose(state->inputTapes[tapenum]);
				pfree(state->inputTapes);
			}

			/* ------------------------------------------------
			 * 【Patch 核心】上一轮输出 = 下一轮输入
			 * 平衡归并最关键规则
			 * ------------------------------------------------
			 */
			state->inputTapes = state->outputTapes;
			state->nInputTapes = state->nOutputTapes;
			state->nInputRuns = state->nOutputRuns;

			/* 重置输出磁带数组,准备本轮输出 */
			state->outputTapes = palloc0(state->nInputTapes * sizeof(LogicalTape *));
			state->nOutputTapes = 0;
			state->nOutputRuns = 0;

			/* ------------------------------------------------
			 * 【Patch 新增】动态计算每轮输入磁带缓冲区大小
			 * 总内存 → 分给输入/输出磁带
			 * ------------------------------------------------
			 */
			input_buffer_size = merge_read_buffer_size(state->tape_buffer_mem,
													   state->nInputTapes,
													   state->nInputRuns,
													   state->maxTapes);

#ifdef TRACE_SORT
			/* 打印:开始新一轮归并 */
			if (trace_sort)
				elog(LOG, "starting merge pass of %d input runs on %d tapes, " INT64_FORMAT " KB of memory for each input tape: %s",
					 state->nInputRuns, state->nInputTapes, input_buffer_size / 1024,
					 pg_rusage_show(&state->ru_start));
#endif

			/* 所有输入磁带倒带,准备读取,设置缓冲区大小 */
			for (tapenum = 0; tapenum < state->nInputTapes; tapenum++)
				LogicalTapeRewindForRead(state->inputTapes[tapenum], input_buffer_size);

			/* ------------------------------------------------
			 * 【Patch 优化】最终归并直通优化
			 * 如果剩下的Run数量 ≤ 输入磁带数
			 * 并且不需要随机访问 → 直接流式归并,不落地磁盘
			 * ------------------------------------------------
			 */
			if (!state->randomAccess && state->nInputRuns <= state->nInputTapes)
			{
				/* 告诉logtape.c不再需要写空间 */
				LogicalTapeSetForgetFreeSpace(state->tapeset);
				/* 初始化堆,准备最终归并 */
				beginmerge(state);
				/* 状态切换为:最终归并中(流式返回) */
				state->status = TSS_FINALMERGE;
				return;
			}
		}

		/* 选择本轮输出磁带(新建或轮询复用) */
		selectnewtape(state);

		/* 执行一次合并:从所有输入磁带各取一个Run,合并成一个新Run写入输出磁带 */
		mergeonerun(state);

		/*
		 * 【Patch 结束条件】
		 * 输入全部读完,并且输出只有1个Run → 归并完成
		 */
		if (state->nInputRuns == 0 && state->nOutputRuns <= 1)
			break;
	}

	/* ========================================================================
	 * 归并全部完成
	 * 结果只有一个输出磁带,里面只有一个最终有序Run
	 * ========================================================================
	 */
	state->result_tape = state->outputTapes[0];
	if (!WORKER(state))
		LogicalTapeFreeze(state->result_tape, NULL);
	else
		worker_freeze_result_tape(state);
	/* 状态切换:已经排序到磁带 */
	state->status = TSS_SORTEDONTAPE;

	/* 关闭所有空的输入磁带,释放缓冲区 */
	for (tapenum = 0; tapenum < state->nInputTapes; tapenum++)
		LogicalTapeClose(state->inputTapes[tapenum]);
}

3. 详细功能分析
  1. 前期准备

    • 禁用 abbreviation 优化(因为后续从磁盘读取时不再适用)。
    • 重置内存上下文,释放旧的 memtuples 数组。
    • 初始化 Slab 分配器(用于后续 heap 中的 tuples)。
    • 分配新的较小 memtuples 数组作为归并堆。
    • 保存剩余内存给所有磁带缓冲区(tape_buffer_mem)。
  2. 主循环(多趟归并核心)

    • 判断是否需要新 PassnInputRuns == 0 表示上一轮输入已耗尽,需切换 input/output
    • Input/Output 角色切换 :上一轮的 outputTapes 变为本轮的 inputTapes
    • 动态计算缓冲区 :调用 merge_read_buffer_size() 为本 pass 分配合适大小的读缓冲。
    • 倒带准备:所有输入磁带倒回开头准备读取。
    • 最终优化判断 :若只需一趟且非随机访问,则直接进入 TSS_FINALMERGE 模式(on-the-fly 返回结果,节省一次写读)。
    • 选择输出磁带合并一轮mergeonerun)→ 检查是否完成。
  3. 收尾工作

    • 设置 result_tape 为最终结果磁带。
    • 冻结磁带(支持后续随机读取)。
    • 清理输入磁带缓冲区。

与旧版 Polyphase 的关键差异

  • 控制逻辑 :旧版依赖 Leveltp_dummytp_runs 等数组和复杂状态机;新版使用简单清晰的 nInputRunsnOutputRuns 计数。
  • 磁带管理 :新版明确区分 inputTapesoutputTapes,切换时直接指针赋值,代码更直观。
  • 可扩展性 :更容易理解和调试,支持未来进一步优化(如并行 merge)。
  • 性能 :在多趟归并场景下 I/O 模式更友好,CPU 开销显著降低。

#mermaid-svg-tTOb2D2BCZ37KUyZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tTOb2D2BCZ37KUyZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tTOb2D2BCZ37KUyZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tTOb2D2BCZ37KUyZ .error-icon{fill:#552222;}#mermaid-svg-tTOb2D2BCZ37KUyZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tTOb2D2BCZ37KUyZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tTOb2D2BCZ37KUyZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tTOb2D2BCZ37KUyZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tTOb2D2BCZ37KUyZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tTOb2D2BCZ37KUyZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tTOb2D2BCZ37KUyZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tTOb2D2BCZ37KUyZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tTOb2D2BCZ37KUyZ .marker.cross{stroke:#333333;}#mermaid-svg-tTOb2D2BCZ37KUyZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tTOb2D2BCZ37KUyZ p{margin:0;}#mermaid-svg-tTOb2D2BCZ37KUyZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tTOb2D2BCZ37KUyZ .cluster-label text{fill:#333;}#mermaid-svg-tTOb2D2BCZ37KUyZ .cluster-label span{color:#333;}#mermaid-svg-tTOb2D2BCZ37KUyZ .cluster-label span p{background-color:transparent;}#mermaid-svg-tTOb2D2BCZ37KUyZ .label text,#mermaid-svg-tTOb2D2BCZ37KUyZ span{fill:#333;color:#333;}#mermaid-svg-tTOb2D2BCZ37KUyZ .node rect,#mermaid-svg-tTOb2D2BCZ37KUyZ .node circle,#mermaid-svg-tTOb2D2BCZ37KUyZ .node ellipse,#mermaid-svg-tTOb2D2BCZ37KUyZ .node polygon,#mermaid-svg-tTOb2D2BCZ37KUyZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tTOb2D2BCZ37KUyZ .rough-node .label text,#mermaid-svg-tTOb2D2BCZ37KUyZ .node .label text,#mermaid-svg-tTOb2D2BCZ37KUyZ .image-shape .label,#mermaid-svg-tTOb2D2BCZ37KUyZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-tTOb2D2BCZ37KUyZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tTOb2D2BCZ37KUyZ .rough-node .label,#mermaid-svg-tTOb2D2BCZ37KUyZ .node .label,#mermaid-svg-tTOb2D2BCZ37KUyZ .image-shape .label,#mermaid-svg-tTOb2D2BCZ37KUyZ .icon-shape .label{text-align:center;}#mermaid-svg-tTOb2D2BCZ37KUyZ .node.clickable{cursor:pointer;}#mermaid-svg-tTOb2D2BCZ37KUyZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tTOb2D2BCZ37KUyZ .arrowheadPath{fill:#333333;}#mermaid-svg-tTOb2D2BCZ37KUyZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tTOb2D2BCZ37KUyZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tTOb2D2BCZ37KUyZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tTOb2D2BCZ37KUyZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tTOb2D2BCZ37KUyZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tTOb2D2BCZ37KUyZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tTOb2D2BCZ37KUyZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tTOb2D2BCZ37KUyZ .cluster text{fill:#333;}#mermaid-svg-tTOb2D2BCZ37KUyZ .cluster span{color:#333;}#mermaid-svg-tTOb2D2BCZ37KUyZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tTOb2D2BCZ37KUyZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tTOb2D2BCZ37KUyZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-tTOb2D2BCZ37KUyZ .icon-shape,#mermaid-svg-tTOb2D2BCZ37KUyZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tTOb2D2BCZ37KUyZ .icon-shape p,#mermaid-svg-tTOb2D2BCZ37KUyZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tTOb2D2BCZ37KUyZ .icon-shape .label rect,#mermaid-svg-tTOb2D2BCZ37KUyZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tTOb2D2BCZ37KUyZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tTOb2D2BCZ37KUyZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tTOb2D2BCZ37KUyZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-tTOb2D2BCZ37KUyZ .newStyle>*{fill:#e6f7e6!important;stroke:#2e8b57!important;stroke-width:2px!important;}#mermaid-svg-tTOb2D2BCZ37KUyZ .newStyle span{fill:#e6f7e6!important;stroke:#2e8b57!important;stroke-width:2px!important;}#mermaid-svg-tTOb2D2BCZ37KUyZ .oldStyle>*{fill:#fff0f0!important;stroke:#d32f2f!important;stroke-width:2px!important;}#mermaid-svg-tTOb2D2BCZ37KUyZ .oldStyle span{fill:#fff0f0!important;stroke:#d32f2f!important;stroke-width:2px!important;} 旧版 Polyphase Merge (Knuth Algorithm D)
新版 Balanced k-way Merge












开始 mergeruns()
校验状态 TSS_BUILDRUNS

内存元组已清空
需要关闭缩写键优化?
禁用 abbrev_converter

切换完整比较器
重置 tuplecontext
释放旧 memtuples 大数组
初始化 Slab 分配器

(基于 nOutputTapes)
分配新的小 memtuples 堆数组
保存 tape_buffer_mem

用于所有磁带缓冲
进入无限循环 for(;;)
nInputRuns == 0 ?

(是否需要开始新 Pass)
关闭上一轮输入磁带

释放 inputTapes 数组
【核心切换】outputTapes → inputTapes
重置 outputTapes 数组

准备新一轮输出
调用 merge_read_buffer_size()

动态计算输入缓冲大小
所有输入磁带 RewindForRead

准备读取
!randomAccess && nInputRuns <= nInputTapes ?
beginmerge() + TSS_FINALMERGE

直接 return(on-the-fly 优化)
selectnewtape()

选择或创建输出磁带
mergeonerun()

执行 k-way 合并生成一个新 Run
nInputRuns == 0 && nOutputRuns <= 1 ?
break 退出循环
result_tape = outputTapes0
冻结磁带(Freeze)

支持后续随机访问
关闭剩余输入磁带

释放读缓冲
状态 = TSS_SORTEDONTAPE

函数结束
初始化 Polyphase 变量

tp_fib、tp_runs、tp_dummy、tp_tapenum、Level 等
计算 numInputTapes 和 numTapes

并退还多余磁带缓冲内存
初始化 mergeactive 数组

标记活跃输入磁带
分配固定 read_buffer_size

为所有输入磁带
进入 Algorithm D 主循环
Level > 0 ?

是否还有归并轮次
while (tp_runs 或 tp_dummy 存在)
allDummy ?

是否全部是虚构运行
处理 dummy runs

平衡各磁带 run 数量(经典磁带优化)
mergeonerun()

执行一次多路归并
Step D6: 磁带重分配

tp_tapenum 轮转 + Level--
rewind 输出磁带作为新输入

关闭旧输入磁带
最终 Level == 0 ?
result_tape = tp_tapenumtapeRange

设置最终结果磁带


init_slab_allocator 函数

1. 函数整体说明
  • 函数名称init_slab_allocator
  • 所属文件tuplesort.c
  • 作用 :初始化 Slab 内存分配器 (一种高效的固定大小内存池),用于外部排序归并阶段缓存从磁带读取的 SortTuple
  • 核心目的 :避免在归并过程中频繁 palloc / pfree 导致的性能损耗和内存碎片。

  和本PATCH相关内容 :在新版 Balanced k-way Merge 中,归并堆(heap)需要为每个输入磁带维护一个 tuple,使用 Slab 分配器可以显著提升内存分配效率。


2. 带中文注释的完整源码
c 复制代码
/*
 * init_slab_allocator -- 初始化 Slab 内存分配器
 *
 * 功能:一次性申请一大块连续内存,切割成固定大小的槽位(slot)
 * 用途:给外部排序归并阶段使用,用于缓存从各个磁带读取的元组
 * 避免频繁 malloc/free 带来的性能损耗和内存碎片
 *
 * numSlots:需要创建多少个槽位(一般 = 输入磁带数 + 1)
 */
static void
init_slab_allocator(Tuplesortstate *state, int numSlots)
{
	/* 如果需要的槽位数量 > 0,才真正创建分配器 */
	if (numSlots > 0)
	{
		char	   *p;                                      // 临时指针,用于遍历构建空闲链表
		int			i;                                      // 循环变量

		/*
		 * 一次性申请整块连续内存
		 * 总大小 = 槽位数量 × 每个槽位固定大小 SLAB_SLOT_SIZE
		 */
		state->slabMemoryBegin = palloc(numSlots * SLAB_SLOT_SIZE); // 分配连续内存块

		/*
		 * 计算 slab 内存的结束地址
		 * 用于后续判断内存是否越界
		 */
		state->slabMemoryEnd = state->slabMemoryBegin + numSlots * SLAB_SLOT_SIZE; // 计算内存块结束边界

		/*
		 * 空闲链表头指针指向内存起始位置
		 * 所有槽初始都是空闲可用的
		 */
		state->slabFreeHead = (SlabSlot *) state->slabMemoryBegin; // 初始化空闲链表头

		/*
		 * 把分配的内存计入排序模块的内存使用统计
		 * 方便 PG 内存管控、OOM 预防
		 */
		USEMEM(state, numSlots * SLAB_SLOT_SIZE);               // 更新内存使用统计

		/*
		 * 遍历所有槽位,把它们串成一个【单向空闲链表】
		 * 每个槽位只存一个指针:指向下一个空闲槽
		 */
		p = state->slabMemoryBegin;                             // p 指向内存起始位置
		for (i = 0; i < numSlots - 1; i++)                      // 遍历除最后一个槽位外的所有槽
		{
			/* 当前槽的 nextfree = 下一个槽的地址 */
			((SlabSlot *) p)->nextfree = (SlabSlot *) (p + SLAB_SLOT_SIZE); // 构建链表指针

			/* 指针向后移动一个槽位 */
			p += SLAB_SLOT_SIZE;                                // 移动到下一个槽
		}

		/* 最后一个槽的 nextfree 置空,表示链表结尾 */
		((SlabSlot *) p)->nextfree = NULL;                      // 链表尾部置 NULL
	}
	else
	{
		/*
		 * 不需要槽位时(比如只排序 Datum,不存元组)
		 * 所有指针置 NULL,不分配内存
		 */
		state->slabMemoryBegin = NULL;                          // 无需分配,置空
		state->slabMemoryEnd = NULL;
		state->slabFreeHead = NULL;
	}

	/* 标记:本排序模块已启用 Slab 分配器 */
	state->slabAllocatorUsed = true;                            // 标记 Slab 分配器已启用
}

3. 修改点标记
  • 关键点
    • 一次性大块分配 + 链表管理,避免了归并过程中大量小块内存分配。
    • 通过 USEMEM() 严格追踪内存使用,支持 PostgreSQL 的内存限额机制。
  • 条件分支 :对 numSlots == 0 的情况做了明确处理(例如仅排序 Datum 的场景)。

4. 详细功能分析

Slab 分配器工作原理

  1. 预分配 :一次性申请一大块连续内存(palloc),避免多次小块分配。
  2. 切割槽位 :将内存切割成固定大小的 SLAB_SLOT_SIZE 槽位。
  3. 空闲链表管理
    • 所有槽位初始串成一个单向链表(nextfree 指针)。
    • 分配时从链表头取出一个槽。
    • 释放时将槽重新插入链表头。
  4. 优点
    • 高性能 :分配/释放几乎是 O(1) 操作。
    • 低碎片:固定大小槽位,内存布局连续。
    • 可控:便于整体内存统计和限制。

在外部排序中的作用

  • mergeruns() 中被调用:init_slab_allocator(state, state->nOutputTapes + 1)
  • 用于存储归并堆(heap)中的 tuples(每个输入磁带一个 + 一个临时槽)。
  • 显著提升多路归并阶段的内存操作效率,尤其在输入 runs 很多、需要多趟合并时效果明显。

关键数据结构

  • slabMemoryBegin / slabMemoryEnd:内存块边界,用于安全检查。
  • slabFreeHead:空闲槽链表头。
  • SlabSlot:每个槽位的结构(内部至少包含一个 nextfree 指针)。

总结init_slab_allocator() 是新版外部排序在内存管理上的重要改进。它通过预分配 + 固定槽位 + 链表复用的方式,为归并阶段提供了高效、稳定的内存供应,是性能优化的重要基础。
#mermaid-svg-wXiYG1vgdjOZtYQR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wXiYG1vgdjOZtYQR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wXiYG1vgdjOZtYQR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wXiYG1vgdjOZtYQR .error-icon{fill:#552222;}#mermaid-svg-wXiYG1vgdjOZtYQR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wXiYG1vgdjOZtYQR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wXiYG1vgdjOZtYQR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wXiYG1vgdjOZtYQR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wXiYG1vgdjOZtYQR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wXiYG1vgdjOZtYQR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wXiYG1vgdjOZtYQR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wXiYG1vgdjOZtYQR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wXiYG1vgdjOZtYQR .marker.cross{stroke:#333333;}#mermaid-svg-wXiYG1vgdjOZtYQR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wXiYG1vgdjOZtYQR p{margin:0;}#mermaid-svg-wXiYG1vgdjOZtYQR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wXiYG1vgdjOZtYQR .cluster-label text{fill:#333;}#mermaid-svg-wXiYG1vgdjOZtYQR .cluster-label span{color:#333;}#mermaid-svg-wXiYG1vgdjOZtYQR .cluster-label span p{background-color:transparent;}#mermaid-svg-wXiYG1vgdjOZtYQR .label text,#mermaid-svg-wXiYG1vgdjOZtYQR span{fill:#333;color:#333;}#mermaid-svg-wXiYG1vgdjOZtYQR .node rect,#mermaid-svg-wXiYG1vgdjOZtYQR .node circle,#mermaid-svg-wXiYG1vgdjOZtYQR .node ellipse,#mermaid-svg-wXiYG1vgdjOZtYQR .node polygon,#mermaid-svg-wXiYG1vgdjOZtYQR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wXiYG1vgdjOZtYQR .rough-node .label text,#mermaid-svg-wXiYG1vgdjOZtYQR .node .label text,#mermaid-svg-wXiYG1vgdjOZtYQR .image-shape .label,#mermaid-svg-wXiYG1vgdjOZtYQR .icon-shape .label{text-anchor:middle;}#mermaid-svg-wXiYG1vgdjOZtYQR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wXiYG1vgdjOZtYQR .rough-node .label,#mermaid-svg-wXiYG1vgdjOZtYQR .node .label,#mermaid-svg-wXiYG1vgdjOZtYQR .image-shape .label,#mermaid-svg-wXiYG1vgdjOZtYQR .icon-shape .label{text-align:center;}#mermaid-svg-wXiYG1vgdjOZtYQR .node.clickable{cursor:pointer;}#mermaid-svg-wXiYG1vgdjOZtYQR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wXiYG1vgdjOZtYQR .arrowheadPath{fill:#333333;}#mermaid-svg-wXiYG1vgdjOZtYQR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wXiYG1vgdjOZtYQR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wXiYG1vgdjOZtYQR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wXiYG1vgdjOZtYQR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wXiYG1vgdjOZtYQR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wXiYG1vgdjOZtYQR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wXiYG1vgdjOZtYQR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wXiYG1vgdjOZtYQR .cluster text{fill:#333;}#mermaid-svg-wXiYG1vgdjOZtYQR .cluster span{color:#333;}#mermaid-svg-wXiYG1vgdjOZtYQR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wXiYG1vgdjOZtYQR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wXiYG1vgdjOZtYQR rect.text{fill:none;stroke-width:0;}#mermaid-svg-wXiYG1vgdjOZtYQR .icon-shape,#mermaid-svg-wXiYG1vgdjOZtYQR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wXiYG1vgdjOZtYQR .icon-shape p,#mermaid-svg-wXiYG1vgdjOZtYQR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wXiYG1vgdjOZtYQR .icon-shape .label rect,#mermaid-svg-wXiYG1vgdjOZtYQR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wXiYG1vgdjOZtYQR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wXiYG1vgdjOZtYQR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wXiYG1vgdjOZtYQR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是,需要分配
循环完毕
否,无需分配
开始:init_slab_allocator()

作用:初始化排序用的slab内存池
判断:numSlots > 0 ?

意义:是否需要分配内存槽
palloc 申请整块连续内存

意义:一次性申请所有槽位内存,避免频繁分配
设置 slabMemoryBegin

意义:记录内存池起始地址
设置 slabMemoryEnd

意义:记录内存池结束地址,防止越界
slabFreeHead 指向起始位置

意义:空闲链表头指向第一个可用槽
USEMEM 统计内存

意义:把slab内存计入排序模块总内存
指针 p 指向起始位置

意义:准备遍历所有槽位
for 循环构建空闲链表

意义:把所有槽位串成单向链表
当前槽 nextfree = 下一个槽

意义:建立空闲槽之间的链接
p 向后移动一个槽位

意义:准备处理下一个槽
循环结束

意义:所有槽位链表构建完成
最后一个槽 nextfree = NULL

意义:标记链表结尾,无后续槽位
slabMemoryBegin = NULL

意义:无内存池,起始地址为空
slabMemoryEnd = NULL

意义:无内存池,结束地址为空
slabFreeHead = NULL

意义:无空闲槽,链表为空
slabAllocatorUsed = true

意义:标记当前排序已启用slab分配器
函数结束

意义:内存池初始化完成


mergeonerun 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * Merge one run from each input tape.
 * 从每个输入磁带中各取一个 run 进行合并
 */
static void
mergeonerun(Tuplesortstate *state)
{
	int			srcTapeIndex;                               // 当前处理的输入磁带索引
	LogicalTape *srcTape;                                   // 当前输入磁带指针

	/*
	 * Start the merge by loading one tuple from each active source tape into
	 * the heap.
	 */
	beginmerge(state);                                      // 调用 beginmerge 向堆中加载每个输入磁带的第一个 tuple

	Assert(state->slabAllocatorUsed);                       // 断言:Slab 分配器必须已启用

	/*
	 * Execute merge by repeatedly extracting lowest tuple in heap, writing it
	 * out, and replacing it with next tuple from same tape (if there is
	 * another one).
	 */
	while (state->memtupcount > 0)                          // 只要堆中还有 tuple,就持续执行合并
	{
		SortTuple	stup;                                   // 用于存放从磁带读取的下一个 tuple

		/* write the tuple to destTape */
		srcTapeIndex = state->memtuples[0].srctape;         // 获取堆顶 tuple 来自哪个输入磁带
		srcTape = state->inputTapes[srcTapeIndex];          // 拿到对应的输入磁带指针
		WRITETUP(state, state->destTape, &state->memtuples[0]); // 将堆顶最小 tuple 写入当前输出磁带

		/* recycle the slot of the tuple we just wrote out, for the next read */
		if (state->memtuples[0].tuple)                      // 如果是变长 tuple
			RELEASE_SLAB_SLOT(state, state->memtuples[0].tuple); // 释放 Slab 槽位,供后续复用

		/*
		 * pull next tuple from the tape, and replace the written-out tuple in
		 * the heap with it.
		 */
		if (mergereadnext(state, srcTape, &stup))           // 从同一输入磁带尝试读取下一个 tuple
		{
			stup.srctape = srcTapeIndex;                    // 记录该 tuple 来自的磁带索引
			tuplesort_heap_replace_top(state, &stup);       // 用新 tuple 替换堆顶,继续维持小根堆
		}
		else
		{
			tuplesort_heap_delete_top(state);               // 该磁带的 run 已读完,从堆中删除该入口
			state->nInputRuns--;                            // 待合并的输入 run 数量减 1
		}
	}

	/*
	 * When the heap empties, we're done.  Write an end-of-run marker on the
	 * output tape.
	 */
	markrunend(state->destTape);                            // 在输出磁带上写入 run 结束标记
}
2. 详细功能解读
  • 函数作用 :执行一次 k-way 合并 ,将当前所有输入磁带各自的一个 run 合并成一个更长的有序 run,写入输出磁带。
  • 核心流程 :使用小根堆heap)维护当前每个输入 run 的"头部" tuple,不断输出最小值并补充新 tuple
  • 重要优化 :使用 Slab 分配器快速回收/复用内存槽位。
  • 结束条件 :当堆变空时,代表所有输入 run 均已处理完毕。

3. 流程图

#mermaid-svg-v0x2fKn0JeMLrIqc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-v0x2fKn0JeMLrIqc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-v0x2fKn0JeMLrIqc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-v0x2fKn0JeMLrIqc .error-icon{fill:#552222;}#mermaid-svg-v0x2fKn0JeMLrIqc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-v0x2fKn0JeMLrIqc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-v0x2fKn0JeMLrIqc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-v0x2fKn0JeMLrIqc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-v0x2fKn0JeMLrIqc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-v0x2fKn0JeMLrIqc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-v0x2fKn0JeMLrIqc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-v0x2fKn0JeMLrIqc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-v0x2fKn0JeMLrIqc .marker.cross{stroke:#333333;}#mermaid-svg-v0x2fKn0JeMLrIqc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-v0x2fKn0JeMLrIqc p{margin:0;}#mermaid-svg-v0x2fKn0JeMLrIqc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-v0x2fKn0JeMLrIqc .cluster-label text{fill:#333;}#mermaid-svg-v0x2fKn0JeMLrIqc .cluster-label span{color:#333;}#mermaid-svg-v0x2fKn0JeMLrIqc .cluster-label span p{background-color:transparent;}#mermaid-svg-v0x2fKn0JeMLrIqc .label text,#mermaid-svg-v0x2fKn0JeMLrIqc span{fill:#333;color:#333;}#mermaid-svg-v0x2fKn0JeMLrIqc .node rect,#mermaid-svg-v0x2fKn0JeMLrIqc .node circle,#mermaid-svg-v0x2fKn0JeMLrIqc .node ellipse,#mermaid-svg-v0x2fKn0JeMLrIqc .node polygon,#mermaid-svg-v0x2fKn0JeMLrIqc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-v0x2fKn0JeMLrIqc .rough-node .label text,#mermaid-svg-v0x2fKn0JeMLrIqc .node .label text,#mermaid-svg-v0x2fKn0JeMLrIqc .image-shape .label,#mermaid-svg-v0x2fKn0JeMLrIqc .icon-shape .label{text-anchor:middle;}#mermaid-svg-v0x2fKn0JeMLrIqc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-v0x2fKn0JeMLrIqc .rough-node .label,#mermaid-svg-v0x2fKn0JeMLrIqc .node .label,#mermaid-svg-v0x2fKn0JeMLrIqc .image-shape .label,#mermaid-svg-v0x2fKn0JeMLrIqc .icon-shape .label{text-align:center;}#mermaid-svg-v0x2fKn0JeMLrIqc .node.clickable{cursor:pointer;}#mermaid-svg-v0x2fKn0JeMLrIqc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-v0x2fKn0JeMLrIqc .arrowheadPath{fill:#333333;}#mermaid-svg-v0x2fKn0JeMLrIqc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-v0x2fKn0JeMLrIqc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-v0x2fKn0JeMLrIqc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-v0x2fKn0JeMLrIqc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-v0x2fKn0JeMLrIqc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-v0x2fKn0JeMLrIqc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-v0x2fKn0JeMLrIqc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-v0x2fKn0JeMLrIqc .cluster text{fill:#333;}#mermaid-svg-v0x2fKn0JeMLrIqc .cluster span{color:#333;}#mermaid-svg-v0x2fKn0JeMLrIqc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-v0x2fKn0JeMLrIqc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-v0x2fKn0JeMLrIqc rect.text{fill:none;stroke-width:0;}#mermaid-svg-v0x2fKn0JeMLrIqc .icon-shape,#mermaid-svg-v0x2fKn0JeMLrIqc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-v0x2fKn0JeMLrIqc .icon-shape p,#mermaid-svg-v0x2fKn0JeMLrIqc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-v0x2fKn0JeMLrIqc .icon-shape .label rect,#mermaid-svg-v0x2fKn0JeMLrIqc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-v0x2fKn0JeMLrIqc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-v0x2fKn0JeMLrIqc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-v0x2fKn0JeMLrIqc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



开始 mergeonerun()
beginmerge():初始化堆,加载每个输入 run 的第一个 tuple
memtupcount > 0 ?
取出堆顶最小 tuple

写入 destTape
释放 Slab 槽位
mergereadnext() 是否成功?
替换堆顶(heap_replace_top)
删除堆顶 + nInputRuns--
markrunend() 写入 run 结束标记
函数结束


beginmerge 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * beginmerge - initialize for a merge pass
 *
 * Fill the merge heap with the first tuple from each input tape.
 * 为一轮归并做初始化:从每个输入磁带读取第一个 tuple 填入堆中
 */
static void
beginmerge(Tuplesortstate *state)
{
	int			activeTapes;                                // 本轮实际参与合并的活跃输入磁带数量
	int			srcTapeIndex;                               // 磁带索引

	/* Heap should be empty here */
	Assert(state->memtupcount == 0);                        // 断言:进入本函数时堆必须为空

	activeTapes = Min(state->nInputTapes, state->nInputRuns); // 计算实际需要处理的输入磁带数(防止 run 比磁带少)

	for (srcTapeIndex = 0; srcTapeIndex < activeTapes; srcTapeIndex++) // 遍历所有活跃输入磁带
	{
		SortTuple	tup;                                    // 临时存放读取的 tuple

		if (mergereadnext(state, state->inputTapes[srcTapeIndex], &tup)) // 尝试读取该磁带的第一个 tuple
		{
			tup.srctape = srcTapeIndex;                     // 记录 tuple 来自哪个磁带
			tuplesort_heap_insert(state, &tup);             // 插入小根堆中
		}
	}
}
2. 详细功能解读
  • 函数作用 :为一轮归并(merge pass)做初始化工作,预加载每个输入 run 的第一个 tuple 到归并堆中,建立初始的小根堆。
  • 关键逻辑 :只处理实际有剩余 run 的磁带(Min(nInputTapes, nInputRuns))。
  • 这是 k-way merge 的起点 ,后续 mergeonerun() 会基于这个初始堆持续合并。

3. 流程图

#mermaid-svg-I11zQf61qB2Q2Zcf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-I11zQf61qB2Q2Zcf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-I11zQf61qB2Q2Zcf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-I11zQf61qB2Q2Zcf .error-icon{fill:#552222;}#mermaid-svg-I11zQf61qB2Q2Zcf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-I11zQf61qB2Q2Zcf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-I11zQf61qB2Q2Zcf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-I11zQf61qB2Q2Zcf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-I11zQf61qB2Q2Zcf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-I11zQf61qB2Q2Zcf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-I11zQf61qB2Q2Zcf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-I11zQf61qB2Q2Zcf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-I11zQf61qB2Q2Zcf .marker.cross{stroke:#333333;}#mermaid-svg-I11zQf61qB2Q2Zcf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-I11zQf61qB2Q2Zcf p{margin:0;}#mermaid-svg-I11zQf61qB2Q2Zcf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-I11zQf61qB2Q2Zcf .cluster-label text{fill:#333;}#mermaid-svg-I11zQf61qB2Q2Zcf .cluster-label span{color:#333;}#mermaid-svg-I11zQf61qB2Q2Zcf .cluster-label span p{background-color:transparent;}#mermaid-svg-I11zQf61qB2Q2Zcf .label text,#mermaid-svg-I11zQf61qB2Q2Zcf span{fill:#333;color:#333;}#mermaid-svg-I11zQf61qB2Q2Zcf .node rect,#mermaid-svg-I11zQf61qB2Q2Zcf .node circle,#mermaid-svg-I11zQf61qB2Q2Zcf .node ellipse,#mermaid-svg-I11zQf61qB2Q2Zcf .node polygon,#mermaid-svg-I11zQf61qB2Q2Zcf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-I11zQf61qB2Q2Zcf .rough-node .label text,#mermaid-svg-I11zQf61qB2Q2Zcf .node .label text,#mermaid-svg-I11zQf61qB2Q2Zcf .image-shape .label,#mermaid-svg-I11zQf61qB2Q2Zcf .icon-shape .label{text-anchor:middle;}#mermaid-svg-I11zQf61qB2Q2Zcf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-I11zQf61qB2Q2Zcf .rough-node .label,#mermaid-svg-I11zQf61qB2Q2Zcf .node .label,#mermaid-svg-I11zQf61qB2Q2Zcf .image-shape .label,#mermaid-svg-I11zQf61qB2Q2Zcf .icon-shape .label{text-align:center;}#mermaid-svg-I11zQf61qB2Q2Zcf .node.clickable{cursor:pointer;}#mermaid-svg-I11zQf61qB2Q2Zcf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-I11zQf61qB2Q2Zcf .arrowheadPath{fill:#333333;}#mermaid-svg-I11zQf61qB2Q2Zcf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-I11zQf61qB2Q2Zcf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-I11zQf61qB2Q2Zcf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-I11zQf61qB2Q2Zcf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-I11zQf61qB2Q2Zcf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-I11zQf61qB2Q2Zcf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-I11zQf61qB2Q2Zcf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-I11zQf61qB2Q2Zcf .cluster text{fill:#333;}#mermaid-svg-I11zQf61qB2Q2Zcf .cluster span{color:#333;}#mermaid-svg-I11zQf61qB2Q2Zcf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-I11zQf61qB2Q2Zcf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-I11zQf61qB2Q2Zcf rect.text{fill:none;stroke-width:0;}#mermaid-svg-I11zQf61qB2Q2Zcf .icon-shape,#mermaid-svg-I11zQf61qB2Q2Zcf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-I11zQf61qB2Q2Zcf .icon-shape p,#mermaid-svg-I11zQf61qB2Q2Zcf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-I11zQf61qB2Q2Zcf .icon-shape .label rect,#mermaid-svg-I11zQf61qB2Q2Zcf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-I11zQf61qB2Q2Zcf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-I11zQf61qB2Q2Zcf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-I11zQf61qB2Q2Zcf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功
结束
开始 beginmerge()
断言:堆必须为空
activeTapes = Min(nInputTapes, nInputRuns)
for 每个活跃输入磁带
mergereadnext() 读取第一个 tuple
插入小根堆 + 记录 srctape
初始化完成


mergereadnext 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * mergereadnext - read next tuple from one merge input tape
 *
 * Returns false on EOF.
 * 从一个输入磁带读取下一个 tuple,返回 false 表示该 run 已结束(EOF)
 */
static bool
mergereadnext(Tuplesortstate *state, LogicalTape *srcTape, SortTuple *stup)
{
	unsigned int tuplen;                                    // tuple 的长度(以字节计)

	/* read next tuple, if any */
	if ((tuplen = getlen(srcTape, true)) == 0)              // 读取 tuple 长度,如果为 0 表示到达 run 结束标记
		return false;                                       // 返回 false 表示 EOF

	READTUP(state, stup, srcTape, tuplen);                  // 根据长度读取完整的 tuple 数据

	return true;                                            // 成功读取,返回 true
}
2. 详细功能解读
  • 函数作用 :从指定输入磁带读取下一个 tuple,是归并过程中最底层的读取函数。
  • 返回语义
    • true:成功读取到一个 tuple
    • false:遇到 run 结束标记(getlen 返回 0),该输入 run 已耗尽。
  • 依赖 :底层使用 getlen()READTUP() 宏/函数实现实际 I/O 操作。

3. 流程图

#mermaid-svg-xSAUF7p1z5Se0MwT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xSAUF7p1z5Se0MwT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xSAUF7p1z5Se0MwT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xSAUF7p1z5Se0MwT .error-icon{fill:#552222;}#mermaid-svg-xSAUF7p1z5Se0MwT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xSAUF7p1z5Se0MwT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xSAUF7p1z5Se0MwT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xSAUF7p1z5Se0MwT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xSAUF7p1z5Se0MwT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xSAUF7p1z5Se0MwT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xSAUF7p1z5Se0MwT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xSAUF7p1z5Se0MwT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xSAUF7p1z5Se0MwT .marker.cross{stroke:#333333;}#mermaid-svg-xSAUF7p1z5Se0MwT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xSAUF7p1z5Se0MwT p{margin:0;}#mermaid-svg-xSAUF7p1z5Se0MwT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xSAUF7p1z5Se0MwT .cluster-label text{fill:#333;}#mermaid-svg-xSAUF7p1z5Se0MwT .cluster-label span{color:#333;}#mermaid-svg-xSAUF7p1z5Se0MwT .cluster-label span p{background-color:transparent;}#mermaid-svg-xSAUF7p1z5Se0MwT .label text,#mermaid-svg-xSAUF7p1z5Se0MwT span{fill:#333;color:#333;}#mermaid-svg-xSAUF7p1z5Se0MwT .node rect,#mermaid-svg-xSAUF7p1z5Se0MwT .node circle,#mermaid-svg-xSAUF7p1z5Se0MwT .node ellipse,#mermaid-svg-xSAUF7p1z5Se0MwT .node polygon,#mermaid-svg-xSAUF7p1z5Se0MwT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xSAUF7p1z5Se0MwT .rough-node .label text,#mermaid-svg-xSAUF7p1z5Se0MwT .node .label text,#mermaid-svg-xSAUF7p1z5Se0MwT .image-shape .label,#mermaid-svg-xSAUF7p1z5Se0MwT .icon-shape .label{text-anchor:middle;}#mermaid-svg-xSAUF7p1z5Se0MwT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xSAUF7p1z5Se0MwT .rough-node .label,#mermaid-svg-xSAUF7p1z5Se0MwT .node .label,#mermaid-svg-xSAUF7p1z5Se0MwT .image-shape .label,#mermaid-svg-xSAUF7p1z5Se0MwT .icon-shape .label{text-align:center;}#mermaid-svg-xSAUF7p1z5Se0MwT .node.clickable{cursor:pointer;}#mermaid-svg-xSAUF7p1z5Se0MwT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xSAUF7p1z5Se0MwT .arrowheadPath{fill:#333333;}#mermaid-svg-xSAUF7p1z5Se0MwT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xSAUF7p1z5Se0MwT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xSAUF7p1z5Se0MwT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xSAUF7p1z5Se0MwT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xSAUF7p1z5Se0MwT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xSAUF7p1z5Se0MwT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xSAUF7p1z5Se0MwT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xSAUF7p1z5Se0MwT .cluster text{fill:#333;}#mermaid-svg-xSAUF7p1z5Se0MwT .cluster span{color:#333;}#mermaid-svg-xSAUF7p1z5Se0MwT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xSAUF7p1z5Se0MwT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xSAUF7p1z5Se0MwT rect.text{fill:none;stroke-width:0;}#mermaid-svg-xSAUF7p1z5Se0MwT .icon-shape,#mermaid-svg-xSAUF7p1z5Se0MwT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xSAUF7p1z5Se0MwT .icon-shape p,#mermaid-svg-xSAUF7p1z5Se0MwT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xSAUF7p1z5Se0MwT .icon-shape .label rect,#mermaid-svg-xSAUF7p1z5Se0MwT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xSAUF7p1z5Se0MwT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xSAUF7p1z5Se0MwT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xSAUF7p1z5Se0MwT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

开始 mergereadnext()
getlen(srcTape) 获取 tuple 长度
tuplen == 0 ?
返回 false(EOF)
READTUP() 读取完整 tuple
返回 true
结束


tuplesort_heap_insert 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * Insert a new tuple into an empty or existing heap, maintaining the
 * heap invariant.  Caller is responsible for ensuring there's room.
 * 将一个新 tuple 插入到小根堆中(空堆或已有堆),维持堆的有序性
 */
static void
tuplesort_heap_insert(Tuplesortstate *state, SortTuple *tuple)
{
	SortTuple  *memtuples;                                  // 指向堆数组的指针
	int			j;                                          // 当前插入位置的索引

	memtuples = state->memtuples;                           // 获取堆数组基地址
	Assert(state->memtupcount < state->memtupsize);         // 断言:堆还有可用空间

	CHECK_FOR_INTERRUPTS();                                 // 检查查询是否被中断(允许取消)

	/*
	 * Sift-up the new entry, per Knuth 5.2.3 exercise 16.
	 */
	j = state->memtupcount++;                               // 把新元素放在堆尾,并增加计数
	while (j > 0)                                           // 从底部向上调整(sift-up)
	{
		int			i = (j - 1) >> 1;                       // 计算父节点索引(0-based)

		if (COMPARETUP(state, tuple, &memtuples[i]) >= 0)   // 如果新元素大于等于父节点,位置正确
			break;
		memtuples[j] = memtuples[i];                        // 父节点下移
		j = i;                                              // 继续向上比较
	}
	memtuples[j] = *tuple;                                  // 把新 tuple 放在最终位置
}
2. 详细功能解读

  该函数实现小根堆的插入操作sift-up)。新元素先放在堆的末尾,然后不断与父节点比较并上浮,直到满足堆的性质(父节点小于等于子节点)。这是归并过程中向堆中添加新 tuple 的核心函数。

3. 流程图

#mermaid-svg-tB1lgp047e6lNImR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tB1lgp047e6lNImR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tB1lgp047e6lNImR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tB1lgp047e6lNImR .error-icon{fill:#552222;}#mermaid-svg-tB1lgp047e6lNImR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tB1lgp047e6lNImR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tB1lgp047e6lNImR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tB1lgp047e6lNImR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tB1lgp047e6lNImR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tB1lgp047e6lNImR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tB1lgp047e6lNImR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tB1lgp047e6lNImR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tB1lgp047e6lNImR .marker.cross{stroke:#333333;}#mermaid-svg-tB1lgp047e6lNImR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tB1lgp047e6lNImR p{margin:0;}#mermaid-svg-tB1lgp047e6lNImR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tB1lgp047e6lNImR .cluster-label text{fill:#333;}#mermaid-svg-tB1lgp047e6lNImR .cluster-label span{color:#333;}#mermaid-svg-tB1lgp047e6lNImR .cluster-label span p{background-color:transparent;}#mermaid-svg-tB1lgp047e6lNImR .label text,#mermaid-svg-tB1lgp047e6lNImR span{fill:#333;color:#333;}#mermaid-svg-tB1lgp047e6lNImR .node rect,#mermaid-svg-tB1lgp047e6lNImR .node circle,#mermaid-svg-tB1lgp047e6lNImR .node ellipse,#mermaid-svg-tB1lgp047e6lNImR .node polygon,#mermaid-svg-tB1lgp047e6lNImR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tB1lgp047e6lNImR .rough-node .label text,#mermaid-svg-tB1lgp047e6lNImR .node .label text,#mermaid-svg-tB1lgp047e6lNImR .image-shape .label,#mermaid-svg-tB1lgp047e6lNImR .icon-shape .label{text-anchor:middle;}#mermaid-svg-tB1lgp047e6lNImR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tB1lgp047e6lNImR .rough-node .label,#mermaid-svg-tB1lgp047e6lNImR .node .label,#mermaid-svg-tB1lgp047e6lNImR .image-shape .label,#mermaid-svg-tB1lgp047e6lNImR .icon-shape .label{text-align:center;}#mermaid-svg-tB1lgp047e6lNImR .node.clickable{cursor:pointer;}#mermaid-svg-tB1lgp047e6lNImR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tB1lgp047e6lNImR .arrowheadPath{fill:#333333;}#mermaid-svg-tB1lgp047e6lNImR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tB1lgp047e6lNImR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tB1lgp047e6lNImR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tB1lgp047e6lNImR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tB1lgp047e6lNImR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tB1lgp047e6lNImR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tB1lgp047e6lNImR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tB1lgp047e6lNImR .cluster text{fill:#333;}#mermaid-svg-tB1lgp047e6lNImR .cluster span{color:#333;}#mermaid-svg-tB1lgp047e6lNImR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tB1lgp047e6lNImR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tB1lgp047e6lNImR rect.text{fill:none;stroke-width:0;}#mermaid-svg-tB1lgp047e6lNImR .icon-shape,#mermaid-svg-tB1lgp047e6lNImR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tB1lgp047e6lNImR .icon-shape p,#mermaid-svg-tB1lgp047e6lNImR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tB1lgp047e6lNImR .icon-shape .label rect,#mermaid-svg-tB1lgp047e6lNImR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tB1lgp047e6lNImR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tB1lgp047e6lNImR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tB1lgp047e6lNImR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是


开始 tuplesort_heap_insert()
memtuples = state->memtuples
断言:堆有可用空间
检查中断
j = memtupcount++

新元素暂放堆尾
j > 0 ?
i = (j-1)>>1

计算父节点
新tuple >= 父节点 ?
位置正确,跳出循环
父节点下移

j = i
memtuplesj = tuple
结束


tuplesort_heap_delete_top 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * Remove the tuple at state->memtuples[0] from the heap.  Decrement
 * memtupcount, and sift up to maintain the heap invariant.
 * 从堆顶删除最小 tuple,减少计数,并通过替换维持堆性质
 */
static void
tuplesort_heap_delete_top(Tuplesortstate *state)
{
	SortTuple  *memtuples = state->memtuples;               // 堆数组指针
	SortTuple  *tuple;                                      // 用于存放堆尾元素的临时指针

	if (--state->memtupcount <= 0)                          // 计数减1,如果堆已空则直接返回
		return;

	/*
	 * Remove the last tuple in the heap, and re-insert it by replacing the top.
	 */
	tuple = &memtuples[state->memtupcount];                 // 取出堆尾最后一个元素
	tuplesort_heap_replace_top(state, tuple);               // 用堆尾元素替换堆顶,并下沉调整
}
2. 详细功能解读

  该函数删除堆顶的最小 tuple(已由调用者释放内存)。它将堆的最后一个元素移到堆顶,然后调用 tuplesort_heap_replace_top 进行下沉调整,维持小根堆性质。这是归并循环中处理 run 结束时的关键操作。

3. 流程图

#mermaid-svg-a6ddlTHGapfx4X5s{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a6ddlTHGapfx4X5s .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a6ddlTHGapfx4X5s .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a6ddlTHGapfx4X5s .error-icon{fill:#552222;}#mermaid-svg-a6ddlTHGapfx4X5s .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a6ddlTHGapfx4X5s .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a6ddlTHGapfx4X5s .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a6ddlTHGapfx4X5s .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a6ddlTHGapfx4X5s .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a6ddlTHGapfx4X5s .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a6ddlTHGapfx4X5s .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a6ddlTHGapfx4X5s .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a6ddlTHGapfx4X5s .marker.cross{stroke:#333333;}#mermaid-svg-a6ddlTHGapfx4X5s svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a6ddlTHGapfx4X5s p{margin:0;}#mermaid-svg-a6ddlTHGapfx4X5s .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a6ddlTHGapfx4X5s .cluster-label text{fill:#333;}#mermaid-svg-a6ddlTHGapfx4X5s .cluster-label span{color:#333;}#mermaid-svg-a6ddlTHGapfx4X5s .cluster-label span p{background-color:transparent;}#mermaid-svg-a6ddlTHGapfx4X5s .label text,#mermaid-svg-a6ddlTHGapfx4X5s span{fill:#333;color:#333;}#mermaid-svg-a6ddlTHGapfx4X5s .node rect,#mermaid-svg-a6ddlTHGapfx4X5s .node circle,#mermaid-svg-a6ddlTHGapfx4X5s .node ellipse,#mermaid-svg-a6ddlTHGapfx4X5s .node polygon,#mermaid-svg-a6ddlTHGapfx4X5s .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a6ddlTHGapfx4X5s .rough-node .label text,#mermaid-svg-a6ddlTHGapfx4X5s .node .label text,#mermaid-svg-a6ddlTHGapfx4X5s .image-shape .label,#mermaid-svg-a6ddlTHGapfx4X5s .icon-shape .label{text-anchor:middle;}#mermaid-svg-a6ddlTHGapfx4X5s .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a6ddlTHGapfx4X5s .rough-node .label,#mermaid-svg-a6ddlTHGapfx4X5s .node .label,#mermaid-svg-a6ddlTHGapfx4X5s .image-shape .label,#mermaid-svg-a6ddlTHGapfx4X5s .icon-shape .label{text-align:center;}#mermaid-svg-a6ddlTHGapfx4X5s .node.clickable{cursor:pointer;}#mermaid-svg-a6ddlTHGapfx4X5s .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a6ddlTHGapfx4X5s .arrowheadPath{fill:#333333;}#mermaid-svg-a6ddlTHGapfx4X5s .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a6ddlTHGapfx4X5s .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a6ddlTHGapfx4X5s .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a6ddlTHGapfx4X5s .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a6ddlTHGapfx4X5s .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a6ddlTHGapfx4X5s .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a6ddlTHGapfx4X5s .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a6ddlTHGapfx4X5s .cluster text{fill:#333;}#mermaid-svg-a6ddlTHGapfx4X5s .cluster span{color:#333;}#mermaid-svg-a6ddlTHGapfx4X5s div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a6ddlTHGapfx4X5s .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a6ddlTHGapfx4X5s rect.text{fill:none;stroke-width:0;}#mermaid-svg-a6ddlTHGapfx4X5s .icon-shape,#mermaid-svg-a6ddlTHGapfx4X5s .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a6ddlTHGapfx4X5s .icon-shape p,#mermaid-svg-a6ddlTHGapfx4X5s .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a6ddlTHGapfx4X5s .icon-shape .label rect,#mermaid-svg-a6ddlTHGapfx4X5s .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a6ddlTHGapfx4X5s .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a6ddlTHGapfx4X5s .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a6ddlTHGapfx4X5s :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

开始 tuplesort_heap_delete_top()
memtupcount--
memtupcount <= 0 ?
直接返回(堆已空)
取出堆尾元素
调用 tuplesort_heap_replace_top

用堆尾替换堆顶并调整
结束


tuplesort_heap_replace_top 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * Replace the tuple at state->memtuples[0] with a new tuple.  Sift up to
 * maintain the heap invariant.
 * 用新 tuple 替换堆顶元素,并进行下沉调整以维持堆性质
 */
static void
tuplesort_heap_replace_top(Tuplesortstate *state, SortTuple *tuple)
{
	SortTuple  *memtuples = state->memtuples;               // 堆数组指针
	unsigned int i, n;                                      // i为当前位置,n为堆当前元素个数

	Assert(state->memtupcount >= 1);                        // 断言:堆中至少有一个元素

	CHECK_FOR_INTERRUPTS();                                 // 检查查询是否被中断

	n = state->memtupcount;                                 // 当前堆中元素总数
	i = 0;                                                  // 从堆顶(位置0)开始下沉

	for (;;)                                                // 下沉循环
	{
		unsigned int j = 2 * i + 1;                         // 左子节点索引

		if (j >= n)                                         // 没有子节点,位置已正确
			break;
		if (j + 1 < n &&                                    // 如果右子节点存在且更小
			COMPARETUP(state, &memtuples[j], &memtuples[j + 1]) > 0)
			j++;                                            // 选择更小的子节点

		if (COMPARETUP(state, tuple, &memtuples[j]) <= 0)   // 新元素小于等于子节点,位置正确
			break;

		memtuples[i] = memtuples[j];                        // 子节点上移
		i = j;                                              // 继续向下比较
	}
	memtuples[i] = *tuple;                                  // 把新 tuple 放在最终位置
}
2. 详细功能解读

  这是堆操作中最核心的下沉函数(sift-down)。用于替换堆顶后维持堆的有序性。在归并过程中被频繁调用(每次写出一个 tuple 后都需要补充新 tuple)。

3. 流程图

#mermaid-svg-qllcqfsU4tycIkPm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qllcqfsU4tycIkPm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qllcqfsU4tycIkPm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qllcqfsU4tycIkPm .error-icon{fill:#552222;}#mermaid-svg-qllcqfsU4tycIkPm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qllcqfsU4tycIkPm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qllcqfsU4tycIkPm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qllcqfsU4tycIkPm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qllcqfsU4tycIkPm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qllcqfsU4tycIkPm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qllcqfsU4tycIkPm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qllcqfsU4tycIkPm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qllcqfsU4tycIkPm .marker.cross{stroke:#333333;}#mermaid-svg-qllcqfsU4tycIkPm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qllcqfsU4tycIkPm p{margin:0;}#mermaid-svg-qllcqfsU4tycIkPm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qllcqfsU4tycIkPm .cluster-label text{fill:#333;}#mermaid-svg-qllcqfsU4tycIkPm .cluster-label span{color:#333;}#mermaid-svg-qllcqfsU4tycIkPm .cluster-label span p{background-color:transparent;}#mermaid-svg-qllcqfsU4tycIkPm .label text,#mermaid-svg-qllcqfsU4tycIkPm span{fill:#333;color:#333;}#mermaid-svg-qllcqfsU4tycIkPm .node rect,#mermaid-svg-qllcqfsU4tycIkPm .node circle,#mermaid-svg-qllcqfsU4tycIkPm .node ellipse,#mermaid-svg-qllcqfsU4tycIkPm .node polygon,#mermaid-svg-qllcqfsU4tycIkPm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qllcqfsU4tycIkPm .rough-node .label text,#mermaid-svg-qllcqfsU4tycIkPm .node .label text,#mermaid-svg-qllcqfsU4tycIkPm .image-shape .label,#mermaid-svg-qllcqfsU4tycIkPm .icon-shape .label{text-anchor:middle;}#mermaid-svg-qllcqfsU4tycIkPm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qllcqfsU4tycIkPm .rough-node .label,#mermaid-svg-qllcqfsU4tycIkPm .node .label,#mermaid-svg-qllcqfsU4tycIkPm .image-shape .label,#mermaid-svg-qllcqfsU4tycIkPm .icon-shape .label{text-align:center;}#mermaid-svg-qllcqfsU4tycIkPm .node.clickable{cursor:pointer;}#mermaid-svg-qllcqfsU4tycIkPm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qllcqfsU4tycIkPm .arrowheadPath{fill:#333333;}#mermaid-svg-qllcqfsU4tycIkPm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qllcqfsU4tycIkPm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qllcqfsU4tycIkPm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qllcqfsU4tycIkPm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qllcqfsU4tycIkPm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qllcqfsU4tycIkPm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qllcqfsU4tycIkPm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qllcqfsU4tycIkPm .cluster text{fill:#333;}#mermaid-svg-qllcqfsU4tycIkPm .cluster span{color:#333;}#mermaid-svg-qllcqfsU4tycIkPm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qllcqfsU4tycIkPm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qllcqfsU4tycIkPm rect.text{fill:none;stroke-width:0;}#mermaid-svg-qllcqfsU4tycIkPm .icon-shape,#mermaid-svg-qllcqfsU4tycIkPm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qllcqfsU4tycIkPm .icon-shape p,#mermaid-svg-qllcqfsU4tycIkPm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qllcqfsU4tycIkPm .icon-shape .label rect,#mermaid-svg-qllcqfsU4tycIkPm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qllcqfsU4tycIkPm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qllcqfsU4tycIkPm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qllcqfsU4tycIkPm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是





开始 tuplesort_heap_replace_top()
断言:堆非空
检查中断
n = memtupcount

i = 0(从堆顶开始)
for 无限循环
j = 2*i + 1

左子节点
j >= n ?
无子节点,结束
右子节点更小 ?
选择更小的子节点
新tuple <= 子节点 ?
位置正确,结束
子节点上移

i = j
memtuplesi = tuple
结束

tuplesort_puttuple_common 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * Shared code for tuple and datum cases.
 * 元组和 Datum 排序的公共处理函数
 */
void
tuplesort_puttuple_common(Tuplesortstate *state, SortTuple *tuple,
						  bool useAbbrev, Size tuplen)
{
	/* 切换到排序专用的内存上下文,保护内存操作 */
	MemoryContext oldcontext = MemoryContextSwitchTo(state->base.sortcontext);

	/* 断言:当前不是并行 Leader 进程 */
	Assert(!LEADER(state));

	/* 统计当前 tuple 占用的内存 */
	USEMEM(state, tuplen);
	state->tupleMem += tuplen;

	/* 如果不使用缩写键优化 */
	if (!useAbbrev)
	{
		/*
		 * 保持普通的 Datum 表示形式,或 NULL 值。
		 * 如果存在转换器,则不处理 NULL 值。
		 */
	}
	/* 如果可以使用缩写键且不需要中止 */
	else if (!consider_abort_common(state))
	{
		/* 存储缩写键表示 */
		tuple->datum1 = state->base.sortKeys->abbrev_converter(tuple->datum1,
															   state->base.sortKeys);
	}
	else
	{
		/*
		 * 放弃缩写键优化,恢复一致状态。
		 * 并移除已复制 tuple 中的缩写键。
		 */
		REMOVEABBREV(state, state->memtuples, state->memtupcount);
	}

	/* 根据当前排序状态进行不同处理 */
	switch (state->status)
	{
		case TSS_INITIAL:
			/*
			 * 在 TSS_INITIAL 阶段,将 tuple 保存到未排序数组中
			 */

			/* 如果数组即将满,先尝试扩展 */
			if (state->memtupcount >= state->memtupsize - 1)
			{
				(void) grow_memtuples(state);
				Assert(state->memtupcount < state->memtupsize);
			}
			/* 将 tuple 放入数组末尾 */
			state->memtuples[state->memtupcount++] = *tuple;

			/*
			 * 检查是否需要切换到有界堆排序(bounded heapsort)
			 */
			if (state->bounded &&
				(state->memtupcount > state->bound * 2 ||
				 (state->memtupcount > state->bound && LACKMEM(state))))
			{
				if (trace_sort)
					elog(LOG, "switching to bounded heapsort at %d tuples: %s",
						 state->memtupcount,
						 pg_rusage_show(&state->ru_start));
				make_bounded_heap(state);
				MemoryContextSwitchTo(oldcontext);
				return;
			}

			/*
			 * 如果仍然能全部放在内存中,则继续内存排序
			 */
			if (state->memtupcount < state->memtupsize && !LACKMEM(state))
			{
				MemoryContextSwitchTo(oldcontext);
				return;
			}

			/* 内存不足,切换到基于磁带的外部排序 */
			inittapes(state, true);

			/* 将内存中的所有 tuple 转储到磁带 */
			dumptuples(state, false);
			break;

		case TSS_BOUNDED:
			/*
			 * 有界堆排序模式:只保留 top-N 最小的 tuple
			 */

			/* 如果新 tuple 小于或等于堆顶,则直接丢弃 */
			if (COMPARETUP(state, tuple, &state->memtuples[0]) <= 0)
			{
				free_sort_tuple(state, tuple);
				CHECK_FOR_INTERRUPTS();
			}
			else
			{
				/* 丢弃堆顶,用新 tuple 替换 */
				free_sort_tuple(state, &state->memtuples[0]);
				tuplesort_heap_replace_top(state, tuple);
			}
			break;

		case TSS_BUILDRUNS:
			/*
			 * 外部排序的初始 runs 构建阶段
			 */

			/* 将 tuple 放入数组(此时一定有空间) */
			state->memtuples[state->memtupcount++] = *tuple;

			/* 如果超过内存限制,则立即转储到磁带 */
			dumptuples(state, false);
			break;

		default:
			elog(ERROR, "invalid tuplesort state");
			break;
	}

	/* 恢复原来的内存上下文 */
	MemoryContextSwitchTo(oldcontext);
}
2. 详细功能解读

函数作用

  这是 PostgreSQL 外部排序中插入单个 tuple 的核心公共函数 ,支持普通 tupleDatum 两种情况。它根据当前排序状态(TSS_INITIALTSS_BOUNDEDTSS_BUILDRUNS)决定如何处理新到达的 tuple

主要流程

  1. 内存上下文切换 + 内存统计。
  2. 缩写键(Abbreviation)处理:尝试使用缩写键加速比较,如果需要则中止优化。
  3. 状态机分支处理
    • TSS_INITIAL:内存排序阶段,尽量把所有数据放在内存数组中,内存满时切换到磁带排序。
    • TSS_BOUNDED :有界排序(Top-N),只保留最小的 N 个元素,超出的直接丢弃。
    • TSS_BUILDRUNS :外部排序的 runs 生成阶段,内存满就转储到磁带。
  4. 恢复内存上下文

与本次 Patch 的关系

  该函数在 Polyphase → Balanced k-way Merge 重构中基本保持稳定,是排序入口的关键函数之一,负责在内存和外部存储之间平滑切换。


3. 流程图

#mermaid-svg-bNBnBEN8Jd9dUJhN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bNBnBEN8Jd9dUJhN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bNBnBEN8Jd9dUJhN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bNBnBEN8Jd9dUJhN .error-icon{fill:#552222;}#mermaid-svg-bNBnBEN8Jd9dUJhN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bNBnBEN8Jd9dUJhN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bNBnBEN8Jd9dUJhN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bNBnBEN8Jd9dUJhN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bNBnBEN8Jd9dUJhN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bNBnBEN8Jd9dUJhN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bNBnBEN8Jd9dUJhN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bNBnBEN8Jd9dUJhN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bNBnBEN8Jd9dUJhN .marker.cross{stroke:#333333;}#mermaid-svg-bNBnBEN8Jd9dUJhN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bNBnBEN8Jd9dUJhN p{margin:0;}#mermaid-svg-bNBnBEN8Jd9dUJhN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bNBnBEN8Jd9dUJhN .cluster-label text{fill:#333;}#mermaid-svg-bNBnBEN8Jd9dUJhN .cluster-label span{color:#333;}#mermaid-svg-bNBnBEN8Jd9dUJhN .cluster-label span p{background-color:transparent;}#mermaid-svg-bNBnBEN8Jd9dUJhN .label text,#mermaid-svg-bNBnBEN8Jd9dUJhN span{fill:#333;color:#333;}#mermaid-svg-bNBnBEN8Jd9dUJhN .node rect,#mermaid-svg-bNBnBEN8Jd9dUJhN .node circle,#mermaid-svg-bNBnBEN8Jd9dUJhN .node ellipse,#mermaid-svg-bNBnBEN8Jd9dUJhN .node polygon,#mermaid-svg-bNBnBEN8Jd9dUJhN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bNBnBEN8Jd9dUJhN .rough-node .label text,#mermaid-svg-bNBnBEN8Jd9dUJhN .node .label text,#mermaid-svg-bNBnBEN8Jd9dUJhN .image-shape .label,#mermaid-svg-bNBnBEN8Jd9dUJhN .icon-shape .label{text-anchor:middle;}#mermaid-svg-bNBnBEN8Jd9dUJhN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bNBnBEN8Jd9dUJhN .rough-node .label,#mermaid-svg-bNBnBEN8Jd9dUJhN .node .label,#mermaid-svg-bNBnBEN8Jd9dUJhN .image-shape .label,#mermaid-svg-bNBnBEN8Jd9dUJhN .icon-shape .label{text-align:center;}#mermaid-svg-bNBnBEN8Jd9dUJhN .node.clickable{cursor:pointer;}#mermaid-svg-bNBnBEN8Jd9dUJhN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bNBnBEN8Jd9dUJhN .arrowheadPath{fill:#333333;}#mermaid-svg-bNBnBEN8Jd9dUJhN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bNBnBEN8Jd9dUJhN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bNBnBEN8Jd9dUJhN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bNBnBEN8Jd9dUJhN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bNBnBEN8Jd9dUJhN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bNBnBEN8Jd9dUJhN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bNBnBEN8Jd9dUJhN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bNBnBEN8Jd9dUJhN .cluster text{fill:#333;}#mermaid-svg-bNBnBEN8Jd9dUJhN .cluster span{color:#333;}#mermaid-svg-bNBnBEN8Jd9dUJhN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bNBnBEN8Jd9dUJhN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bNBnBEN8Jd9dUJhN rect.text{fill:none;stroke-width:0;}#mermaid-svg-bNBnBEN8Jd9dUJhN .icon-shape,#mermaid-svg-bNBnBEN8Jd9dUJhN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bNBnBEN8Jd9dUJhN .icon-shape p,#mermaid-svg-bNBnBEN8Jd9dUJhN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bNBnBEN8Jd9dUJhN .icon-shape .label rect,#mermaid-svg-bNBnBEN8Jd9dUJhN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bNBnBEN8Jd9dUJhN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bNBnBEN8Jd9dUJhN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bNBnBEN8Jd9dUJhN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是







开始 tuplesort_puttuple_common()
切换到 sortcontext

USEMEM 统计内存
useAbbrev ?
处理缩写键

consider_abort_common
switch state->status
TSS_INITIAL
grow_memtuples

扩展数组
插入数组 memtuples\[\]
bounded 且 达到阈值?
切换到 bounded heapsort
仍在内存限制内?
inittapes + dumptuples

切换到磁带排序
TSS_BOUNDED
新tuple <= 堆顶?
丢弃新 tuple
替换堆顶
TSS_BUILDRUNS
插入数组
dumptuples

转储到磁带
恢复原内存上下文
结束


✅ 以下为两个函数的独立完整解读


inittapes 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * inittapes - initialize for tape sorting.
 *
 * This is called only if we have found we won't sort in memory.
 * 初始化磁带排序相关状态,仅在内存排序无法完成时调用
 */
static void
inittapes(Tuplesortstate *state, bool mergeruns)
{
	/* 断言:当前不是并行 Leader 进程 */
	Assert(!LEADER(state));

	if (mergeruns)
	{
		/* 根据可用内存计算归并时使用的输入磁带数量 */
		state->maxTapes = tuplesort_merge_order(state->allowedMem);
	}
	else
	{
		/* Worker 进程可能只需输出单个 run,无需多路归并 */
		Assert(WORKER(state));
		state->maxTapes = MINORDER;
	}

	if (trace_sort)
		elog(LOG, "worker %d switching to external sort with %d tapes: %s",
			 state->worker, state->maxTapes, pg_rusage_show(&state->ru_start));

	/* 初始化磁带通用状态(内存扣除、临时表空间准备) */
	inittapestate(state, state->maxTapes);

	/* 创建 LogicalTapeSet,用于管理所有逻辑磁带 */
	state->tapeset =
		LogicalTapeSetCreate(false,
							 state->shared ? &state->shared->fileset : NULL,
							 state->worker);

	state->currentRun = 0;

	/*
	 * Initialize logical tape arrays.
	 */
	state->inputTapes = NULL;                               // 输入磁带数组初始为空
	state->nInputTapes = 0;                                 // 输入磁带数量为 0
	state->nInputRuns = 0;                                  // 输入 runs 数量为 0

	/* 预分配输出磁带数组 */
	state->outputTapes = palloc0(state->maxTapes * sizeof(LogicalTape *));
	state->nOutputTapes = 0;                                // 输出磁带数量初始为 0
	state->nOutputRuns = 0;                                 // 输出 runs 数量初始为 0

	/* 状态切换到初始 runs 构建阶段 */
	state->status = TSS_BUILDRUNS;

	/* 选择第一个输出磁带 */
	selectnewtape(state);
}
2. 详细功能解读
  • 函数作用 :当内存无法容纳全部数据时,初始化基于磁带的外部排序环境。
  • 核心逻辑
    • 根据 allowedMem 计算合理的归并路数(maxTapes)。
    • 创建 LogicalTapeSet 管理所有临时磁带。
    • 初始化 inputTapes / outputTapes 数组和计数器。
    • 切换状态为 TSS_BUILDRUNS,并选择第一个输出磁带。
  • 这是从内存排序切换到外部排序的关键入口
3. 流程图

#mermaid-svg-C2OYTFLUluyOPNXQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-C2OYTFLUluyOPNXQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-C2OYTFLUluyOPNXQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-C2OYTFLUluyOPNXQ .error-icon{fill:#552222;}#mermaid-svg-C2OYTFLUluyOPNXQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-C2OYTFLUluyOPNXQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-C2OYTFLUluyOPNXQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-C2OYTFLUluyOPNXQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-C2OYTFLUluyOPNXQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-C2OYTFLUluyOPNXQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-C2OYTFLUluyOPNXQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-C2OYTFLUluyOPNXQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-C2OYTFLUluyOPNXQ .marker.cross{stroke:#333333;}#mermaid-svg-C2OYTFLUluyOPNXQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-C2OYTFLUluyOPNXQ p{margin:0;}#mermaid-svg-C2OYTFLUluyOPNXQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-C2OYTFLUluyOPNXQ .cluster-label text{fill:#333;}#mermaid-svg-C2OYTFLUluyOPNXQ .cluster-label span{color:#333;}#mermaid-svg-C2OYTFLUluyOPNXQ .cluster-label span p{background-color:transparent;}#mermaid-svg-C2OYTFLUluyOPNXQ .label text,#mermaid-svg-C2OYTFLUluyOPNXQ span{fill:#333;color:#333;}#mermaid-svg-C2OYTFLUluyOPNXQ .node rect,#mermaid-svg-C2OYTFLUluyOPNXQ .node circle,#mermaid-svg-C2OYTFLUluyOPNXQ .node ellipse,#mermaid-svg-C2OYTFLUluyOPNXQ .node polygon,#mermaid-svg-C2OYTFLUluyOPNXQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-C2OYTFLUluyOPNXQ .rough-node .label text,#mermaid-svg-C2OYTFLUluyOPNXQ .node .label text,#mermaid-svg-C2OYTFLUluyOPNXQ .image-shape .label,#mermaid-svg-C2OYTFLUluyOPNXQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-C2OYTFLUluyOPNXQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-C2OYTFLUluyOPNXQ .rough-node .label,#mermaid-svg-C2OYTFLUluyOPNXQ .node .label,#mermaid-svg-C2OYTFLUluyOPNXQ .image-shape .label,#mermaid-svg-C2OYTFLUluyOPNXQ .icon-shape .label{text-align:center;}#mermaid-svg-C2OYTFLUluyOPNXQ .node.clickable{cursor:pointer;}#mermaid-svg-C2OYTFLUluyOPNXQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-C2OYTFLUluyOPNXQ .arrowheadPath{fill:#333333;}#mermaid-svg-C2OYTFLUluyOPNXQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-C2OYTFLUluyOPNXQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-C2OYTFLUluyOPNXQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-C2OYTFLUluyOPNXQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-C2OYTFLUluyOPNXQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-C2OYTFLUluyOPNXQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-C2OYTFLUluyOPNXQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-C2OYTFLUluyOPNXQ .cluster text{fill:#333;}#mermaid-svg-C2OYTFLUluyOPNXQ .cluster span{color:#333;}#mermaid-svg-C2OYTFLUluyOPNXQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-C2OYTFLUluyOPNXQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-C2OYTFLUluyOPNXQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-C2OYTFLUluyOPNXQ .icon-shape,#mermaid-svg-C2OYTFLUluyOPNXQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-C2OYTFLUluyOPNXQ .icon-shape p,#mermaid-svg-C2OYTFLUluyOPNXQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-C2OYTFLUluyOPNXQ .icon-shape .label rect,#mermaid-svg-C2OYTFLUluyOPNXQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-C2OYTFLUluyOPNXQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-C2OYTFLUluyOPNXQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-C2OYTFLUluyOPNXQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

开始 inittapes(state, mergeruns)
断言:非 Leader 进程
mergeruns ?
计算 maxTapes = tuplesort_merge_order()
maxTapes = MINORDER
记录日志:切换到外部排序
inittapestate()
LogicalTapeSetCreate()
currentRun = 0
初始化 inputTapes / outputTapes 数组和计数器
status = TSS_BUILDRUNS
selectnewtape() 选择第一个输出磁带
结束


dumptuples 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * dumptuples - 将内存中的元组写出到磁盘,生成一个有序的初始Run
 *
 * 作用:当内存满了 或 全部数据输入完毕时,将内存里排序好的元组
 *      一次性写入磁带(临时磁盘文件),形成一个有序Run,为后续归并做准备
 *
 * alltuples = true:表示所有数据已输入完毕,强制写出内存中所有元组
 * alltuples = false:内存不足,需要溢写到磁盘
 */
static void
dumptuples(Tuplesortstate *state, bool alltuples)
{
	int			memtupwrite;                                // 本次需要写出的元组总数
	int			i;

	/*
	 * 1. 快速判断:不需要写出的场景,直接返回
	 *    条件:内存没满 + 内存充足 + 不是最终写出
	 */
	if (state->memtupcount < state->memtupsize && !LACKMEM(state) &&
		!alltuples)
		return;

	/*
	 * 2. 避免生成空的Run(除非是并行worker,必须至少输出一个)
	 */
	if (state->memtupcount == 0 && state->currentRun > 0)
		return;

	// 校验状态:必须处于构建Run的阶段
	Assert(state->status == TSS_BUILDRUNS);

	/*
	 * 3. 安全检查:防止生成的Run数量超出int最大值
	 */
	if (state->currentRun == INT_MAX)
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("cannot have more than %d runs for an external sort",
						INT_MAX)));

	/*
	 * 4. 如果不是第一个Run,选择一个新的输出磁带
	 */
	if (state->currentRun > 0)
		selectnewtape(state);

	// 当前生成的Run编号 +1
	state->currentRun++;

	// 打印trace日志:开始快速排序
	if (trace_sort)
		elog(LOG, "worker %d starting quicksort of run %d: %s",
			 state->worker, state->currentRun,
			 pg_rusage_show(&state->ru_start));

	/*
	 * 5. 核心步骤:对内存中的所有元组执行快速排序
	 */
	tuplesort_sort_memtuples(state);

	// 打印trace日志:排序完成
	if (trace_sort)
		elog(LOG, "worker %d finished quicksort of run %d: %s",
			 state->worker, state->currentRun,
			 pg_rusage_show(&state->ru_start));

	/*
	 * 6. 循环将排序后的元组逐个写入磁带(生成有序Run)
	 */
	memtupwrite = state->memtupcount;
	for (i = 0; i < memtupwrite; i++)
	{
		SortTuple  *stup = &state->memtuples[i];
		WRITETUP(state, state->destTape, stup);             // 写入磁盘磁带
	}

	/*
	 * 7. 清空内存计数器:所有元组已写出,内存为空
	 */
	state->memtupcount = 0;

	/*
	 * 8. 重置内存上下文,一次性释放所有元组内存
	 * 优势:避免碎片,比逐个pfree快得多
	 */
	MemoryContextReset(state->base.tuplecontext);

	/*
	 * 9. 更新内存统计信息,标记内存已释放
	 */
	FREEMEM(state, state->tupleMem);
	state->tupleMem = 0;

	/*
	 * 10. 写入Run结束标记,告诉归并阶段此Run已写完
	 */
	markrunend(state->destTape);

	// 打印trace日志:写出Run完成
	if (trace_sort)
		elog(LOG, "worker %d finished writing run %d to tape %d: %s",
			 state->worker, state->currentRun, (state->currentRun - 1) % state->nOutputTapes + 1,
			 pg_rusage_show(&state->ru_start));
}
2. 详细功能解读
  • 函数作用 :将内存中积累的 tuple 排序后,作为一个初始有序 run 写入磁带。
  • 关键步骤
    • 检查是否需要转储(内存不足或最终调用)。
    • 选择输出磁带。
    • 对内存 tuple 进行 Quicksort
    • 批量写入当前 destTape
    • 重置内存上下文 + 更新内存统计。
    • 写入 run 结束标记。
  • 这是生成初始有序 runs 的核心函数 ,在新版 Balanced k-way Merge 中配合 selectnewtape 使用。
3. 流程图

#mermaid-svg-b4bdWYYHYHqCg8ub{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-b4bdWYYHYHqCg8ub .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-b4bdWYYHYHqCg8ub .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-b4bdWYYHYHqCg8ub .error-icon{fill:#552222;}#mermaid-svg-b4bdWYYHYHqCg8ub .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-b4bdWYYHYHqCg8ub .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-b4bdWYYHYHqCg8ub .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-b4bdWYYHYHqCg8ub .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-b4bdWYYHYHqCg8ub .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-b4bdWYYHYHqCg8ub .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-b4bdWYYHYHqCg8ub .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-b4bdWYYHYHqCg8ub .marker{fill:#333333;stroke:#333333;}#mermaid-svg-b4bdWYYHYHqCg8ub .marker.cross{stroke:#333333;}#mermaid-svg-b4bdWYYHYHqCg8ub svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-b4bdWYYHYHqCg8ub p{margin:0;}#mermaid-svg-b4bdWYYHYHqCg8ub .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-b4bdWYYHYHqCg8ub .cluster-label text{fill:#333;}#mermaid-svg-b4bdWYYHYHqCg8ub .cluster-label span{color:#333;}#mermaid-svg-b4bdWYYHYHqCg8ub .cluster-label span p{background-color:transparent;}#mermaid-svg-b4bdWYYHYHqCg8ub .label text,#mermaid-svg-b4bdWYYHYHqCg8ub span{fill:#333;color:#333;}#mermaid-svg-b4bdWYYHYHqCg8ub .node rect,#mermaid-svg-b4bdWYYHYHqCg8ub .node circle,#mermaid-svg-b4bdWYYHYHqCg8ub .node ellipse,#mermaid-svg-b4bdWYYHYHqCg8ub .node polygon,#mermaid-svg-b4bdWYYHYHqCg8ub .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-b4bdWYYHYHqCg8ub .rough-node .label text,#mermaid-svg-b4bdWYYHYHqCg8ub .node .label text,#mermaid-svg-b4bdWYYHYHqCg8ub .image-shape .label,#mermaid-svg-b4bdWYYHYHqCg8ub .icon-shape .label{text-anchor:middle;}#mermaid-svg-b4bdWYYHYHqCg8ub .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-b4bdWYYHYHqCg8ub .rough-node .label,#mermaid-svg-b4bdWYYHYHqCg8ub .node .label,#mermaid-svg-b4bdWYYHYHqCg8ub .image-shape .label,#mermaid-svg-b4bdWYYHYHqCg8ub .icon-shape .label{text-align:center;}#mermaid-svg-b4bdWYYHYHqCg8ub .node.clickable{cursor:pointer;}#mermaid-svg-b4bdWYYHYHqCg8ub .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-b4bdWYYHYHqCg8ub .arrowheadPath{fill:#333333;}#mermaid-svg-b4bdWYYHYHqCg8ub .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-b4bdWYYHYHqCg8ub .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-b4bdWYYHYHqCg8ub .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b4bdWYYHYHqCg8ub .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-b4bdWYYHYHqCg8ub .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b4bdWYYHYHqCg8ub .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-b4bdWYYHYHqCg8ub .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-b4bdWYYHYHqCg8ub .cluster text{fill:#333;}#mermaid-svg-b4bdWYYHYHqCg8ub .cluster span{color:#333;}#mermaid-svg-b4bdWYYHYHqCg8ub div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-b4bdWYYHYHqCg8ub .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-b4bdWYYHYHqCg8ub rect.text{fill:none;stroke-width:0;}#mermaid-svg-b4bdWYYHYHqCg8ub .icon-shape,#mermaid-svg-b4bdWYYHYHqCg8ub .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b4bdWYYHYHqCg8ub .icon-shape p,#mermaid-svg-b4bdWYYHYHqCg8ub .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-b4bdWYYHYHqCg8ub .icon-shape .label rect,#mermaid-svg-b4bdWYYHYHqCg8ub .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b4bdWYYHYHqCg8ub .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-b4bdWYYHYHqCg8ub .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-b4bdWYYHYHqCg8ub :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是







循环结束
开始 dumptuples()

作用:内存溢写,生成有序Run
内存未满 && 内存充足 && 非最终写出?
直接 return 无需操作
元组为空 && 非首次Run?
直接 return 不生成空Run
校验状态:TSS_BUILDRUNS
Run数量达到INT_MAX?
报错:溢出
非第一个Run?
selectnewtape() 选择输出磁带
currentRun++ Run编号+1
tuplesort_sort_memtuples()

对内存元组执行快速排序
循环遍历所有元组
WRITETUP 写入磁带
memtupcount = 0 清空内存计数
MemoryContextReset 一次性释放所有内存
更新内存统计 tupleMem = 0
markrunend 写入Run结束符
函数结束


inittapestate 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * inittapestate - initialize generic tape management state
 * 初始化通用的磁带管理状态
 */
static void
inittapestate(Tuplesortstate *state, int maxTapes)
{
	int64		tapeSpace;                                  // 用于计算磁带缓冲区所需内存空间

	/*
	 * Decrease availMem to reflect the space needed for tape buffers; but
	 * don't decrease it to the point that we have no room for tuples.
	 */
	tapeSpace = (int64) maxTapes * TAPE_BUFFER_OVERHEAD;    // 计算所有磁带缓冲区的基础开销

	/* 
	 * 如果扣除磁带缓冲后仍有足够空间存放 tuples,则扣除这部分内存
	 * 避免在仅排序 Datum 的场景下把可用内存扣成负数
	 */
	if (tapeSpace + GetMemoryChunkSpace(state->memtuples) < state->allowedMem)
		USEMEM(state, tapeSpace);                           // 正式扣除磁带缓冲区内存

	/*
	 * Make sure that the temp file(s) underlying the tape set are created in
	 * suitable temp tablespaces. 
	 */
	PrepareTempTablespaces();                               // 确保临时文件创建在合适的临时表空间
}
2. 详细功能解读
  • 函数作用 :初始化磁带相关的通用状态,主要负责内存预扣除临时表空间准备
  • 调用时机 :在 inittapes() 中被调用,用于创建 LogicalTapeSet 之前进行必要的前置初始化。
  • 核心逻辑
    • 计算所有磁带需要的缓冲区基础开销(TAPE_BUFFER_OVERHEAD)。
    • 智能判断是否扣除这部分内存(防止把 availMem 扣成负值,尤其在 pass-by-value Datum 排序场景)。
    • 调用 PrepareTempTablespaces() 确保临时文件使用正确的表空间(支持并行排序)。
  • Patch 的关系:新版虽然重构了磁带管理,但这个函数被保留并简化,职责更加专注。
3. 流程图

#mermaid-svg-gkKuDBraFU2Pv3TG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gkKuDBraFU2Pv3TG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gkKuDBraFU2Pv3TG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gkKuDBraFU2Pv3TG .error-icon{fill:#552222;}#mermaid-svg-gkKuDBraFU2Pv3TG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gkKuDBraFU2Pv3TG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gkKuDBraFU2Pv3TG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gkKuDBraFU2Pv3TG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gkKuDBraFU2Pv3TG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gkKuDBraFU2Pv3TG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gkKuDBraFU2Pv3TG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gkKuDBraFU2Pv3TG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gkKuDBraFU2Pv3TG .marker.cross{stroke:#333333;}#mermaid-svg-gkKuDBraFU2Pv3TG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gkKuDBraFU2Pv3TG p{margin:0;}#mermaid-svg-gkKuDBraFU2Pv3TG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gkKuDBraFU2Pv3TG .cluster-label text{fill:#333;}#mermaid-svg-gkKuDBraFU2Pv3TG .cluster-label span{color:#333;}#mermaid-svg-gkKuDBraFU2Pv3TG .cluster-label span p{background-color:transparent;}#mermaid-svg-gkKuDBraFU2Pv3TG .label text,#mermaid-svg-gkKuDBraFU2Pv3TG span{fill:#333;color:#333;}#mermaid-svg-gkKuDBraFU2Pv3TG .node rect,#mermaid-svg-gkKuDBraFU2Pv3TG .node circle,#mermaid-svg-gkKuDBraFU2Pv3TG .node ellipse,#mermaid-svg-gkKuDBraFU2Pv3TG .node polygon,#mermaid-svg-gkKuDBraFU2Pv3TG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gkKuDBraFU2Pv3TG .rough-node .label text,#mermaid-svg-gkKuDBraFU2Pv3TG .node .label text,#mermaid-svg-gkKuDBraFU2Pv3TG .image-shape .label,#mermaid-svg-gkKuDBraFU2Pv3TG .icon-shape .label{text-anchor:middle;}#mermaid-svg-gkKuDBraFU2Pv3TG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gkKuDBraFU2Pv3TG .rough-node .label,#mermaid-svg-gkKuDBraFU2Pv3TG .node .label,#mermaid-svg-gkKuDBraFU2Pv3TG .image-shape .label,#mermaid-svg-gkKuDBraFU2Pv3TG .icon-shape .label{text-align:center;}#mermaid-svg-gkKuDBraFU2Pv3TG .node.clickable{cursor:pointer;}#mermaid-svg-gkKuDBraFU2Pv3TG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gkKuDBraFU2Pv3TG .arrowheadPath{fill:#333333;}#mermaid-svg-gkKuDBraFU2Pv3TG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gkKuDBraFU2Pv3TG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gkKuDBraFU2Pv3TG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gkKuDBraFU2Pv3TG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gkKuDBraFU2Pv3TG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gkKuDBraFU2Pv3TG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gkKuDBraFU2Pv3TG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gkKuDBraFU2Pv3TG .cluster text{fill:#333;}#mermaid-svg-gkKuDBraFU2Pv3TG .cluster span{color:#333;}#mermaid-svg-gkKuDBraFU2Pv3TG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gkKuDBraFU2Pv3TG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gkKuDBraFU2Pv3TG rect.text{fill:none;stroke-width:0;}#mermaid-svg-gkKuDBraFU2Pv3TG .icon-shape,#mermaid-svg-gkKuDBraFU2Pv3TG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gkKuDBraFU2Pv3TG .icon-shape p,#mermaid-svg-gkKuDBraFU2Pv3TG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gkKuDBraFU2Pv3TG .icon-shape .label rect,#mermaid-svg-gkKuDBraFU2Pv3TG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gkKuDBraFU2Pv3TG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gkKuDBraFU2Pv3TG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gkKuDBraFU2Pv3TG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

开始 inittapestate(state, maxTapes)
计算 tapeSpace = maxTapes * TAPE_BUFFER_OVERHEAD
tapeSpace + memtuples 空间 < allowedMem ?
是:USEMEM 扣除磁带缓冲区内存
否:不扣除(保护 tuple 内存空间)
PrepareTempTablespaces()

准备合适临时表空间
函数结束


selectnewtape 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * selectnewtape -- select next tape to output to.
 *
 * This is called after finishing a run when we know another run
 * must be started. This is used both when building the initial
 * runs, and during merge passes.
 */
static void
selectnewtape(Tuplesortstate *state)
{
	/*
	 * At the beginning of each merge pass, nOutputTapes and nOutputRuns are
	 * both zero. On each call, we create a new output tape to hold the next
	 * run, until maxTapes is reached. After that, we assign new runs to the
	 * existing tapes in a round robin fashion.
	 */
	if (state->nOutputTapes < state->maxTapes)
	{
		/* Create a new tape to hold the next run */
		Assert(state->outputTapes[state->nOutputRuns] == NULL);     // 断言:当前位置应为空
		Assert(state->nOutputRuns == state->nOutputTapes);          // 断言:计数一致

		state->destTape = LogicalTapeCreate(state->tapeset);        // 创建一个新的 LogicalTape
		state->outputTapes[state->nOutputTapes] = state->destTape;  // 存入输出磁带数组
		state->nOutputTapes++;                                      // 输出磁带数量 +1
		state->nOutputRuns++;                                       // 输出 runs 数量 +1
	}
	else
	{
		/*
		 * We have reached the max number of tapes. Append to an existing
		 * tape.
		 */
		/* 已达到最大磁带数,采用轮询方式追加到已有磁带 */
		state->destTape = state->outputTapes[state->nOutputRuns % state->nOutputTapes]; // 轮询选择已有磁带
		state->nOutputRuns++;                                       // 输出 runs 数量 +1
	}
}
2. 详细功能解读
  • 函数作用 :为当前即将生成的 run 选择(或创建)一个输出磁带
  • 使用场景
    • 初始 runs 生成阶段(dumptuples() 中调用)。
    • 每一轮 merge pass 中生成新 run 时调用。
  • 核心策略 (新版 Balanced k-way Merge 的重要设计):
    1. maxTapesruns :每次都创建一个新的输出磁带
    2. 超过 maxTapes :采用轮询(round-robin 方式,将新 run 追加到已有的磁带上。
  • 优点 :实现简单、I/O 分布均匀,避免了旧版 Polyphase 中复杂的斐波那契分布和 dummy runs 逻辑。

3. 流程图

#mermaid-svg-dXfVWzIIg6STNnuA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dXfVWzIIg6STNnuA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dXfVWzIIg6STNnuA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dXfVWzIIg6STNnuA .error-icon{fill:#552222;}#mermaid-svg-dXfVWzIIg6STNnuA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dXfVWzIIg6STNnuA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dXfVWzIIg6STNnuA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dXfVWzIIg6STNnuA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dXfVWzIIg6STNnuA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dXfVWzIIg6STNnuA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dXfVWzIIg6STNnuA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dXfVWzIIg6STNnuA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dXfVWzIIg6STNnuA .marker.cross{stroke:#333333;}#mermaid-svg-dXfVWzIIg6STNnuA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dXfVWzIIg6STNnuA p{margin:0;}#mermaid-svg-dXfVWzIIg6STNnuA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dXfVWzIIg6STNnuA .cluster-label text{fill:#333;}#mermaid-svg-dXfVWzIIg6STNnuA .cluster-label span{color:#333;}#mermaid-svg-dXfVWzIIg6STNnuA .cluster-label span p{background-color:transparent;}#mermaid-svg-dXfVWzIIg6STNnuA .label text,#mermaid-svg-dXfVWzIIg6STNnuA span{fill:#333;color:#333;}#mermaid-svg-dXfVWzIIg6STNnuA .node rect,#mermaid-svg-dXfVWzIIg6STNnuA .node circle,#mermaid-svg-dXfVWzIIg6STNnuA .node ellipse,#mermaid-svg-dXfVWzIIg6STNnuA .node polygon,#mermaid-svg-dXfVWzIIg6STNnuA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dXfVWzIIg6STNnuA .rough-node .label text,#mermaid-svg-dXfVWzIIg6STNnuA .node .label text,#mermaid-svg-dXfVWzIIg6STNnuA .image-shape .label,#mermaid-svg-dXfVWzIIg6STNnuA .icon-shape .label{text-anchor:middle;}#mermaid-svg-dXfVWzIIg6STNnuA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dXfVWzIIg6STNnuA .rough-node .label,#mermaid-svg-dXfVWzIIg6STNnuA .node .label,#mermaid-svg-dXfVWzIIg6STNnuA .image-shape .label,#mermaid-svg-dXfVWzIIg6STNnuA .icon-shape .label{text-align:center;}#mermaid-svg-dXfVWzIIg6STNnuA .node.clickable{cursor:pointer;}#mermaid-svg-dXfVWzIIg6STNnuA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dXfVWzIIg6STNnuA .arrowheadPath{fill:#333333;}#mermaid-svg-dXfVWzIIg6STNnuA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dXfVWzIIg6STNnuA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dXfVWzIIg6STNnuA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dXfVWzIIg6STNnuA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dXfVWzIIg6STNnuA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dXfVWzIIg6STNnuA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dXfVWzIIg6STNnuA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dXfVWzIIg6STNnuA .cluster text{fill:#333;}#mermaid-svg-dXfVWzIIg6STNnuA .cluster span{color:#333;}#mermaid-svg-dXfVWzIIg6STNnuA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dXfVWzIIg6STNnuA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dXfVWzIIg6STNnuA rect.text{fill:none;stroke-width:0;}#mermaid-svg-dXfVWzIIg6STNnuA .icon-shape,#mermaid-svg-dXfVWzIIg6STNnuA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dXfVWzIIg6STNnuA .icon-shape p,#mermaid-svg-dXfVWzIIg6STNnuA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dXfVWzIIg6STNnuA .icon-shape .label rect,#mermaid-svg-dXfVWzIIg6STNnuA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dXfVWzIIg6STNnuA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dXfVWzIIg6STNnuA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dXfVWzIIg6STNnuA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-dXfVWzIIg6STNnuA .decision>*{fill:#fff2cc!important;stroke:#d97706!important;}#mermaid-svg-dXfVWzIIg6STNnuA .decision span{fill:#fff2cc!important;stroke:#d97706!important;}#mermaid-svg-dXfVWzIIg6STNnuA .action>*{fill:#e6f7e6!important;stroke:#2e8b57!important;}#mermaid-svg-dXfVWzIIg6STNnuA .action span{fill:#e6f7e6!important;stroke:#2e8b57!important;} 是

开始 selectnewtape(state)
nOutputTapes < maxTapes ?
是:创建新磁带
LogicalTapeCreate()

新建 LogicalTape
存入 outputTapes 数组
nOutputTapes++

nOutputRuns++
否:轮询已有磁带
destTape = outputTapesnOutputRuns % nOutputTapes
nOutputRuns++
函数结束


worker_nomergeruns 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * worker_nomergeruns - dump memtuples in worker, without merging
 * Worker 进程无需归并时,直接将内存中的 tuple 作为最终结果
 */
static void
worker_nomergeruns(Tuplesortstate *state)
{
	/* 断言:当前必须是 Worker 进程 */
	Assert(WORKER(state));

	/* 断言:结果磁带尚未设置 */
	Assert(state->result_tape == NULL);

	/* 断言:只产生了一个输出 run */
	Assert(state->nOutputRuns == 1);

	/* 将当前输出磁带设置为最终结果磁带 */
	state->result_tape = state->destTape;

	/* 冻结结果磁带,供后续读取使用 */
	worker_freeze_result_tape(state);
}
2. 详细功能解读

  该函数是并行排序 Worker 进程 的特殊处理路径。当 Worker 只生成一个 run、无需进行多路归并时,直接把当前输出磁带标记为最终结果磁带,并冻结以供 Leader 读取。

  这是对单 run 场景的优化,避免不必要的归并操作。

3. 流程图

#mermaid-svg-9YigUrD2hDyZHgos{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9YigUrD2hDyZHgos .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9YigUrD2hDyZHgos .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9YigUrD2hDyZHgos .error-icon{fill:#552222;}#mermaid-svg-9YigUrD2hDyZHgos .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9YigUrD2hDyZHgos .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9YigUrD2hDyZHgos .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9YigUrD2hDyZHgos .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9YigUrD2hDyZHgos .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9YigUrD2hDyZHgos .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9YigUrD2hDyZHgos .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9YigUrD2hDyZHgos .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9YigUrD2hDyZHgos .marker.cross{stroke:#333333;}#mermaid-svg-9YigUrD2hDyZHgos svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9YigUrD2hDyZHgos p{margin:0;}#mermaid-svg-9YigUrD2hDyZHgos .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9YigUrD2hDyZHgos .cluster-label text{fill:#333;}#mermaid-svg-9YigUrD2hDyZHgos .cluster-label span{color:#333;}#mermaid-svg-9YigUrD2hDyZHgos .cluster-label span p{background-color:transparent;}#mermaid-svg-9YigUrD2hDyZHgos .label text,#mermaid-svg-9YigUrD2hDyZHgos span{fill:#333;color:#333;}#mermaid-svg-9YigUrD2hDyZHgos .node rect,#mermaid-svg-9YigUrD2hDyZHgos .node circle,#mermaid-svg-9YigUrD2hDyZHgos .node ellipse,#mermaid-svg-9YigUrD2hDyZHgos .node polygon,#mermaid-svg-9YigUrD2hDyZHgos .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9YigUrD2hDyZHgos .rough-node .label text,#mermaid-svg-9YigUrD2hDyZHgos .node .label text,#mermaid-svg-9YigUrD2hDyZHgos .image-shape .label,#mermaid-svg-9YigUrD2hDyZHgos .icon-shape .label{text-anchor:middle;}#mermaid-svg-9YigUrD2hDyZHgos .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9YigUrD2hDyZHgos .rough-node .label,#mermaid-svg-9YigUrD2hDyZHgos .node .label,#mermaid-svg-9YigUrD2hDyZHgos .image-shape .label,#mermaid-svg-9YigUrD2hDyZHgos .icon-shape .label{text-align:center;}#mermaid-svg-9YigUrD2hDyZHgos .node.clickable{cursor:pointer;}#mermaid-svg-9YigUrD2hDyZHgos .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9YigUrD2hDyZHgos .arrowheadPath{fill:#333333;}#mermaid-svg-9YigUrD2hDyZHgos .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9YigUrD2hDyZHgos .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9YigUrD2hDyZHgos .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9YigUrD2hDyZHgos .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9YigUrD2hDyZHgos .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9YigUrD2hDyZHgos .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9YigUrD2hDyZHgos .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9YigUrD2hDyZHgos .cluster text{fill:#333;}#mermaid-svg-9YigUrD2hDyZHgos .cluster span{color:#333;}#mermaid-svg-9YigUrD2hDyZHgos div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9YigUrD2hDyZHgos .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9YigUrD2hDyZHgos rect.text{fill:none;stroke-width:0;}#mermaid-svg-9YigUrD2hDyZHgos .icon-shape,#mermaid-svg-9YigUrD2hDyZHgos .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9YigUrD2hDyZHgos .icon-shape p,#mermaid-svg-9YigUrD2hDyZHgos .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9YigUrD2hDyZHgos .icon-shape .label rect,#mermaid-svg-9YigUrD2hDyZHgos .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9YigUrD2hDyZHgos .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9YigUrD2hDyZHgos .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9YigUrD2hDyZHgos :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 开始 worker_nomergeruns()
断言:是 Worker 进程
断言:result_tape 为空
断言:只有一个输出 run
result_tape = destTape
worker_freeze_result_tape()
结束


leader_takeover_tapes 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * leader_takeover_tapes - 主线程(Leader)接管所有工作线程(Worker)的排序磁带
 *
 * 功能:并行外部排序中,所有 Worker 已经完成本地排序、生成了有序 Run
 *       由 Leader 统一接管所有 Worker 的磁盘磁带(临时文件)
 *       并把这些磁带作为【归并的输入源】,准备执行最终的 k-way 归并
 *
 * 调用时机:并行排序所有 Worker 完成后,Leader 开始归并前
 */
static void
leader_takeover_tapes(Tuplesortstate *state)
{
	Sharedsort *shared = state->shared;          // 指向并行排序共享内存结构
	int			nParticipants = state->nParticipants; // 总参与线程数(Leader + Workers)
	int			workersFinished;                  // 已完成排序的 Worker 数量
	int			j;                                // 循环变量

	Assert(LEADER(state));                       // 断言:只有 Leader 能调用此函数
	Assert(nParticipants >= 1);                   // 断言:至少有1个线程参与

	/* 原子读取:已完成工作的 Worker 数量(自旋锁保证线程安全) */
	SpinLockAcquire(&shared->mutex);
	workersFinished = shared->workersFinished;
	SpinLockRelease(&shared->mutex);

	/* 安全检查:必须等所有 Worker 都写完 Run 才能接管,否则报错 */
	if (nParticipants != workersFinished)
		elog(ERROR, "cannot take over tapes before all workers finish");

	/*
	 * 初始化磁带状态
	 * 参与线程数 = 输入 Run 数量,每个 Worker 输出 1 个有序 Run
	 */
	inittapestate(state, nParticipants);

	/* 创建 Leader 专用的逻辑磁带管理器(读取共享临时文件) */
	state->tapeset = LogicalTapeSetCreate(false, &shared->fileset, -1);

	/*
	 * currentRun 记录总 Run 数量 = 参与者数量
	 * 每个 Worker 输出一个有序 Run,共 nParticipants 个 Run
	 */
	state->currentRun = nParticipants;

	/* 清空输入磁带变量(准备接收上一轮输出作为输入) */
	state->inputTapes = NULL;
	state->nInputTapes = 0;
	state->nInputRuns = 0;

	/*
	 * 分配输出磁带数组
	 * 这里的 outputTapes 实际存放【所有 Worker 的有序 Run 磁带】
	 * 后续会直接变成 inputTapes 用于归并
	 */
	state->outputTapes = palloc0(nParticipants * sizeof(LogicalTape *));
	state->nOutputTapes = nParticipants;  // 磁带数 = 参与者数
	state->nOutputRuns = nParticipants;   // Run 数 = 参与者数

	/*
	 * 循环:从共享内存导入所有 Worker 产生的磁带
	 * 每个 Worker 对应一个磁带,存储它的有序 Run
	 */
	for (j = 0; j < nParticipants; j++)
	{
		// 导入第 j 个 Worker 的磁带到 Leader 的 outputTapes 数组
		state->outputTapes[j] = LogicalTapeImport(state->tapeset, j, &shared->tapes[j]);
	}

	/*
	 * 设置状态为 TSS_BUILDRUNS
	 * 表示:所有初始有序 Run 已准备完毕,可以进入归并流程
	 */
	state->status = TSS_BUILDRUNS;
}
2. 详细功能解读

  这个函数是并行外部排序中 Leader 进程的核心"接管"函数 ,其设计目的是让 Leader 在不实际排序任何数据的情况下,伪装成自己也完成了初始 runs 的生成 ,从而可以无缝地进入后续的 mergeruns() 阶段。

为什么需要这个函数?(背景理解)

  在 PostgreSQL 并行排序(Parallel Sort)中:

  • Worker 进程 :真正执行排序工作,每个 Worker 独立生成一个有序 run (即使数据很少,也至少产生一个 run)。
  • Leader 进程 :负责协调,不实际排序数据,只负责最终的多路归并merge)。

  当所有 Worker 都完成排序后,Leader 需要"接管"这些 Worker 生成的磁带(tapes),让自己的 Tuplesortstate 状态看起来和单进程外部排序完全一致 (即已经生成了多个初始 runs)。这样 Leader 就可以直接调用 mergeruns() 执行 Balanced k-way Merge,而不需要为并行情况写一套完全不同的逻辑。

  这体现了代码设计的优雅性:并行与串行路径高度复用,减少维护成本。

函数核心步骤含义

  1. 检查所有 Worker 是否完成

      通过共享内存 + 自旋锁 ,确保所有 Worker 都已执行完 tuplesort_performsort(),防止状态不一致导致崩溃。

  2. 初始化磁带状态

      调用 inittapestate()Leader 准备内存扣除和临时表空间。

  3. 创建 Leader 自己的 TapeSet

      LogicalTapeSetCreate() 创建新的磁带集合,并关联共享文件集(shared->fileset),这样 Leader 可以访问 Worker 写入的临时文件。

  4. 初始化新版 Balanced k-way Merge 的数据结构

    • inputTapesnInputTapesnInputRuns:用于后续多趟归并的输入管理。
    • outputTapesnOutputTapesnOutputRuns:直接把 Worker 产生的 run 视为"上一轮的输出",数量等于 Worker 数。
  5. 导入 Worker 磁带(最关键操作)

      LogicalTapeImport() 把每个 Worker 写入的磁带"导入"到 Leadertapeset 中,使 Leader 可以像读取自己生成的 run 一样读取它们。

  6. 设置状态为 TSS_BUILDRUNS

      让 Leader 进入"初始 runs 已生成完毕"的状态,后续 mergeruns() 会自然地开始多路归并。

与新版 Balanced k-way Merge 的配合

  在新版算法中,由于采用了清晰的 inputTapes / outputTapes 切换机制,Leader 接管后状态与普通外部排序几乎完全一致,因此可以直接复用 mergeruns()selectnewtape()mergeonerun() 等函数,无需为并行情况写额外代码。

3. 流程图

#mermaid-svg-EkJcuPVHDQ9Vyt5d{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .error-icon{fill:#552222;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .marker.cross{stroke:#333333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EkJcuPVHDQ9Vyt5d p{margin:0;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .cluster-label text{fill:#333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .cluster-label span{color:#333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .cluster-label span p{background-color:transparent;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .label text,#mermaid-svg-EkJcuPVHDQ9Vyt5d span{fill:#333;color:#333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .node rect,#mermaid-svg-EkJcuPVHDQ9Vyt5d .node circle,#mermaid-svg-EkJcuPVHDQ9Vyt5d .node ellipse,#mermaid-svg-EkJcuPVHDQ9Vyt5d .node polygon,#mermaid-svg-EkJcuPVHDQ9Vyt5d .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .rough-node .label text,#mermaid-svg-EkJcuPVHDQ9Vyt5d .node .label text,#mermaid-svg-EkJcuPVHDQ9Vyt5d .image-shape .label,#mermaid-svg-EkJcuPVHDQ9Vyt5d .icon-shape .label{text-anchor:middle;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .rough-node .label,#mermaid-svg-EkJcuPVHDQ9Vyt5d .node .label,#mermaid-svg-EkJcuPVHDQ9Vyt5d .image-shape .label,#mermaid-svg-EkJcuPVHDQ9Vyt5d .icon-shape .label{text-align:center;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .node.clickable{cursor:pointer;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .arrowheadPath{fill:#333333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EkJcuPVHDQ9Vyt5d .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EkJcuPVHDQ9Vyt5d .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EkJcuPVHDQ9Vyt5d .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .cluster text{fill:#333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .cluster span{color:#333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EkJcuPVHDQ9Vyt5d rect.text{fill:none;stroke-width:0;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .icon-shape,#mermaid-svg-EkJcuPVHDQ9Vyt5d .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .icon-shape p,#mermaid-svg-EkJcuPVHDQ9Vyt5d .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .icon-shape .label rect,#mermaid-svg-EkJcuPVHDQ9Vyt5d .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EkJcuPVHDQ9Vyt5d .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EkJcuPVHDQ9Vyt5d .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EkJcuPVHDQ9Vyt5d :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否

开始 leader_takeover_tapes()

作用:Leader 接管所有 Worker 磁带
获取共享内存结构 & 参与线程数
自旋锁读取已完成 Worker 数量
所有 Worker 都完成了?
报错:未完成无法接管
初始化磁带状态 inittapestate()
创建逻辑磁带管理器 LogicalTapeSetCreate
设置 Run 数量 = 线程数量
初始化输入/输出磁带数组
循环导入所有 Worker 的磁带
将 Worker 磁带存入 outputTapes
设置状态为 TSS_BUILDRUNS
结束:准备进入归并阶段


LogicalTapeSetCreate 函数

1. 带中文注释的完整源码
c 复制代码
/*
 * LogicalTapeSetCreate - 创建一个逻辑磁带集合(LogicalTapeSet)
 *
 * 功能:为外部排序/归并创建管理虚拟磁带的顶层结构
 *       底层真正存储数据的是 BufFile(临时磁盘文件)
 *
 * 参数:
 *  preallocate:是否启用预分配(优化写性能)
 *  fileset:共享文件集(并行排序时 Leader/Worker 共享磁盘文件)
 *  worker:Worker 编号,-1 表示 Leader
 */
LogicalTapeSet *
LogicalTapeSetCreate(bool preallocate, SharedFileSet *fileset, int worker)
{
	LogicalTapeSet *lts;                                    // 磁带集合管理结构体

	/*
	 * 1. 分配 LogicalTapeSet 顶层结构体
	 */
	lts = (LogicalTapeSet *) palloc(sizeof(LogicalTapeSet));

	/*
	 * 2. 初始化统计与状态字段
	 */
	lts->nBlocksAllocated = 0L;                             // 已分配物理块数
	lts->nBlocksWritten = 0L;                               // 已写入数据块数
	lts->nHoleBlocks = 0L;                                  // 空洞块(未使用)数量
	lts->forgetFreeSpace = false;                           // 不忽略空闲空间(正常回收)

	lts->freeBlocksLen = 32;	                            // 空闲块数组初始容量
	lts->freeBlocks = (int64 *) palloc(lts->freeBlocksLen * sizeof(int64)); // 空闲块数组
	lts->nFreeBlocks = 0;                                   // 初始无空闲块
	lts->enable_prealloc = preallocate;                     // 是否启用块预分配

	lts->fileset = fileset;                                 // 保存共享文件集(并行排序用)
	lts->worker = worker;                                   // 保存当前 worker 编号(-1=Leader)

	/*
	 * 3. 创建底层磁盘文件(BufFile)
	 * 三种分支:Leader 并行 / Worker 并行 / 普通单进程
	 */
	if (fileset && worker == -1)
	{
		/*
		 * 并行排序 - Leader 进程
		 * 不创建文件,后续导入 Worker 磁带时直接复用已有文件
		 */
		lts->pfile = NULL;
	}
	else if (fileset)
	{
		/*
		 * 并行排序 - Worker 进程
		 * 创建属于自己的共享临时文件,用于存储排序后的 Run
		 */
		char		filename[MAXPGPATH];
		pg_itoa(worker, filename);                          // 用 worker 编号做文件名
		lts->pfile = BufFileCreateFileSet(&fileset->fs, filename);
	}
	else
	{
		/*
		 * 普通单进程排序
		 * 创建私有临时文件
		 */
		lts->pfile = BufFileCreateTemp(false);
	}

	/* 返回创建完成的磁带集合 */
	return lts;
}
2. 详细功能解读

函数作用

  创建并初始化一个 LogicalTapeSet (逻辑磁带集合),这是 PostgreSQL 外部排序中管理所有"逻辑磁带"的顶层容器。每个 LogicalTapeSet 底层对应一个或多个临时文件,用于实现高效的外部归并排序。

  这是整个外部排序磁带系统的"入口工厂函数"

核心设计目的

  • 统一管理多条逻辑磁带 :一条 LogicalTapeSet 可以包含多条 LogicalTape(逻辑磁带),底层共享同一个或关联的物理临时文件。
  • 支持空间复用 :通过 freeBlocks 数组管理已释放的块,实现磁盘空间的循环利用,避免临时文件无限膨胀。
  • 支持并行排序 :通过 SharedFileSet 机制,让 Leader 和多个 Worker 共同使用同一组临时文件。

不同场景的行为差异(重点)

  1. 单进程排序(Serial Sort

    • fileset = NULLworker = -1
    • 直接创建一个普通的临时 BufFileBufFileCreateTemp
  2. 并行排序 Worker 进程

    • fileset 不为空,worker 为具体编号(0、1、2...
    • 为每个 Worker 创建独立的 BufFile,文件名基于 Worker 编号
  3. 并行排序 Leader 进程

    • fileset 不为空,worker = -1
    • 不立即创建 pfile (设为 NULL
    • 后续通过 LogicalTapeImport() 接管 Worker 创建的 BufFile(见 leader_takeover_tapes

重要成员字段含义

  • enable_prealloc:是否预分配块(减少多磁带同时写入时的碎片)
  • freeBlocks / nFreeBlocks:空闲块管理链表,用于空间回收
  • pfile:底层真正的 BufFile 对象(I/O 操作最终落到这里)

在新版 Balanced k-way Merge 中的地位

  此函数被 inittapes() 调用,是从内存排序切换到磁带排序时的关键初始化步骤。Leaderleader_takeover_tapes() 中也会调用它来接管 Worker 的磁带。

3. 流程图

#mermaid-svg-KcV66gAqJEUu7YYo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KcV66gAqJEUu7YYo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KcV66gAqJEUu7YYo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KcV66gAqJEUu7YYo .error-icon{fill:#552222;}#mermaid-svg-KcV66gAqJEUu7YYo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KcV66gAqJEUu7YYo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KcV66gAqJEUu7YYo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KcV66gAqJEUu7YYo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KcV66gAqJEUu7YYo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KcV66gAqJEUu7YYo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KcV66gAqJEUu7YYo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KcV66gAqJEUu7YYo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KcV66gAqJEUu7YYo .marker.cross{stroke:#333333;}#mermaid-svg-KcV66gAqJEUu7YYo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KcV66gAqJEUu7YYo p{margin:0;}#mermaid-svg-KcV66gAqJEUu7YYo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KcV66gAqJEUu7YYo .cluster-label text{fill:#333;}#mermaid-svg-KcV66gAqJEUu7YYo .cluster-label span{color:#333;}#mermaid-svg-KcV66gAqJEUu7YYo .cluster-label span p{background-color:transparent;}#mermaid-svg-KcV66gAqJEUu7YYo .label text,#mermaid-svg-KcV66gAqJEUu7YYo span{fill:#333;color:#333;}#mermaid-svg-KcV66gAqJEUu7YYo .node rect,#mermaid-svg-KcV66gAqJEUu7YYo .node circle,#mermaid-svg-KcV66gAqJEUu7YYo .node ellipse,#mermaid-svg-KcV66gAqJEUu7YYo .node polygon,#mermaid-svg-KcV66gAqJEUu7YYo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KcV66gAqJEUu7YYo .rough-node .label text,#mermaid-svg-KcV66gAqJEUu7YYo .node .label text,#mermaid-svg-KcV66gAqJEUu7YYo .image-shape .label,#mermaid-svg-KcV66gAqJEUu7YYo .icon-shape .label{text-anchor:middle;}#mermaid-svg-KcV66gAqJEUu7YYo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KcV66gAqJEUu7YYo .rough-node .label,#mermaid-svg-KcV66gAqJEUu7YYo .node .label,#mermaid-svg-KcV66gAqJEUu7YYo .image-shape .label,#mermaid-svg-KcV66gAqJEUu7YYo .icon-shape .label{text-align:center;}#mermaid-svg-KcV66gAqJEUu7YYo .node.clickable{cursor:pointer;}#mermaid-svg-KcV66gAqJEUu7YYo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KcV66gAqJEUu7YYo .arrowheadPath{fill:#333333;}#mermaid-svg-KcV66gAqJEUu7YYo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KcV66gAqJEUu7YYo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KcV66gAqJEUu7YYo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KcV66gAqJEUu7YYo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KcV66gAqJEUu7YYo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KcV66gAqJEUu7YYo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KcV66gAqJEUu7YYo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KcV66gAqJEUu7YYo .cluster text{fill:#333;}#mermaid-svg-KcV66gAqJEUu7YYo .cluster span{color:#333;}#mermaid-svg-KcV66gAqJEUu7YYo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KcV66gAqJEUu7YYo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KcV66gAqJEUu7YYo rect.text{fill:none;stroke-width:0;}#mermaid-svg-KcV66gAqJEUu7YYo .icon-shape,#mermaid-svg-KcV66gAqJEUu7YYo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KcV66gAqJEUu7YYo .icon-shape p,#mermaid-svg-KcV66gAqJEUu7YYo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KcV66gAqJEUu7YYo .icon-shape .label rect,#mermaid-svg-KcV66gAqJEUu7YYo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KcV66gAqJEUu7YYo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KcV66gAqJEUu7YYo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KcV66gAqJEUu7YYo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



开始 LogicalTapeSetCreate(preallocate, fileset, worker)
palloc LogicalTapeSet 顶层结构
初始化统计字段

nBlocksAllocated = 0 等
分配 freeBlocks 数组

初始容量 32
设置 enable_prealloc、fileset、worker
fileset && worker == -1 ?
Leader 模式:

pfile = NULL(后续导入)
fileset ?
Worker 模式:

创建基于 SharedFileSet 的 BufFile
单进程模式:

BufFileCreateTemp()
返回 LogicalTapeSet
结束

整体执行逻辑总结

主要执行逻辑流程(对应核心函数)

  新版 Balanced k-way Merge 的整体执行路径如下:

  1. 初始化阶段

    • tuplesort_begin_batch():开始一批排序,初始化基本状态。
  2. 数据插入阶段

    • tuplesort_puttuple_common():插入 tuple(核心入口)。
    • consider_abort_common():判断是否中止缩写键优化。
  3. 内存排序判断与切换

    • 若内存足够 :继续在内存中处理(可能进入 make_bounded_heap() / sort_bounded_heap())。
    • 若内存不足 :调用 inittapes()inittapestate() 切换到外部磁带排序。
  4. 初始有序 Runs 生成阶段

    • inittapes():初始化磁带系统(设置 maxTapes、创建 tapeset)。
    • selectnewtape():选择或创建输出磁带。
    • dumptuples():对内存 tuple 进行 tuplesort_sort_memtuples()quicksort),然后 WRITETUP 写入磁带,调用 markrunend()
    • init_slab_allocator():在归并前初始化 Slab 内存分配器。
  5. 归并阶段(核心:Balanced k-way Merge

    • mergeruns():多趟归并主循环(最核心函数)。
      • 判断是否需要新 Pass → 输入/输出磁带切换。
      • selectnewtape():选择输出磁带。
      • beginmerge():从每个输入磁带读取第一个 tuple初始化小根堆
      • mergeonerun():执行一次 k-way 合并。
        • mergereadnext():从磁带读取下一个 tuple
        • tuplesort_heap_insert() / tuplesort_heap_replace_top() / tuplesort_heap_delete_top():堆维护操作。
      • markrunend():写入 run 结束标记。
    • 最终生成 result_tape
  6. 并行排序特殊路径

    • Workerworker_nomergeruns()(无需归并时直接冻结结果)。
    • Leaderleader_takeover_tapes()(接管所有 Worker 磁带,初始化状态,准备归并)。
  7. 清理与结束

    • tuplesort_free():释放资源。
    • worker_freeze_result_tape() / reversedirection() 等辅助函数。

整体流程图(包含所有主要函数)

#mermaid-svg-OGmjoElQZMsEB2IC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OGmjoElQZMsEB2IC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OGmjoElQZMsEB2IC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OGmjoElQZMsEB2IC .error-icon{fill:#552222;}#mermaid-svg-OGmjoElQZMsEB2IC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OGmjoElQZMsEB2IC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OGmjoElQZMsEB2IC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OGmjoElQZMsEB2IC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OGmjoElQZMsEB2IC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OGmjoElQZMsEB2IC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OGmjoElQZMsEB2IC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OGmjoElQZMsEB2IC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OGmjoElQZMsEB2IC .marker.cross{stroke:#333333;}#mermaid-svg-OGmjoElQZMsEB2IC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OGmjoElQZMsEB2IC p{margin:0;}#mermaid-svg-OGmjoElQZMsEB2IC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OGmjoElQZMsEB2IC .cluster-label text{fill:#333;}#mermaid-svg-OGmjoElQZMsEB2IC .cluster-label span{color:#333;}#mermaid-svg-OGmjoElQZMsEB2IC .cluster-label span p{background-color:transparent;}#mermaid-svg-OGmjoElQZMsEB2IC .label text,#mermaid-svg-OGmjoElQZMsEB2IC span{fill:#333;color:#333;}#mermaid-svg-OGmjoElQZMsEB2IC .node rect,#mermaid-svg-OGmjoElQZMsEB2IC .node circle,#mermaid-svg-OGmjoElQZMsEB2IC .node ellipse,#mermaid-svg-OGmjoElQZMsEB2IC .node polygon,#mermaid-svg-OGmjoElQZMsEB2IC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OGmjoElQZMsEB2IC .rough-node .label text,#mermaid-svg-OGmjoElQZMsEB2IC .node .label text,#mermaid-svg-OGmjoElQZMsEB2IC .image-shape .label,#mermaid-svg-OGmjoElQZMsEB2IC .icon-shape .label{text-anchor:middle;}#mermaid-svg-OGmjoElQZMsEB2IC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OGmjoElQZMsEB2IC .rough-node .label,#mermaid-svg-OGmjoElQZMsEB2IC .node .label,#mermaid-svg-OGmjoElQZMsEB2IC .image-shape .label,#mermaid-svg-OGmjoElQZMsEB2IC .icon-shape .label{text-align:center;}#mermaid-svg-OGmjoElQZMsEB2IC .node.clickable{cursor:pointer;}#mermaid-svg-OGmjoElQZMsEB2IC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OGmjoElQZMsEB2IC .arrowheadPath{fill:#333333;}#mermaid-svg-OGmjoElQZMsEB2IC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OGmjoElQZMsEB2IC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OGmjoElQZMsEB2IC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OGmjoElQZMsEB2IC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OGmjoElQZMsEB2IC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OGmjoElQZMsEB2IC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OGmjoElQZMsEB2IC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OGmjoElQZMsEB2IC .cluster text{fill:#333;}#mermaid-svg-OGmjoElQZMsEB2IC .cluster span{color:#333;}#mermaid-svg-OGmjoElQZMsEB2IC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OGmjoElQZMsEB2IC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OGmjoElQZMsEB2IC rect.text{fill:none;stroke-width:0;}#mermaid-svg-OGmjoElQZMsEB2IC .icon-shape,#mermaid-svg-OGmjoElQZMsEB2IC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OGmjoElQZMsEB2IC .icon-shape p,#mermaid-svg-OGmjoElQZMsEB2IC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OGmjoElQZMsEB2IC .icon-shape .label rect,#mermaid-svg-OGmjoElQZMsEB2IC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OGmjoElQZMsEB2IC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OGmjoElQZMsEB2IC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OGmjoElQZMsEB2IC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-OGmjoElQZMsEB2IC .core>*{fill:#e6f7e6!important;stroke:#2e8b57!important;}#mermaid-svg-OGmjoElQZMsEB2IC .core span{fill:#e6f7e6!important;stroke:#2e8b57!important;}#mermaid-svg-OGmjoElQZMsEB2IC .merge>*{fill:#fff2cc!important;stroke:#d97706!important;}#mermaid-svg-OGmjoElQZMsEB2IC .merge span{fill:#fff2cc!important;stroke:#d97706!important;} 平衡K路归并阶段
并行排序
初始有序Run生成
切换外部排序
输入与内存排序阶段
足够
不足




开始排序

tuplesort_begin_batch()

【初始化排序环境】
tuplesort_puttuple_common()

  • consider_abort_common()

【写入元组 + 检查内存是否溢出】
内存是否足够?
make_bounded_heap() / sort_bounded_heap()

【内存排序:堆排序/快速排序】
inittapes() → inittapestate()

【初始化逻辑磁带】
init_slab_allocator()

【初始化Slab内存池,复用元组】
selectnewtape()

【选择/切换输出磁带】
dumptuples()

→ tuplesort_sort_memtuples()

  • markrunend()

【内存排序 → 写入磁盘生成有序Run】
Worker: worker_nomergeruns()

  • worker_freeze_result_tape()

【Worker只生成Run,不做归并】
Leader: leader_takeover_tapes()

【Leader接管所有Worker的磁带】
mergeruns()

【多趟归并主函数】
需要新Pass?

(nInputRuns == 0)
inputTapes ↔ outputTapes 切换

【上轮输出 = 本轮输入】
beginmerge()

【初始化最小堆,准备归并】
mergeonerun()

【执行一次K路合并,生成一个新Run】
堆操作:

tuplesort_heap_insert / replace_top / delete_top

【维护最小堆,选出最小元组】
mergereadnext() + getlen()

【从磁带读取下一个元组】
最终只剩1个Run?
排序结束

result_tape + tuplesort_free()

【返回结果磁带,释放资源】


📗 推荐进一步阅读

1. 《PostgreSQL 中的查询:排序与合并》(外部排序最经典中文综述)

章节要点

  • 内存排序 → 外部排序触发条件
  • 初始 run 生成(快速排序 + 落盘)
  • 多路归并(k-way merge)原理
  • 磁带(逻辑文件)管理

2. PostgreSQL Sort 模块代码分析

章节要点

  • Tuplesortstate 状态机(TSS_BUILDRUNS / TSS_MERGE
  • 内存排序:make_bounded_heap / sort_bounded_heap
  • 外部排序:dumptuples(写 run)、mergeruns(多趟归并)
  • 并行排序:Leader + Worker 协作

3. 并行排序原理(PostgreSQL 11+)

章节要点

  • Worker:生成独立有序 run,不做归并
  • Leader:接管所有 Worker 磁带,执行最终 k-way 归并
  • 并行排序性能数据与调优参数
相关推荐
xieliyu.2 小时前
MySQL 全套入门笔记:基础、库操作、数据类型
数据库·笔记·mysql
XGeFei2 小时前
【Fastapi学习笔记(7)】—— Fastapi 中间件、前端跨域请求
笔记·学习·fastapi
lvbinemail2 小时前
【无标题】
数据库·postgresql·zabbix·监控
技术小甜甜2 小时前
[办公效率] Excel 表格越做越乱,先整理字段、格式还是公式?
数据库·excel·办公效率·数据整理
踏着七彩祥云的小丑2 小时前
嵌入式测试学习第 31 天:兼容性测试:版本兼容、外设兼容、硬件版本兼容
单片机·嵌入式硬件·学习
hans汉斯2 小时前
【人工智能与机器人研究】基于分层控制的多智能体编队协同控制
网络·人工智能·学习·yolo·机器人
Data-Miner2 小时前
休闲食品行业数据分析平台建设方案,揭秘增长新引擎!
大数据·数据库·数据分析
KKKlucifer2 小时前
数据分类分级排名解析:三大核心能力决定选型方向
大数据·数据库·分类
Kobebryant-Manba2 小时前
学习模型构造
python·深度学习·学习