PostgreSQL-03-Executor-执行器
模块概览
模块职责
Executor(执行器)是 PostgreSQL 查询处理管道的最后阶段,负责执行优化器生成的执行计划(Plan),并返回查询结果。执行器采用火山模型(Volcano Model),通过迭代器模式逐元组(Tuple-at-a-time)处理数据。
核心能力
- 计划执行:解释执行计划树,递归调用各类节点的执行函数
- 元组处理:按需拉取元组(Pull-based),支持流水线执行
- 表达式计算:执行投影(Projection)、过滤(Filter)、连接条件等表达式
- 物化管理:临时物化中间结果(如排序、哈希表)
- JIT 编译:可选的 LLVM JIT 编译加速表达式计算
- 并行执行:协调多个工作进程并行执行,汇聚结果
- 资源管理:管理内存使用(work_mem)、临时文件、锁
输入/输出
-
输入:
- 执行计划(PlannedStmt *):优化器生成的计划树
- 参数(ParamListInfo):绑定的查询参数
- 快照(Snapshot):事务可见性快照
- 目标描述符(DestReceiver):结果输出目标(客户端/物化视图/其他)
-
输出:
- 元组流(TupleTableSlot):逐行返回查询结果
- 执行统计(Instrumentation):行数、时间、Buffer 访问等
上下游依赖
- 上游:Planner(优化器)生成执行计划
- 下游:
- Access Methods(访问方法):调用 Heap AM、Index AM 读取数据
- Buffer Manager(缓冲管理器):请求数据页
- Transaction Manager(事务管理器):检查元组可见性
- 并行协作:
- Parallel Workers(并行工作进程):执行部分计划
- Shared Memory(共享内存):传递参数、汇聚结果
架构图
flowchart TB
subgraph Executor["Executor 执行器"]
direction TB
Entry[ExecutorStart<br/>初始化执行器] --> InitPlan[InitPlan<br/>初始化计划节点]
InitPlan --> OpenRel[打开表/索引]
InitPlan --> AllocSlots[分配元组槽]
Entry --> Run[ExecutorRun<br/>执行计划]
Run --> ExecProc[ExecProcNode<br/>递归执行节点]
ExecProc --> ScanNode[扫描节点]
ScanNode --> SeqScan[SeqScan<br/>顺序扫描]
ScanNode --> IdxScan[IndexScan<br/>索引扫描]
ScanNode --> IdxOnlyScan[IndexOnlyScan<br/>仅索引扫描]
ExecProc --> JoinNode[连接节点]
JoinNode --> NestLoop[NestLoop<br/>嵌套循环连接]
JoinNode --> HashJoin[HashJoin<br/>哈希连接]
JoinNode --> MergeJoin[MergeJoin<br/>归并连接]
ExecProc --> AggNode[聚合节点]
AggNode --> HashAgg[HashAgg<br/>哈希聚合]
AggNode --> GroupAgg[GroupAgg<br/>分组聚合]
ExecProc --> SortNode[Sort<br/>排序节点]
ExecProc --> LimitNode[Limit<br/>限制节点]
ExecProc --> Projection[ExecProject<br/>投影表达式]
ExecProc --> Filter[ExecQual<br/>过滤条件]
Run --> Gather[Gather<br/>汇聚并行结果]
Entry --> End[ExecutorEnd<br/>清理资源]
end
subgraph Input["输入"]
Plan[PlannedStmt<br/>执行计划]
Params[ParamListInfo<br/>参数]
Snapshot[Snapshot<br/>快照]
end
subgraph Dependencies["依赖模块"]
HeapAM[Heap AM<br/>堆表访问]
IndexAM[Index AM<br/>索引访问]
BufferMgr[Buffer Manager<br/>缓冲管理]
TxnMgr[Transaction Mgr<br/>事务管理]
ExprEval[表达式计算<br/>JIT 可选]
end
Plan --> Entry
Params --> Entry
Snapshot --> Entry
SeqScan --> HeapAM
IdxScan --> IndexAM
IdxOnlyScan --> IndexAM
HeapAM --> BufferMgr
IndexAM --> BufferMgr
ExecProc --> TxnMgr
Projection --> ExprEval
Filter --> ExprEval
subgraph Output["输出"]
Tuples[元组流<br/>TupleTableSlot]
Stats[执行统计<br/>Instrumentation]
end
ExecProc --> Tuples
End --> Stats
架构说明
火山模型(Volcano Model)
执行器采用迭代器模式,每个计划节点实现三个标准接口:
- Init:初始化节点状态(分配内存、打开表)
- ExecProcNode:返回下一个元组(按需拉取)
- End:清理节点资源
调用流程:
ExecutorStart() // 初始化所有节点
while (ExecutorRun()) // 循环获取元组
tuple = ExecProcNode(plan) // 递归调用子节点
if (tuple == NULL) break
send_to_client(tuple)
ExecutorEnd() // 清理所有节点
优势:
- 流水线执行:上层节点从下层节点按需拉取元组,无需等待全部结果
- 内存高效:每次仅处理一行,避免全表物化(除非必须,如排序)
- 易于扩展:新增节点类型仅需实现标准接口
节点类型分类
| 类别 | 节点类型 | 说明 |
|---|---|---|
| 扫描节点 | SeqScan | 顺序扫描表 |
| IndexScan | 索引扫描后回表 | |
| IndexOnlyScan | 仅索引扫描(无需回表) | |
| BitmapHeapScan | 位图堆扫描(多索引合并) | |
| TidScan | TID(行标识符)扫描 | |
| 连接节点 | NestLoop | 嵌套循环连接 |
| HashJoin | 哈希连接 | |
| MergeJoin | 归并连接 | |
| 聚合节点 | Agg | 聚合(带 GROUP BY) |
| WindowAgg | 窗口聚合 | |
| 排序节点 | Sort | 快速排序 |
| IncrementalSort | 增量排序 | |
| 其他节点 | Limit | 限制行数 |
| Result | 常量结果/表达式计算 | |
| Append | 合并多个子计划(UNION/分区表) | |
| Gather | 汇聚并行工作进程结果 |
核心数据结构
1. QueryDesc(查询描述符)
/* src/include/executor/execdesc.h */
typedef struct QueryDesc
{
CmdType operation; /* 操作类型:SELECT/INSERT/UPDATE/DELETE */
PlannedStmt *plannedstmt; /* 执行计划 */
const char *sourceText; /* 原始 SQL 文本 */
Snapshot snapshot; /* 查询快照(可见性判断) */
Snapshot crosscheck_snapshot; /* 交叉检查快照(FOR UPDATE) */
DestReceiver *dest; /* 结果输出目标 */
ParamListInfo params; /* 参数值 */
QueryEnvironment *queryEnv; /* 查询环境(CTE 等) */
int instrument_options; /* EXPLAIN ANALYZE 选项 */
/* 执行状态 */
EState *estate; /* 执行器状态 */
PlanState *planstate; /* 计划树根节点状态 */
/* 执行统计 */
TupleTableSlot *tupDesc; /* 元组描述符 */
bool already_executed; /* 是否已执行(防止重复执行) */
/* 内存上下文 */
MemoryContext queryContext; /* 查询生命周期内存 */
} QueryDesc;
关键字段说明
| 字段 | 说明 |
|---|---|
plannedstmt |
优化器生成的执行计划,包含计划树根节点 |
snapshot |
事务快照,用于 MVCC 可见性判断 |
dest |
结果输出目标(客户端/物化视图/NULL) |
estate |
执行器状态,包含全局信息(如事务 ID、范围表) |
planstate |
计划树的状态树(每个 Plan 节点对应一个 PlanState) |
2. EState(执行器状态)
/* src/include/nodes/execnodes.h */
typedef struct EState
{
NodeTag type;
/* 基本信息 */
CmdType es_operation; /* 操作类型 */
uint64 es_query_id; /* 查询 ID */
/* 快照 */
Snapshot es_snapshot; /* 查询快照 */
Snapshot es_crosscheck_snapshot; /* 交叉检查快照 */
/* 范围表(表和子查询列表) */
List *es_range_table; /* RangeTblEntry 列表 */
/* 结果关系(INSERT/UPDATE/DELETE 目标表) */
ResultRelInfo *es_result_relations; /* 目标表信息 */
int es_num_result_relations;
ResultRelInfo *es_result_relation_info; /* 当前目标表 */
/* 参数 */
ParamListInfo es_param_list_info; /* 外部参数 */
ParamExecData *es_param_exec_vals; /* 内部参数(子查询结果) */
/* 元组表(复用元组槽) */
TupleTableSlot **es_tupleTable; /* 元组槽数组 */
uint32 es_trig_tuple_slot; /* 触发器元组槽索引 */
/* 事务信息 */
TransactionId es_output_cid; /* 输出命令 ID */
/* JIT 编译 */
struct JitContext *es_jit; /* JIT 编译上下文 */
/* 执行统计 */
Instrumentation *es_instrument; /* EXPLAIN ANALYZE 统计 */
/* 内存管理 */
MemoryContext es_query_cxt; /* 查询内存上下文 */
/* 并行执行 */
struct ParallelExecutorInfo *es_parallel_info; /* 并行执行信息 */
/* ... 更多字段 ... */
} EState;
3. PlanState(计划节点状态)
/* src/include/nodes/execnodes.h */
typedef struct PlanState
{
NodeTag type;
Plan *plan; /* 对应的 Plan 节点 */
EState *state; /* 执行器状态 */
ExecProcNodeMtd ExecProcNode; /* 执行函数指针 */
/* 子节点 */
struct PlanState *lefttree; /* 左子树 */
struct PlanState *righttree; /* 右子树 */
/* 表达式状态 */
List *qual; /* 过滤条件(ExprState 列表) */
struct ProjectionInfo *ps_ProjInfo; /* 投影信息 */
/* 元组槽 */
TupleTableSlot *ps_ResultTupleSlot; /* 结果元组槽 */
/* 执行统计 */
Instrumentation *instrument; /* EXPLAIN ANALYZE 统计 */
struct WorkerInstrumentation *worker_instrument; /* 并行工作进程统计 */
/* 状态标志 */
bool chgParam; /* 参数是否改变(需重新初始化) */
/* ... 派生类型特定字段 ... */
} PlanState;
派生类型示例
/* 顺序扫描状态 */
typedef struct SeqScanState
{
ScanState ss; /* 继承 ScanState */
Size pscan_len; /* 并行扫描结构大小 */
} SeqScanState;
/* 索引扫描状态 */
typedef struct IndexScanState
{
ScanState ss; /* 继承 ScanState */
List *indexqualorig; /* 原始索引条件 */
List *indexorderbyorig; /* 原始排序键 */
ScanKey iss_ScanKeys; /* 扫描键数组 */
int iss_NumScanKeys; /* 扫描键数量 */
IndexScanDesc iss_ScanDesc; /* 索引扫描描述符 */
bool iss_RuntimeKeysReady; /* 运行时键是否准备好 */
/* ... 更多字段 ... */
} IndexScanState;
/* 哈希连接状态 */
typedef struct HashJoinState
{
JoinState js; /* 继承 JoinState */
List *hashclauses; /* 哈希键条件 */
HashJoinTable hj_HashTable; /* 哈希表 */
uint32 hj_CurHashValue; /* 当前哈希值 */
int hj_CurBucketNo; /* 当前桶编号 */
int hj_CurSkewBucketNo; /* 当前倾斜桶编号 */
HashJoinTuple hj_CurTuple; /* 当前哈希元组 */
/* ... 更多字段 ... */
} HashJoinState;
类图
classDiagram
class QueryDesc {
+CmdType operation
+PlannedStmt *plannedstmt
+Snapshot snapshot
+EState *estate
+PlanState *planstate
+DestReceiver *dest
}
class EState {
+CmdType es_operation
+Snapshot es_snapshot
+List *es_range_table
+ParamListInfo es_param_list_info
+TupleTableSlot **es_tupleTable
+JitContext *es_jit
}
class PlanState {
<<abstract>>
+Plan *plan
+EState *state
+PlanState *lefttree
+PlanState *righttree
+TupleTableSlot *ps_ResultTupleSlot
+ExecProcNode()
}
class ScanState {
+Relation ss_currentRelation
+TableScanDesc ss_currentScanDesc
+TupleTableSlot *ss_ScanTupleSlot
}
class SeqScanState {
+Size pscan_len
}
class IndexScanState {
+ScanKey iss_ScanKeys
+IndexScanDesc iss_ScanDesc
}
class JoinState {
+JoinType jointype
}
class HashJoinState {
+List *hashclauses
+HashJoinTable hj_HashTable
}
class NestLoopState {
+bool nl_NeedNewOuter
}
QueryDesc --> EState
QueryDesc --> PlanState
EState --> PlanState
PlanState <|-- ScanState
ScanState <|-- SeqScanState
ScanState <|-- IndexScanState
PlanState <|-- JoinState
JoinState <|-- HashJoinState
JoinState <|-- NestLoopState
核心 API 与执行流程
API 1: ExecutorStart() - 初始化执行器
/* src/backend/executor/execMain.c */
void
ExecutorStart(QueryDesc *queryDesc, int eflags)
{
EState *estate;
MemoryContext oldcontext;
/* 1. 创建执行器状态 */
estate = CreateExecutorState();
queryDesc->estate = estate;
/* 2. 设置基本信息 */
estate->es_param_list_info = queryDesc->params;
estate->es_snapshot = queryDesc->snapshot;
estate->es_crosscheck_snapshot = queryDesc->crosscheck_snapshot;
estate->es_range_table = queryDesc->plannedstmt->rtable;
estate->es_plannedstmt = queryDesc->plannedstmt;
/* 3. 切换到查询内存上下文 */
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
/* 4. 初始化计划树 */
queryDesc->planstate = ExecInitNode(queryDesc->plannedstmt->planTree,
estate, eflags);
/* 5. 初始化结果元组描述符 */
ExecAssignExprContext(estate, &queryDesc->planstate->ps_ExprContext);
/* 6. 初始化 JIT 编译(如启用) */
if (jit_enabled && jit_above_cost >= 0 &&
queryDesc->plannedstmt->jitFlags & PGJIT_PERFORM)
{
estate->es_jit = jit_compile_expr(queryDesc);
}
MemoryContextSwitchTo(oldcontext);
}
关键步骤
- 创建执行器状态:分配
EState结构,初始化事务信息、快照、范围表 - 初始化计划树:递归调用
ExecInitNode()为每个 Plan 节点创建对应的 PlanState - 分配元组槽:为每个节点分配
TupleTableSlot(复用槽,避免频繁分配) - 打开表和索引:扫描节点打开目标表或索引
- 初始化表达式:编译投影表达式、过滤条件
- JIT 编译(可选):使用 LLVM 编译热路径表达式
API 2: ExecutorRun() - 执行查询
void
ExecutorRun(QueryDesc *queryDesc,
ScanDirection direction, uint64 count,
bool execute_once)
{
EState *estate;
CmdType operation;
DestReceiver *dest;
bool sendTuples;
TupleTableSlot *slot;
estate = queryDesc->estate;
operation = queryDesc->operation;
dest = queryDesc->dest;
/* 1. 确定是否需要发送元组 */
sendTuples = (operation == CMD_SELECT ||
queryDesc->plannedstmt->hasReturning);
if (sendTuples)
dest->rStartup(dest, operation, queryDesc->tupDesc);
/* 2. 主执行循环 */
for (;;)
{
/* 从计划树根节点获取下一个元组 */
slot = ExecProcNode(queryDesc->planstate);
/* 无更多元组,退出循环 */
if (TupIsNull(slot))
break;
/* 检查是否达到限制 */
if (count && --count == 0)
break;
/* 发送元组到目标(客户端) */
if (sendTuples)
dest->receiveSlot(slot, dest);
}
/* 3. 关闭目标 */
if (sendTuples)
dest->rShutdown(dest);
}
执行流程
- 初始化目标接收器:
dest->rStartup(),准备接收结果 - 主循环:调用
ExecProcNode()逐元组获取结果ExecProcNode()递归调用子节点,直到叶子节点(扫描节点)- 扫描节点从表/索引读取元组,向上层返回
- 发送元组:通过
dest->receiveSlot()发送给客户端 - 退出条件:无更多元组或达到 LIMIT 限制
API 3: ExecProcNode() - 递归执行节点
/* src/include/executor/executor.h */
static inline TupleTableSlot *
ExecProcNode(PlanState *node)
{
/* 检查中断(Ctrl+C) */
CHECK_FOR_INTERRUPTS();
/* 调用节点特定的执行函数 */
return node->ExecProcNode(node);
}
节点执行函数表
每个节点类型有对应的执行函数:
| 节点类型 | 执行函数 | 功能 |
|---|---|---|
T_SeqScan |
ExecSeqScan() |
顺序扫描表 |
T_IndexScan |
ExecIndexScan() |
索引扫描 |
T_NestLoop |
ExecNestLoop() |
嵌套循环连接 |
T_HashJoin |
ExecHashJoin() |
哈希连接 |
T_Hash |
ExecHash() |
构建哈希表 |
T_Agg |
ExecAgg() |
聚合 |
T_Sort |
ExecSort() |
排序 |
T_Limit |
ExecLimit() |
限制行数 |
示例:顺序扫描节点执行
/* src/backend/executor/nodeSeqscan.c */
static TupleTableSlot *
ExecSeqScan(PlanState *pstate)
{
SeqScanState *node = castNode(SeqScanState, pstate);
return ExecScan(&node->ss,
(ExecScanAccessMtd) SeqNext,
(ExecScanRecheckMtd) SeqRecheck);
}
static TupleTableSlot *
SeqNext(SeqScanState *node)
{
TableScanDesc scandesc;
TupleTableSlot *slot;
scandesc = node->ss.ss_currentScanDesc;
slot = node->ss.ss_ScanTupleSlot;
/* 从表中获取下一个元组 */
if (table_scan_getnextslot(scandesc, ForwardScanDirection, slot))
{
/* 返回元组 */
return slot;
}
/* 无更多元组 */
return ExecClearTuple(slot);
}
关键功能实现
功能 1:表达式计算
执行器需要计算各种表达式:投影(SELECT 列表)、过滤(WHERE 条件)、连接条件等。
表达式状态(ExprState)
/* src/include/nodes/execnodes.h */
typedef struct ExprState
{
NodeTag tag;
uint8 flags; /* 表达式标志 */
bool resnull; /* 结果是否为 NULL */
Datum resvalue; /* 结果值 */
TupleTableSlot *resultslot; /* 结果元组槽 */
/* 表达式求值步骤 */
struct ExprEvalStep *steps; /* 求值步骤数组 */
ExprStateEvalFunc evalfunc; /* 求值函数 */
Expr *expr; /* 原始表达式树 */
/* JIT 编译函数 */
void *evalfunc_private; /* JIT 编译后的函数指针 */
/* ... 更多字段 ... */
} ExprState;
表达式计算示例
/* 计算过滤条件 */
bool
ExecQual(ExprState *state, ExprContext *econtext)
{
Datum result;
bool isnull;
/* 调用表达式求值函数 */
result = ExecEvalExprSwitchContext(state, econtext, &isnull);
/* NULL 视为 false */
if (isnull)
return false;
/* 转换为布尔值 */
return DatumGetBool(result);
}
功能 2:哈希连接实现
哈希连接是高效的等值连接算法,适用于大表连接。
哈希连接执行流程
/* src/backend/executor/nodeHashjoin.c */
static TupleTableSlot *
ExecHashJoin(PlanState *pstate)
{
HashJoinState *node = castNode(HashJoinState, pstate);
PlanState *outerNode;
HashJoinTable hashtable;
TupleTableSlot *outerTupleSlot;
/* 1. 第一次调用:构建哈希表(内表) */
if (node->hj_HashTable == NULL)
{
/* 执行内表(右子树),构建哈希表 */
MultiExecHash(node->js.ps.righttree);
node->hj_HashTable = ((HashState *) node->js.ps.righttree)->hashtable;
}
hashtable = node->hj_HashTable;
outerNode = node->js.ps.lefttree;
/* 2. 主循环:探测哈希表(外表) */
for (;;)
{
/* 获取外表的下一个元组 */
outerTupleSlot = ExecProcNode(outerNode);
if (TupIsNull(outerTupleSlot))
return NULL; /* 外表扫描完毕 */
/* 计算哈希值 */
ExecHashGetHashValue(hashtable, econtext, node->hj_OuterHashKeys,
true, false, &node->hj_CurHashValue);
/* 在哈希表中查找匹配元组 */
node->hj_CurTuple = ExecScanHashBucket(node, econtext);
if (node->hj_CurTuple != NULL)
{
/* 找到匹配,返回连接结果 */
return ExecProject(node->js.ps.ps_ProjInfo);
}
}
}
哈希表结构
typedef struct HashJoinTableData
{
int nbuckets; /* 桶数量(2 的幂) */
int log2_nbuckets; /* log2(nbuckets) */
HashJoinTuple *buckets; /* 桶数组 */
int nbatch; /* 批次数(内存不足时分批处理) */
int curbatch; /* 当前批次 */
BufFile **innerBatchFile; /* 内表溢出文件 */
BufFile **outerBatchFile; /* 外表溢出文件 */
Size spaceUsed; /* 已使用内存 */
Size spaceAllowed; /* 允许的最大内存(work_mem) */
/* ... 更多字段 ... */
} HashJoinTableData;
内存管理
- work_mem 限制:哈希表大小不能超过
work_mem - 批次处理:如果内表太大,分批处理:
- 将内表和外表按哈希值分割成多个批次
- 每个批次写入临时文件
- 逐批次加载到内存,执行连接
功能 3:并行执行
PostgreSQL 支持并行查询,由多个工作进程(Worker)协同执行。
并行执行架构
Leader 进程(主进程)
├─ Gather 节点:汇聚结果
└─ Parallel Workers(工作进程)
├─ Worker 0: Partial Seq Scan (part 1)
├─ Worker 1: Partial Seq Scan (part 2)
└─ Worker 2: Partial Seq Scan (part 3)
Gather 节点执行
/* src/backend/executor/nodeGather.c */
static TupleTableSlot *
ExecGather(PlanState *pstate)
{
GatherState *node = castNode(GatherState, pstate);
TupleTableSlot *slot;
/* 1. 第一次调用:启动并行工作进程 */
if (!node->initialized)
{
/* 启动工作进程 */
ExecParallelSetupWorkers(node);
node->initialized = true;
}
/* 2. 从队列中获取元组(来自各个工作进程) */
slot = gather_getnext(node);
if (TupIsNull(slot))
{
/* 所有工作进程完成,清理资源 */
ExecShutdownGather(node);
return NULL;
}
return slot;
}
并行通信
- 共享内存队列:工作进程将结果元组写入共享内存队列
- Leader 汇聚:Leader 进程从队列读取元组,合并返回
- 同步机制:使用条件变量(Condition Variable)通知 Leader
执行时序图
场景:SELECT 查询执行完整流程
sequenceDiagram
autonumber
participant Client as 客户端
participant Portal as Portal
participant Exec as ExecutorStart
participant Run as ExecutorRun
participant Proc as ExecProcNode
participant Scan as SeqScan
participant HeapAM as Heap AM
participant Buffer as Buffer Mgr
participant End as ExecutorEnd
Client->>Portal: PortalRun(portal)
Portal->>Exec: ExecutorStart(queryDesc)
Exec->>Exec: CreateExecutorState()
Exec->>Exec: ExecInitNode(plan)
Exec->>Scan: ExecInitSeqScan()
Scan->>HeapAM: table_beginscan()
HeapAM-->>Scan: 返回扫描描述符
Exec-->>Portal: 初始化完成
Portal->>Run: ExecutorRun(queryDesc)
loop 逐元组获取
Run->>Proc: ExecProcNode(planstate)
Proc->>Scan: ExecSeqScan()
Scan->>HeapAM: table_scan_getnextslot()
HeapAM->>Buffer: ReadBuffer(relid, blocknum)
alt 页面在缓冲区
Buffer-->>HeapAM: 返回页面指针
else 页面不在缓冲区
Buffer->>Buffer: 从磁盘读取页面
Buffer-->>HeapAM: 返回页面指针
end
HeapAM->>HeapAM: 扫描页面,检查可见性
HeapAM-->>Scan: 返回元组
Scan->>Scan: ExecQual(过滤条件)
Scan-->>Proc: 返回元组槽
Proc-->>Run: 返回元组槽
Run->>Client: 发送元组到客户端
end
Run-->>Portal: 执行完成
Portal->>End: ExecutorEnd(queryDesc)
End->>Scan: ExecEndSeqScan()
Scan->>HeapAM: table_endscan()
HeapAM-->>Scan: 关闭扫描
End->>End: FreeExecutorState()
End-->>Portal: 清理完成
Portal-->>Client: 查询结束
性能优化与最佳实践
1. JIT 编译优化
PostgreSQL 11+ 支持 LLVM JIT 编译,加速表达式计算。
启用 JIT
-- 全局启用
SET jit = on;
SET jit_above_cost = 100000; -- 代价超过 10 万时启用 JIT
-- 查看 JIT 效果
EXPLAIN (ANALYZE, VERBOSE)
SELECT SUM(amount) FROM orders WHERE status = 'paid';
/*
输出包含 JIT 编译信息:
Planning Time: 0.5 ms
JIT:
Functions: 3
Options: Inlining true, Optimization true, Expressions true
Timing: Generation 2.5 ms, Inlining 1.2 ms, Optimization 8.3 ms, Emission 5.1 ms, Total 17.1 ms
Execution Time: 25.3 ms
*/
JIT 适用场景
- 大量表达式计算(如复杂的 WHERE 条件)
- 聚合查询(SUM、AVG 等)
- 不适用:简单查询(JIT 编译开销 > 节省时间)
2. work_mem 调优
work_mem 控制排序、哈希等操作的内存使用。
调优策略
-- 查看当前设置
SHOW work_mem; -- 默认 4MB
-- 针对特定会话调整
SET work_mem = '256MB';
-- 查看是否使用临时文件(磁盘排序)
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM large_table ORDER BY created_at DESC;
/*
如果输出包含:
Sort Method: external merge Disk: 102400kB
说明内存不足,使用了临时文件(性能下降)
调优后:
Sort Method: quicksort Memory: 256000kB
说明在内存中完成排序(性能提升)
*/
推荐值
- 简单查询:4MB(默认)
- 中等复杂查询:64-256MB
- 复杂 OLAP 查询:512MB-1GB
- 注意:
work_mem×max_connections不应超过系统内存
3. 并行查询调优
-- 查看并行度
EXPLAIN SELECT COUNT(*) FROM large_table;
/*
Finalize Aggregate (cost=...)
-> Gather (cost=...)
Workers Planned: 4
-> Partial Aggregate (cost=...)
-> Parallel Seq Scan on large_table (cost=...)
*/
-- 调整并行度
SET max_parallel_workers_per_gather = 8;
SET parallel_tuple_cost = 0.05; -- 降低并行通信代价估算
总结
Executor 模块是 PostgreSQL 查询处理的核心,采用火山模型实现高效的迭代执行。关键设计包括:
- 迭代器模式:按需拉取元组,支持流水线执行
- 节点化设计:每种操作封装为节点,易于扩展
- 表达式计算:支持 JIT 编译加速
- 并行执行:多进程协同,提升 OLAP 性能
- 内存管理:work_mem 控制,超限自动溢出到磁盘
理解 Executor 的工作原理,有助于:
- 分析查询性能瓶颈
- 优化 work_mem 等参数
- 设计高效的 SQL 查询