PostgreSQL-01-Parser-解析器
模块概览
模块职责
Parser 模块是 PostgreSQL 查询处理管道的第一阶段,负责将 SQL 文本字符串转换为结构化的抽象语法树(Abstract Syntax Tree, AST)。该模块不执行任何数据库访问,即使在中止的事务中也能正常工作,返回的"原始"解析树需要由后续的 Analyzer 模块进行语义分析。
核心能力
- 词法分析:识别 SQL 关键字、标识符、字面量、运算符等词法单元(Token)
- 语法分析:根据 PostgreSQL SQL 语法规则构建解析树
- 多种解析模式:支持标准 SQL、PL/pgSQL 表达式、类型名称等不同上下文的解析
- 错误定位:记录每个语法元素在原始 SQL 文本中的位置,便于错误报告
输入/输出
- 输入:
- SQL 文本字符串(const char *)
- 解析模式(RawParseMode):默认模式、类型名称模式、PL/pgSQL 表达式模式等
- 输出:
- 解析树列表(List *):包含一个或多个原始语句节点(RawStmt)
- 失败时返回 NIL(空列表)
上下游依赖
- 上游调用方:
- Backend 进程主循环(tcop/postgres.c)
- PL/pgSQL 编译器(pl/plpgsql)
- 实用工具命令(如 PREPARE)
- 下游被调方:
- Lexer(scan.l):词法分析器,Flex 生成
- Grammar(gram.y):语法分析器,Bison 生成
- 后续处理:
- Analyzer(parser/analyze.c):将解析树转换为查询树(Query)
生命周期
- 解析器状态为临时对象,仅在单次解析过程中存活
- 使用栈上分配的扫描器状态(yyscanner)
- 解析树节点在 CurrentMemoryContext 中分配
- 解析完成后,扫描器资源立即释放
模块架构图
flowchart TB
subgraph Parser["Parser 模块 (src/backend/parser)"]
direction TB
Entry[raw_parser 入口函数] --> Init[初始化阶段]
Init --> Scanner[scanner_init<br/>初始化 Lexer]
Init --> ParserInit[parser_init<br/>初始化 Bison]
Scanner --> Lex[base_yylex<br/>词法分析器]
ParserInit --> Gram[base_yyparse<br/>语法分析器]
Lex -->|Token 流| Gram
Gram --> Actions[语法动作<br/>构建 AST 节点]
Actions --> Tree[生成 Parse Tree]
Tree --> Cleanup[scanner_finish<br/>清理资源]
Cleanup --> Return[返回 List 结构]
end
subgraph External["外部输入"]
SQL[SQL 文本]
Mode[RawParseMode]
end
subgraph Keywords["关键字表"]
KW[ScanKeywords<br/>SQL 关键字列表]
KWTokens[ScanKeywordTokens<br/>关键字到 Token 映射]
end
subgraph Output["输出结果"]
RawStmt[RawStmt 节点]
SelectStmt[SelectStmt]
InsertStmt[InsertStmt]
UpdateStmt[UpdateStmt]
DeleteStmt[DeleteStmt]
UtilityStmt[DDL/DCL 语句]
end
SQL --> Entry
Mode --> Entry
KW --> Scanner
KWTokens --> Scanner
Return --> RawStmt
RawStmt --> SelectStmt
RawStmt --> InsertStmt
RawStmt --> UpdateStmt
RawStmt --> DeleteStmt
RawStmt --> UtilityStmt
subgraph NextStage["后续阶段"]
Analyzer[Analyzer<br/>语义分析]
end
Return --> Analyzer
架构说明
分层设计
-
接口层(parser.c):
raw_parser()函数是唯一的公开入口点- 封装词法和语法分析器的初始化与清理
- 处理多种解析模式的差异
-
词法分析层(scan.l):
- Flex 生成的状态机,识别 SQL 词法单元
- 关键字识别:通过完美哈希表(ScanKeywords)快速查找
- 字符串处理:支持标准 SQL 字符串、转义字符、Unicode 转义
- 注释过滤:自动跳过单行注释(–)和多行注释(/* */)
-
语法分析层(gram.y):
- Bison 生成的 LALR(1) 解析器
- 超过 600 条语法规则,覆盖 PostgreSQL SQL 方言
- 语法动作:嵌入 C 代码构建 AST 节点
- 错误恢复:检测到语法错误时尝试继续解析
关键设计点
1. Token 前瞻处理
标准 SQL 某些语法需要多个 Token 前瞻(如 NULLS FIRST),但 LALR(1) 仅支持单 Token 前瞻。Parser 通过 base_yylex() 中间层实现 Token 合并,将多词关键字合并为单个 Token(如 NULLS_LA + FIRST → NULLS_FIRST),保持语法 LALR(1) 的约束。
2. 多模式支持
通过 RawParseMode 枚举控制解析行为:
RAW_PARSE_DEFAULT:标准 SQL 语句列表(分号分隔)RAW_PARSE_TYPE_NAME:仅解析类型名称(如integer[])RAW_PARSE_PLPGSQL_EXPR:PL/pgSQL 表达式(无需分号)RAW_PARSE_PLPGSQL_ASSIGNn:PL/pgSQL 赋值语句(n 表示目标变量的点号层数)
实现机制:通过 lookahead_token 向 Lexer 注入模式标识 Token,语法文件根据该 Token 选择不同的起始规则。
3. 位置跟踪
每个 AST 节点包含 location 字段,记录其在原始 SQL 文本中的字节偏移(非字符偏移)。错误报告时,通过该偏移在 SQL 文本中标注错误位置(^ 符号)。
4. 内存管理
- 解析树节点通过
palloc()在CurrentMemoryContext中分配 - 扫描器状态(yyscanner)在栈上分配,仅持有指向共享资源的指针
- 关键字表(ScanKeywords)为全局只读数据,编译时静态初始化
边界条件
并发性
- 无全局状态:每次调用
raw_parser()使用独立的扫描器状态 - 线程不安全:依赖 PostgreSQL 单线程后端模型,但不同后端进程可并发调用
- 关键字表为只读共享资源,无竞争问题
输入限制
- SQL 文本长度无硬编码限制,受内存大小约束
- Token 长度限制:标识符最大
NAMEDATALEN - 1字节(默认 63) - 嵌套深度:语法树深度无限制,但深度过大会导致栈溢出(递归解析)
错误处理
- 语法错误:Bison 通过
yyerror()报告错误,返回非零值,raw_parser()返回 NIL - 词法错误:如非法字符、未终止的字符串,Lexer 调用
ereport(ERROR) - 内存不足:
palloc()失败触发elog(ERROR),通过 PostgreSQL 异常机制传播
性能要点
热路径优化
- 关键字查找:使用完美哈希表(编译时生成),O(1) 时间复杂度
- Token 缓冲:Flex 使用内部缓冲区,减少系统调用
- 字符串复制:仅在必要时复制字符串(如去除引号、处理转义)
瓶颈识别
- CPU 密集:词法分析和语法分析均为 CPU 密集操作
- 无 I/O:不访问磁盘或网络
- 内存分配:频繁的
palloc()调用,可能导致内存碎片
优化策略
- 准备语句(Prepared Statement):解析一次,执行多次,缓存解析结果
- 简单查询协议优化:短查询(如
SELECT 1)通过快速路径处理 - 并行解析:不支持,单个 SQL 文本的解析是串行的
版本兼容性
- SQL 语法向后兼容:新版本 PostgreSQL 支持旧版本 SQL 语法
- 新关键字引入:可能导致旧代码使用的标识符成为保留字(需加引号)
- 解析树节点结构:跨版本不兼容,不可序列化持久化(每次查询重新解析)
数据结构与 UML 图
核心数据结构
1. RawStmt(原始语句节点)
/* src/include/nodes/parsenodes.h */
typedef struct RawStmt
{
NodeTag type; /* 节点类型标识:T_RawStmt */
Node *stmt; /* 实际的语句节点(SelectStmt/InsertStmt 等) */
int stmt_location; /* 语句在原始文本中的起始位置(字节偏移) */
int stmt_len; /* 语句长度(字节数),-1 表示未知 */
} RawStmt;
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | NodeTag | 是 | 节点类型标识,固定为 T_RawStmt |
| stmt | Node * | 是 | 指向实际的语句节点(多态指针,可指向 SelectStmt、InsertStmt、CreateStmt 等) |
| stmt_location | int | 是 | 语句在原始 SQL 文本中的起始字节偏移,用于错误定位 |
| stmt_len | int | 否 | 语句长度(字节数),仅在多语句文本时有效;单语句时为 -1 |
设计意义
RawStmt 是解析器返回的顶层节点,封装了实际的语句节点及其元数据。通过统一的包装结构,后续阶段可以获取语句的源码位置信息,用于错误报告和查询日志。
2. SelectStmt(SELECT 语句节点)
/* src/include/nodes/parsenodes.h */
typedef struct SelectStmt
{
NodeTag type; /* 节点类型:T_SelectStmt */
List *distinctClause; /* DISTINCT 子句(NIL 表示无 DISTINCT) */
IntoClause *intoClause; /* INTO 子句(用于 SELECT INTO) */
List *targetList; /* 选择列表(ResTarget 节点列表) */
List *fromClause; /* FROM 子句(RangeVar 等节点列表) */
Node *whereClause; /* WHERE 条件表达式 */
List *groupClause; /* GROUP BY 子句(GroupingClause 节点列表) */
bool groupDistinct; /* GROUP BY DISTINCT */
Node *havingClause; /* HAVING 条件表达式 */
List *windowClause; /* WINDOW 定义列表 */
List *valuesLists; /* VALUES 子句(嵌套 List,用于 VALUES 语法) */
List *sortClause; /* ORDER BY 子句(SortBy 节点列表) */
Node *limitOffset; /* OFFSET 表达式 */
Node *limitCount; /* LIMIT 表达式 */
LimitOption limitOption; /* LIMIT 选项(WITH TIES 等) */
List *lockingClause; /* FOR UPDATE/SHARE 子句 */
WithClause *withClause; /* WITH 子句(CTE) */
/* 集合操作(UNION/INTERSECT/EXCEPT) */
SetOperation op; /* 集合操作类型 */
bool all; /* ALL 标志(如 UNION ALL) */
struct SelectStmt *larg; /* 左操作数(用于集合操作) */
struct SelectStmt *rarg; /* 右操作数(用于集合操作) */
} SelectStmt;
字段说明(核心字段)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| targetList | List * | 是 | 选择列表,包含 ResTarget 节点(如 SELECT a, b+1 AS c) |
| fromClause | List * | 否 | FROM 子句,包含 RangeVar(表)、JoinExpr(连接)等节点 |
| whereClause | Node * | 否 | WHERE 条件表达式,为 Expr 类型节点(如 A_Expr、BoolExpr) |
| groupClause | List * | 否 | GROUP BY 子句,包含分组表达式或 GroupingSet 节点 |
| havingClause | Node * | 否 | HAVING 条件表达式 |
| sortClause | List * | 否 | ORDER BY 子句,包含 SortBy 节点(指定排序列和排序方向) |
| limitCount | Node * | 否 | LIMIT 表达式,可以是常量或参数占位符 |
| limitOffset | Node * | 否 | OFFSET 表达式 |
| op | SetOperation | 否 | 集合操作类型(SETOP_UNION、SETOP_INTERSECT、SETOP_EXCEPT) |
| larg / rarg | SelectStmt * | 否 | 集合操作的左右操作数(递归结构) |
使用场景
- 简单 SELECT:仅
targetList和fromClause非空 - 聚合查询:包含
groupClause和havingClause - 集合操作:
op非空,larg和rarg指向子查询 - VALUES 语法:
valuesLists非空(如VALUES (1, 2), (3, 4))
3. InsertStmt(INSERT 语句节点)
/* src/include/nodes/parsenodes.h */
typedef struct InsertStmt
{
NodeTag type; /* 节点类型:T_InsertStmt */
RangeVar *relation; /* 目标表 */
List *cols; /* 列名列表(ResTarget 节点,仅包含列名) */
Node *selectStmt; /* 数据源(SelectStmt 或 VALUES) */
OnConflictClause *onConflictClause; /* ON CONFLICT 子句 */
List *returningList; /* RETURNING 子句 */
WithClause *withClause; /* WITH 子句(CTE) */
OverridingKind override; /* OVERRIDING SYSTEM/USER VALUE */
} InsertStmt;
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| relation | RangeVar * | 是 | 目标表名(包含模式名、表名) |
| cols | List * | 否 | 插入的列名列表;如为 NIL,表示所有列(按表定义顺序) |
| selectStmt | Node * | 是 | 数据源,可以是 SelectStmt(子查询)或直接 VALUES 列表 |
| onConflictClause | OnConflictClause * | 否 | UPSERT 语法的冲突处理子句(ON CONFLICT DO NOTHING/UPDATE) |
| returningList | List * | 否 | RETURNING 子句,返回插入后的列值(如 RETURNING id) |
语义约束
cols长度必须与selectStmt返回的列数一致(Analyzer 阶段检查)onConflictClause仅在指定冲突目标(唯一索引)或无冲突目标(任意冲突)时有效
4. UpdateStmt(UPDATE 语句节点)
/* src/include/nodes/parsenodes.h */
typedef struct UpdateStmt
{
NodeTag type; /* 节点类型:T_UpdateStmt */
RangeVar *relation; /* 目标表 */
List *targetList; /* SET 子句(ResTarget 节点列表) */
Node *whereClause; /* WHERE 条件表达式 */
List *fromClause; /* FROM 子句(用于多表 UPDATE) */
List *returningList; /* RETURNING 子句 */
WithClause *withClause; /* WITH 子句(CTE) */
} UpdateStmt;
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| relation | RangeVar * | 是 | 目标表名 |
| targetList | List * | 是 | SET 子句,每个 ResTarget 节点表示一个赋值(如 name = 'Alice') |
| whereClause | Node * | 否 | WHERE 条件表达式,限定更新的行 |
| fromClause | List * | 否 | FROM 子句,允许 UPDATE 时连接其他表 |
| returningList | List * | 否 | RETURNING 子句,返回更新后的列值 |
PostgreSQL 特性
PostgreSQL 支持 UPDATE 的 FROM 子句,允许基于其他表的数据更新目标表:
UPDATE users SET status = orders.status
FROM orders
WHERE users.id = orders.user_id;
5. DeleteStmt(DELETE 语句节点)
/* src/include/nodes/parsenodes.h */
typedef struct DeleteStmt
{
NodeTag type; /* 节点类型:T_DeleteStmt */
RangeVar *relation; /* 目标表 */
List *usingClause; /* USING 子句(用于多表 DELETE) */
Node *whereClause; /* WHERE 条件表达式 */
List *returningList; /* RETURNING 子句 */
WithClause *withClause; /* WITH 子句(CTE) */
} DeleteStmt;
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| relation | RangeVar * | 是 | 目标表名 |
| usingClause | List * | 否 | USING 子句,允许 DELETE 时连接其他表 |
| whereClause | Node * | 否 | WHERE 条件表达式,限定删除的行 |
| returningList | List * | 否 | RETURNING 子句,返回删除前的列值 |
PostgreSQL 特性
类似 UPDATE,PostgreSQL 支持 DELETE 的 USING 子句:
DELETE FROM users
USING orders
WHERE users.id = orders.user_id AND orders.status = 'cancelled';
类图(Mermaid ClassDiagram)
classDiagram
class Node {
<<abstract>>
+NodeTag type
}
class RawStmt {
+NodeTag type
+Node *stmt
+int stmt_location
+int stmt_len
}
class SelectStmt {
+NodeTag type
+List *distinctClause
+List *targetList
+List *fromClause
+Node *whereClause
+List *groupClause
+Node *havingClause
+List *sortClause
+Node *limitCount
+Node *limitOffset
+SetOperation op
+SelectStmt *larg
+SelectStmt *rarg
}
class InsertStmt {
+NodeTag type
+RangeVar *relation
+List *cols
+Node *selectStmt
+OnConflictClause *onConflictClause
+List *returningList
}
class UpdateStmt {
+NodeTag type
+RangeVar *relation
+List *targetList
+Node *whereClause
+List *fromClause
+List *returningList
}
class DeleteStmt {
+NodeTag type
+RangeVar *relation
+List *usingClause
+Node *whereClause
+List *returningList
}
class CreateStmt {
+NodeTag type
+RangeVar *relation
+List *tableElts
+List *inhRelations
+PartitionSpec *partspec
+OnCommitAction oncommit
}
class List {
+int length
+ListCell *head
}
class RangeVar {
+char *catalogname
+char *schemaname
+char *relname
+int location
}
class ResTarget {
+char *name
+Node *val
+int location
}
Node <|-- RawStmt
Node <|-- SelectStmt
Node <|-- InsertStmt
Node <|-- UpdateStmt
Node <|-- DeleteStmt
Node <|-- CreateStmt
RawStmt --> Node : stmt
SelectStmt --> List : targetList
SelectStmt --> List : fromClause
SelectStmt --> SelectStmt : larg, rarg
InsertStmt --> RangeVar : relation
InsertStmt --> Node : selectStmt
UpdateStmt --> RangeVar : relation
UpdateStmt --> List : targetList
DeleteStmt --> RangeVar : relation
List --> ResTarget : element
List --> RangeVar : element
类图说明
继承关系
- 所有解析树节点继承自抽象基类
Node Node仅包含type字段(NodeTag 枚举),用于运行时类型识别(RTTI)
组合关系
RawStmt包含多态的stmt指针,指向具体的语句节点SelectStmt支持递归结构(集合操作时larg和rarg指向子查询)- 所有列表使用 PostgreSQL 通用
List结构(双向链表)
多态机制
PostgreSQL 使用 C 语言的"手工多态":
- 基类
Node包含类型标识type - 所有节点类型的第一个字段必须是
NodeTag type - 通过
nodeTag(node)宏获取节点类型,再强制类型转换
示例:
Node *node = (Node *) stmt;
switch (nodeTag(node)) {
case T_SelectStmt:
SelectStmt *select = (SelectStmt *) node;
/* 处理 SELECT 语句 */
break;
case T_InsertStmt:
InsertStmt *insert = (InsertStmt *) node;
/* 处理 INSERT 语句 */
break;
/* ... */
}
API 详细规格与调用链路
API 1: raw_parser() - SQL 文本解析
基本信息
- 函数名称:
raw_parser - 函数签名:
List *raw_parser(const char *str, RawParseMode mode) - 所属文件:
src/backend/parser/parser.c - 协议/方法:C 函数调用(内部 API)
- 幂等性:是(相同输入产生相同输出,无副作用)
请求参数结构
/* 函数参数说明 */
const char *str; /* SQL 文本字符串(以 \0 结尾) */
RawParseMode mode; /* 解析模式(枚举) */
参数表
| 参数 | 类型 | 必填 | 默认 | 约束 | 说明 |
|---|---|---|---|---|---|
| str | const char * | 是 | - | 非 NULL,以 \0 结尾 | SQL 文本字符串,可以包含多条语句(分号分隔) |
| mode | RawParseMode | 是 | RAW_PARSE_DEFAULT | 枚举值之一 | 解析模式,控制语法规则和返回结果格式 |
RawParseMode 枚举值
| 枚举值 | 说明 | 返回结果 |
|---|---|---|
RAW_PARSE_DEFAULT |
标准 SQL 语句列表 | List |
RAW_PARSE_TYPE_NAME |
类型名称(如 integer[]) |
List |
RAW_PARSE_PLPGSQL_EXPR |
PL/pgSQL 表达式 | List |
RAW_PARSE_PLPGSQL_ASSIGN1 |
PL/pgSQL 赋值语句(单级变量) | List |
RAW_PARSE_PLPGSQL_ASSIGN2 |
PL/pgSQL 赋值语句(两级变量,如 a.b) |
List |
RAW_PARSE_PLPGSQL_ASSIGN3 |
PL/pgSQL 赋值语句(三级变量,如 a.b.c) |
List |
响应结构
/* 返回值说明 */
List *result; /* 解析树列表,失败时返回 NIL */
返回值表
| 字段 | 类型 | 必有 | 说明 |
|---|---|---|---|
| result | List * | 是 | 解析树节点列表;成功时非 NIL,失败时为 NIL |
成功场景
- 返回
List结构,包含一个或多个RawStmt节点(或其他节点,取决于mode) - 每个
RawStmt节点的stmt字段指向实际的语句节点(如SelectStmt)
失败场景
- 返回
NIL(空列表指针) - 错误信息通过 PostgreSQL 错误报告机制(
ereport(ERROR))输出 - 常见错误:
- 语法错误(如
SELECT FROM缺少列名) - 词法错误(如未终止的字符串
'abc) - 非法字符(如控制字符)
- 语法错误(如
入口函数核心代码
/* src/backend/parser/parser.c */
List *
raw_parser(const char *str, RawParseMode mode)
{
core_yyscan_t yyscanner;
base_yy_extra_type yyextra;
int yyresult;
/* 1. 初始化词法分析器(Flex 扫描器) */
yyscanner = scanner_init(str, &yyextra.core_yy_extra,
&ScanKeywords, ScanKeywordTokens);
/* 2. 根据解析模式设置前瞻 Token */
if (mode == RAW_PARSE_DEFAULT)
yyextra.have_lookahead = false; /* 默认模式:无前瞻 */
else
{
/* 其他模式:注入模式 Token 作为前瞻,引导语法选择特定规则 */
static const int mode_token[] = {
[RAW_PARSE_DEFAULT] = 0,
[RAW_PARSE_TYPE_NAME] = MODE_TYPE_NAME,
[RAW_PARSE_PLPGSQL_EXPR] = MODE_PLPGSQL_EXPR,
/* ... 其他模式 ... */
};
yyextra.have_lookahead = true;
yyextra.lookahead_token = mode_token[mode];
yyextra.lookahead_yylloc = 0;
yyextra.lookahead_end = NULL;
}
/* 3. 初始化语法分析器(Bison 解析器) */
parser_init(&yyextra);
/* 4. 执行语法分析 */
yyresult = base_yyparse(yyscanner);
/* 5. 清理扫描器资源 */
scanner_finish(yyscanner);
/* 6. 检查解析结果 */
if (yyresult) /* 非零表示错误 */
return NIL;
/* 7. 返回解析树列表 */
return yyextra.parsetree;
}
逐步解释
步骤 1:初始化词法分析器
- 调用
scanner_init()创建 Flex 扫描器状态 - 传入 SQL 文本指针、关键字表(
ScanKeywords)和 Token 映射表(ScanKeywordTokens) yyscanner为不透明句柄,指向扫描器内部状态
步骤 2:设置前瞻 Token
- 默认模式(
RAW_PARSE_DEFAULT):无前瞻,语法从stmtblock规则开始 - 其他模式:设置
lookahead_token为特定值(如MODE_TYPE_NAME),语法从对应的规则开始 - 前瞻机制:Lexer 在第一次调用
yylex()时直接返回lookahead_token,无需扫描输入
步骤 3:初始化语法分析器
- 调用
parser_init()初始化 Bison 解析器状态 - 清空解析树列表(
yyextra.parsetree = NIL)
步骤 4:执行语法分析
- 调用
base_yyparse(),Bison 生成的解析函数 - 内部循环调用
base_yylex()获取 Token,根据语法规则构建解析树 - 解析树节点通过语法动作(嵌入的 C 代码)创建并添加到
yyextra.parsetree
步骤 5:清理扫描器资源
- 调用
scanner_finish()释放 Flex 扫描器占用的内存 - 解析树节点在
CurrentMemoryContext中,不被清理
步骤 6:检查解析结果
yyresult为 0 表示成功,非 0 表示语法错误- 错误时,Bison 已通过
yyerror()调用ereport(ERROR)报告错误(但错误可能被捕获)
步骤 7:返回解析树
- 成功:返回
yyextra.parsetree(非 NIL 的 List 指针) - 失败:返回
NIL
调用链路与上层函数
调用链路 1:Backend 主循环处理 SQL 命令
PostgresMain (postgres.c)
→ exec_simple_query (postgres.c)
→ pg_parse_query (postgres.c)
→ raw_parser (parser.c) ← 当前函数
上层函数:pg_parse_query()
/* src/backend/tcop/postgres.c */
List *
pg_parse_query(const char *query_string)
{
List *raw_parsetree_list;
/* 调用解析器,使用默认模式 */
raw_parsetree_list = raw_parser(query_string, RAW_PARSE_DEFAULT);
/* (此处省略查询重写和规范化处理) */
return raw_parsetree_list;
}
功能说明
pg_parse_query()是 Backend 进程处理 SQL 命令的第一步- 输入:客户端发送的原始 SQL 文本(可能包含多条语句)
- 输出:解析树列表,每个元素对应一条语句
- 后续处理:逐个语句调用 Analyzer、Planner、Executor
上层函数:exec_simple_query()
/* src/backend/tcop/postgres.c */
static void
exec_simple_query(const char *query_string)
{
List *parsetree_list;
ListCell *parsetree_item;
/* 1. 解析 SQL 文本 */
parsetree_list = pg_parse_query(query_string);
/* 2. 遍历每条语句,逐个执行 */
foreach(parsetree_item, parsetree_list)
{
RawStmt *parsetree = lfirst_node(RawStmt, parsetree_item);
/* 3. 语义分析(Analyzer) */
Query *query = parse_analyze_fixedparams(parsetree, query_string, NULL, 0, NULL);
/* 4. 查询重写(Rewriter) */
List *querytree_list = pg_rewrite_query(query);
/* 5. 生成执行计划(Planner) */
/* 6. 执行计划(Executor) */
/* (此处省略后续步骤) */
}
}
功能说明
exec_simple_query()实现"简单查询协议"(Simple Query Protocol)- 流程:Parse → Analyze → Rewrite → Plan → Execute → Return
- 错误处理:任一阶段失败,通过
PG_TRY/PG_CATCH捕获,回滚事务,返回错误给客户端
调用链路 2:PL/pgSQL 编译器解析表达式
plpgsql_compile (pl_comp.c)
→ plpgsql_parse_word (pl_comp.c)
→ raw_parser (parser.c, mode = RAW_PARSE_PLPGSQL_EXPR)
上层函数:plpgsql_parse_word()
/* src/pl/plpgsql/src/pl_comp.c */
PLpgSQL_expr *
plpgsql_parse_word(const char *word)
{
List *parsetree;
RawStmt *rawstmt;
/* 使用 PL/pgSQL 表达式模式解析 */
parsetree = raw_parser(word, RAW_PARSE_PLPGSQL_EXPR);
/* 提取唯一的 RawStmt 节点 */
rawstmt = linitial_node(RawStmt, parsetree);
/* 构造 PLpgSQL_expr 结构 */
/* (此处省略表达式包装代码) */
return expr;
}
功能说明
- PL/pgSQL 变量名或表达式(如
my_var或a + b)需要解析为表达式树 - 使用
RAW_PARSE_PLPGSQL_EXPR模式,语法规则仅接受单个表达式(无分号) - 返回的解析树后续转换为可执行的表达式对象
时序图
sequenceDiagram
autonumber
participant Client as 客户端
participant Backend as Backend 进程
participant PG as PostgresMain
participant ESQ as exec_simple_query
participant PPQ as pg_parse_query
participant RP as raw_parser
participant SI as scanner_init
participant LEX as base_yylex (Lexer)
participant PARSE as base_yyparse (Parser)
participant SF as scanner_finish
Client->>Backend: 发送 SQL 文本
Backend->>PG: PostgresMain() 主循环
PG->>ESQ: exec_simple_query(sql)
ESQ->>PPQ: pg_parse_query(sql)
PPQ->>RP: raw_parser(sql, RAW_PARSE_DEFAULT)
RP->>SI: scanner_init(sql, keywords)
SI-->>RP: yyscanner 句柄
RP->>RP: 设置解析模式(前瞻 Token)
RP->>RP: parser_init(&yyextra)
RP->>PARSE: base_yyparse(yyscanner)
loop 逐 Token 解析
PARSE->>LEX: base_yylex() 获取下一个 Token
LEX->>LEX: 扫描输入,识别关键字/标识符/字面量
LEX-->>PARSE: 返回 Token 类型和值
PARSE->>PARSE: 根据语法规则移进/规约
PARSE->>PARSE: 语法动作:构建 AST 节点
end
PARSE-->>RP: 返回 yyresult(0=成功,非0=失败)
RP->>SF: scanner_finish(yyscanner)
SF-->>RP: 清理完成
alt 解析成功
RP-->>PPQ: 返回 List<RawStmt>
PPQ-->>ESQ: 返回解析树列表
ESQ->>ESQ: 遍历语句,调用 Analyzer
else 解析失败
RP-->>PPQ: 返回 NIL
PPQ->>PPQ: ereport(ERROR) 报告错误
PPQ-->>Client: 返回错误消息
end
时序图说明
关键步骤
- SQL 文本接收(步骤 1-5):客户端通过 libpq 协议发送 SQL,Backend 进程主循环接收
- 解析器初始化(步骤 6-9):初始化 Lexer 和 Parser 状态,设置解析模式
- 词法/语法分析循环(步骤 10-15):Parser 驱动 Lexer 逐 Token 扫描,构建解析树
- 资源清理与返回(步骤 16-20):释放扫描器资源,返回解析树或错误
并发性
- 每个 Backend 进程独立处理一个客户端连接,无跨进程共享状态
- 扫描器和解析器状态在栈或进程私有内存中,无锁竞争
- 关键字表为全局只读数据,可安全并发访问
超时
- 解析器本身无超时机制(纯 CPU 操作)
- 外层通过
statement_timeout全局变量控制,信号处理器中断长时间操作
错误恢复
- Bison 语法包含错误规则(
errorToken),尝试在错误点后恢复解析 - 但 PostgreSQL 实践中,首次错误即中止解析,返回 NIL
- 错误信息通过
ereport(ERROR)输出,包含错误位置(errlocation())
关键功能与实现细节
关键功能 1:词法分析(Lexer)
功能概述
词法分析器(Lexer)负责将 SQL 文本字符串分解为词法单元(Token)序列。Lexer 由 Flex(快速词法分析器生成器)从源文件 scan.l 生成,输出为 scan.c。
核心任务
-
Token 识别:
- SQL 关键字(如
SELECT、FROM、WHERE) - 标识符(表名、列名、变量名)
- 字面量(整数、浮点数、字符串、布尔值)
- 运算符(
+、-、=、||等) - 分隔符(逗号、分号、括号)
- SQL 关键字(如
-
关键字查找:
- 通过完美哈希表(Perfect Hash Table)快速判断标识符是否为关键字
- 关键字表
ScanKeywords在编译时由src/tools/gen_keywordlist.pl生成
-
字符串处理:
- 标准 SQL 字符串:单引号包围(
'text'),内部单引号转义('') - 转义字符串:
E'text\n'(支持 C 风格转义) - Unicode 转义:
U&'data\0041'(Unicode 码点)
- 标准 SQL 字符串:单引号包围(
-
注释过滤:
- 单行注释:
-- comment - 多行注释:
/* comment */(支持嵌套)
- 单行注释:
-
位置跟踪:
- 记录每个 Token 在原始文本中的字节偏移(
yylloc) - 错误报告时高亮错误位置
- 记录每个 Token 在原始文本中的字节偏移(
关键代码:scanner_init()
/* src/backend/parser/scan.l */
core_yyscan_t
scanner_init(const char *str,
core_yy_extra_type *yyext,
const ScanKeywordList *keywordlist,
const uint16 *keyword_tokens)
{
Size slen = strlen(str);
/* 1. 分配扫描器状态结构 */
yyscan_t scanner;
yylex_init(&scanner);
/* 2. 设置输入缓冲区 */
yy_scan_string(str, scanner);
/* 3. 关联扩展数据 */
yyext->keywords = keywordlist;
yyext->keyword_tokens = keyword_tokens;
yyext->backslash_quote = backslash_quote;
yyext->escape_string_warning = escape_string_warning;
yyext->standard_conforming_strings = standard_conforming_strings;
yyset_extra(yyext, scanner);
/* 4. 初始化位置跟踪 */
yyext->scanbuflen = slen;
yyext->scanbuf = str;
return scanner;
}
逐步解释
-
分配扫描器状态:
yylex_init()创建 Flex 生成的扫描器状态结构- 状态为可重入(reentrant),支持多个独立实例
-
设置输入缓冲区:
yy_scan_string()将 SQL 文本字符串设置为输入源- Flex 内部复制字符串到缓冲区(双缓冲机制)
-
关联关键字表:
keywords指向ScanKeywords全局数组(所有 SQL 关键字)keyword_tokens指向关键字到 Token 类型的映射表
-
配置字符串处理行为:
backslash_quote:控制反斜杠转义单引号的行为(GUC 参数)standard_conforming_strings:是否符合 SQL 标准字符串语义
关键代码:base_yylex() Token 前瞻处理
/* src/backend/parser/parser.c */
int
base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
{
base_yy_extra_type *yyextra = pg_yyget_extra(yyscanner);
int cur_token;
int next_token;
/* 1. 处理前瞻 Token(用于模式切换) */
if (yyextra->have_lookahead)
{
yyextra->have_lookahead = false;
cur_token = yyextra->lookahead_token;
*llocp = yyextra->lookahead_yylloc;
if (yyextra->lookahead_end)
*(yyextra->lookahead_end) = yyextra->lookahead_yyleng;
return cur_token;
}
/* 2. 调用 Flex 生成的 Lexer 获取下一个 Token */
cur_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
/* 3. Token 合并逻辑:处理多词关键字 */
/*
* 例如:NULLS FIRST 需要合并为 NULLS_FIRST,因为 LALR(1) 仅支持单 Token 前瞻
*/
if (cur_token == NULLS_LA) /* NULLS 后可能跟 FIRST/LAST */
{
next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
if (next_token == FIRST_P)
cur_token = NULLS_FIRST;
else if (next_token == LAST_P)
cur_token = NULLS_LAST;
else
{
/* 不匹配,回退 next_token */
yyextra->have_lookahead = true;
yyextra->lookahead_token = next_token;
yyextra->lookahead_yylloc = *llocp;
}
}
/* (此处省略其他多词关键字处理) */
return cur_token;
}
逐步解释
-
前瞻 Token 处理:
- 如果
have_lookahead标志为真,直接返回lookahead_token - 用于解析模式切换(如
RAW_PARSE_TYPE_NAME模式注入MODE_TYPE_NAMEToken)
- 如果
-
调用核心 Lexer:
core_yylex()是 Flex 生成的词法分析函数- 返回 Token 类型(整数),Token 值通过
lvalp传递(联合体)
-
多词关键字合并:
- PostgreSQL SQL 语法包含多词关键字(如
NULLS FIRST、WITH TIME ZONE) - LALR(1) 语法仅支持单 Token 前瞻,无法直接识别多词关键字
- 解决方案:在 Lexer 层合并多词关键字为单个 Token
- 示例:
- 扫描到
NULLS,返回NULLS_LA(Lookahead 标记) - 扫描下一个 Token,如果是
FIRST,合并为NULLS_FIRST - 如果不是
FIRST或LAST,回退该 Token(设置前瞻标志)
- 扫描到
- PostgreSQL SQL 语法包含多词关键字(如
Token 合并列表(部分)
| 多词关键字 | 合并后的 Token |
|---|---|
NULLS FIRST |
NULLS_FIRST |
NULLS LAST |
NULLS_LAST |
WITH TIME ZONE |
WITH_TIME |
WITHOUT TIME ZONE |
WITHOUT_TIME |
GROUP BY |
无需合并(BY 已是关键字) |
关键功能 2:语法分析(Parser)
功能概述
语法分析器(Parser)根据 PostgreSQL SQL 语法规则,将 Token 序列构建为抽象语法树(AST)。Parser 由 Bison(GNU 语法分析器生成器)从源文件 gram.y 生成,输出为 gram.c。
核心任务
-
语法规则匹配:
- 定义超过 600 条语法规则,覆盖 SQL 标准与 PostgreSQL 扩展
- LALR(1) 解析算法,单 Token 前瞻
-
AST 节点构造:
- 每条语法规则的语义动作(嵌入 C 代码)构造对应的 AST 节点
- 节点通过
palloc()在当前内存上下文中分配
-
错误恢复:
- 语法错误时,Bison 调用
yyerror()函数 - PostgreSQL 实现中,首次错误即中止解析
- 语法错误时,Bison 调用
语法规则示例:SELECT 语句
/* src/backend/parser/gram.y */
/* 简化的 SELECT 语句规则 */
SelectStmt:
simple_select
| select_with_parens
| select_clause sort_clause opt_for_locking_clause opt_select_limit
/* ... 其他变体 ... */
;
simple_select:
SELECT opt_all_clause opt_target_list
into_clause from_clause where_clause
group_clause having_clause window_clause
{
/* 语义动作:构造 SelectStmt 节点 */
SelectStmt *n = makeNode(SelectStmt);
n->targetList = $3; /* 选择列表 */
n->intoClause = $4; /* INTO 子句 */
n->fromClause = $5; /* FROM 子句 */
n->whereClause = $6; /* WHERE 条件 */
n->groupClause = $7; /* GROUP BY */
n->havingClause = $8; /* HAVING */
n->windowClause = $9; /* WINDOW */
$$ = (Node *) n;
}
;
opt_target_list:
target_list { $$ = $1; }
| /* EMPTY */ { $$ = NIL; }
;
target_list:
target_el { $$ = list_make1($1); }
| target_list ',' target_el { $$ = lappend($1, $3); }
;
target_el:
a_expr AS ColLabel
{
/* 构造 ResTarget 节点(带别名) */
$$ = makeNode(ResTarget);
$$->name = $3;
$$->val = (Node *) $1;
$$->location = @1;
}
| a_expr
{
/* 构造 ResTarget 节点(无别名) */
$$ = makeNode(ResTarget);
$$->name = NULL;
$$->val = (Node *) $1;
$$->location = @1;
}
| '*'
{
/* 通配符 SELECT * */
ColumnRef *cr = makeNode(ColumnRef);
cr->fields = list_make1(makeNode(A_Star));
cr->location = @1;
$$ = makeNode(ResTarget);
$$->name = NULL;
$$->val = (Node *) cr;
$$->location = @1;
}
;
语法规则说明
规则结构
- 规则头(如
SelectStmt:)定义非终结符 - 规则体(如
simple_select)定义该非终结符的可能展开 - 语义动作(
{ ... }中的 C 代码)在规则匹配时执行
符号引用
$n:引用规则中第 n 个符号的语义值(联合体YYSTYPE)$$:当前规则的语义值(返回值)@n:引用第 n 个符号的位置(YYLTYPE,字节偏移)
示例解析
SQL:SELECT id, name AS user_name FROM users WHERE id = 1
解析过程:
- 匹配
simple_select规则 opt_target_list匹配两个target_el:id:表达式为ColumnRef(fields=['id']),无别名name AS user_name:表达式为ColumnRef(fields=['name']),别名为user_name
from_clause匹配users表(RangeVar(relname='users'))where_clause匹配条件表达式(A_Expr(kind=AEXPR_OP, name='=', lexpr=ColumnRef('id'), rexpr=A_Const(1)))- 构造
SelectStmt节点,包含上述字段
AST 结构示例
SelectStmt
├── targetList = List [
│ ├── ResTarget { name=NULL, val=ColumnRef('id'), location=7 }
│ ├── ResTarget { name='user_name', val=ColumnRef('name'), location=11 }
│ ]
├── fromClause = List [
│ └── RangeVar { relname='users', location=33 }
│ ]
├── whereClause = A_Expr {
│ kind = AEXPR_OP,
│ name = List ['='],
│ lexpr = ColumnRef('id'),
│ rexpr = A_Const { val=1, location=49 }
│ }
├── groupClause = NIL
├── havingClause = NULL
└── windowClause = NIL
关键代码:parser_init()
/* src/backend/parser/gram.y */
void
parser_init(base_yy_extra_type *yyext)
{
/* 初始化解析树列表为空 */
yyext->parsetree = NIL;
/* 初始化错误标志 */
yyext->have_error = false;
}
功能说明
- 在调用
base_yyparse()前初始化解析器状态 - 清空解析树列表,准备接收新构建的 AST 节点
- 错误标志用于检测解析过程中的错误(虽然实际未使用,错误通过异常机制传播)
关键代码:base_yyparse() 语法分析主循环
/* src/backend/parser/gram.c(Bison 生成) */
int
base_yyparse(core_yyscan_t yyscanner)
{
/* (此处省略大量 Bison 生成的状态机代码) */
/* 简化的逻辑伪代码 */
int yystate = 0; /* 初始状态 */
int yytoken;
while (true)
{
/* 1. 获取下一个 Token */
yytoken = base_yylex(&yylval, &yylloc, yyscanner);
/* 2. 查表决定动作(移进/规约/接受/错误) */
int action = yytable[yystate][yytoken];
if (action > 0) /* 移进 */
{
/* 将 Token 压栈,切换状态 */
push_stack(yytoken, yylval, yylloc);
yystate = action;
}
else if (action < 0) /* 规约 */
{
/* 根据规则号执行规约 */
int rule = -action;
execute_rule(rule); /* 调用语义动作 */
/* 弹出规则右部的符号,压入规则左部 */
pop_stack(rule_length[rule]);
push_stack(rule_lhs[rule], rule_result);
/* 切换状态(goto 表) */
yystate = yygoto[stack_top_state][rule_lhs[rule]];
}
else if (action == ACCEPT) /* 接受 */
{
return 0; /* 解析成功 */
}
else /* 错误 */
{
yyerror("syntax error");
return 1; /* 解析失败 */
}
}
}
逐步解释
-
状态机驱动:
- Bison 生成的 LALR(1) 解析器是基于状态机的
- 每个状态对应语法分析的某个阶段
- 状态转移表(
yytable)和 goto 表(yygoto)在编译时生成
-
移进(Shift):
- 将当前 Token 压入符号栈
- 切换到新状态,继续读取下一个 Token
-
规约(Reduce):
- 根据规则号,将栈顶的多个符号替换为规则左部的非终结符
- 执行语义动作(构造 AST 节点)
- 更新状态(查 goto 表)
-
接受(Accept):
- 所有 Token 已处理,语法匹配成功
- 返回 0 表示成功
-
错误(Error):
- Token 无法匹配任何规则
- 调用
yyerror()报告错误 - 返回非零值表示失败
错误处理:yyerror()
/* src/backend/parser/gram.y */
static void
yyerror(core_yyscan_t yyscanner, const char *msg)
{
parser_yyerror(msg);
}
void
parser_yyerror(const char *msg)
{
const char *query_text = /* 当前 SQL 文本 */;
int error_location = /* 错误位置 */;
/* 报告语法错误 */
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("%s", msg),
parser_errposition(error_location)));
}
功能说明
- Bison 在检测到语法错误时调用
yyerror() ereport(ERROR)触发 PostgreSQL 异常机制,跳转到错误处理代码parser_errposition()在错误消息中标注错误位置(在 SQL 文本下方显示^)
示例错误输出
SELECT FROM users;
错误消息:
ERROR: syntax error at or near "FROM"
LINE 1: SELECT FROM users;
^
典型使用场景与最佳实践
场景 1:简单 SELECT 查询解析
SQL 示例
SELECT id, name FROM users WHERE id = 1;
解析流程
-
词法分析:
- Token 序列:
SELECT,id,,,name,FROM,users,WHERE,id,=,1,;
- Token 序列:
-
语法分析:
- 匹配
SelectStmt→simple_select规则 - 构造
SelectStmt节点,包含:targetList:两个ResTarget节点(id和name)fromClause:一个RangeVar节点(users表)whereClause:一个A_Expr节点(id = 1)
- 匹配
-
返回结果:
List包含一个RawStmt节点RawStmt->stmt指向SelectStmt节点
代码示例
const char *sql = "SELECT id, name FROM users WHERE id = 1;";
List *parsetree_list = raw_parser(sql, RAW_PARSE_DEFAULT);
/* 提取第一个语句 */
RawStmt *rawstmt = linitial_node(RawStmt, parsetree_list);
SelectStmt *select = (SelectStmt *) rawstmt->stmt;
/* 访问解析树 */
printf("目标列数: %d\n", list_length(select->targetList));
printf("表名: %s\n", ((RangeVar *) linitial(select->fromClause))->relname);
场景 2:多语句批量解析
SQL 示例
BEGIN;
INSERT INTO users VALUES (1, 'Alice');
COMMIT;
解析流程
- 返回
List包含三个RawStmt节点:TransactionStmt(BEGIN)InsertStmt(INSERT)TransactionStmt(COMMIT)
代码示例
const char *sql = "BEGIN; INSERT INTO users VALUES (1, 'Alice'); COMMIT;";
List *parsetree_list = raw_parser(sql, RAW_PARSE_DEFAULT);
/* 遍历所有语句 */
ListCell *lc;
foreach(lc, parsetree_list)
{
RawStmt *rawstmt = lfirst_node(RawStmt, lc);
switch (nodeTag(rawstmt->stmt))
{
case T_TransactionStmt:
printf("事务控制语句\n");
break;
case T_InsertStmt:
printf("插入语句\n");
break;
default:
printf("其他语句\n");
}
}
场景 3:错误处理与诊断
SQL 示例(语法错误)
SELECT * FORM users; /* FROM 拼写错误 */
解析流程
- Lexer 识别
FORM为标识符(非关键字) - Parser 期望
FROM关键字,但收到标识符,触发语法错误 yyerror()调用ereport(ERROR),输出错误消息
错误消息
ERROR: syntax error at or near "FORM"
LINE 1: SELECT * FORM users;
^
最佳实践
- 客户端错误处理:捕获
PQresultStatus()返回PGRES_FATAL_ERROR - 错误码检查:通过
PQresultErrorField(result, PG_DIAG_SQLSTATE)获取 SQLSTATE - 语法错误码:
42601(ERRCODE_SYNTAX_ERROR)
场景 4:准备语句优化
原理
准备语句(Prepared Statement)允许解析一次,执行多次,避免重复解析开销。
SQL 示例
PREPARE get_user AS SELECT * FROM users WHERE id = $1;
EXECUTE get_user(1);
EXECUTE get_user(2);
流程
-
PREPARE命令:- 解析 SQL(
SELECT * FROM users WHERE id = $1) - 生成解析树,缓存在会话内存中
- 参数占位符
$1保留为ParamRef节点
- 解析 SQL(
-
EXECUTE命令:- 跳过解析阶段,直接使用缓存的解析树
- 参数值(
1或2)在执行阶段绑定
性能提升
- 避免重复的词法/语法分析(CPU 密集)
- 适用于高频短查询(如 OLTP 场景)
最佳实践
- 使用 libpq API:
PQprepare()+PQexecPrepared() - ORM 框架(如 SQLAlchemy)自动使用准备语句
- 注意:准备语句的生命周期为会话级别,会话结束后失效
配置与调优
相关 GUC 参数
| 参数名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
standard_conforming_strings |
bool | on | 是否符合 SQL 标准字符串语义(关闭时 \ 为转义字符) |
escape_string_warning |
bool | on | 非标准字符串转义时是否发出警告 |
backslash_quote |
enum | safe_encoding | 控制 \' 的行为(off/on/safe_encoding) |
配置建议
- 生产环境:保持默认值(
standard_conforming_strings = on),符合 SQL 标准 - 迁移旧应用:如应用依赖
\转义,设置standard_conforming_strings = off
性能优化
1. 使用准备语句
- 适用场景:高频执行的相同结构 SQL
- 效果:减少 10%-30% CPU 开销(取决于查询复杂度)
2. 减少语句长度
- 超长 SQL(如大量 INSERT VALUES)会增加解析时间
- 解决方案:使用
COPY命令批量导入(绕过解析器)
3. 避免动态 SQL
- 动态拼接 SQL 字符串无法使用准备语句
- 解决方案:使用参数化查询
调试与诊断
调试工具
1. 查看解析树
启用调试输出(需重新编译,开启 --enable-debug):
/* 设置调试标志 */
debug_print_parse = true;
/* 执行查询 */
SELECT * FROM users;
输出示例(简化):
{QUERY
:commandType 1
:targetList (
{TARGETENTRY
:expr {VAR :varno 1 :varattno 1}
:resname id
}
...
)
...
}
2. GDB 断点调试
gdb --args postgres --single -D /path/to/data postgres
(gdb) break raw_parser
(gdb) run
(gdb) next # 单步执行
(gdb) print yyextra->parsetree # 查看解析树
3. 日志记录
SET client_min_messages = DEBUG1;
SELECT * FROM users;
输出包含解析阶段的详细日志(如关键字查找、Token 识别)。
常见问题诊断
问题 1:语法错误难以定位
症状:错误消息指向错误位置不准确
原因:错误恢复机制可能延迟错误报告
解决方案:
- 检查错误位置前后的语法
- 使用 SQL 格式化工具(如
pgFormatter)规范化 SQL
问题 2:关键字冲突
症状:旧代码使用的标识符成为新版本 PostgreSQL 的保留字
原因:新版本引入新的 SQL 关键字
解决方案:
- 使用双引号包围标识符(如
"user") - 查询保留字列表:
SELECT * FROM pg_get_keywords();
问题 3:解析性能瓶颈
症状:复杂查询解析耗时过长
诊断:
EXPLAIN (ANALYZE, BUFFERS, TIMING ON) SELECT ...;
查看 Planning Time(包含解析时间)
解决方案:
- 简化查询结构(减少嵌套子查询)
- 使用 CTE(WITH 子句)拆分复杂查询
- 使用准备语句缓存解析结果
总结
Parser 模块是 PostgreSQL 查询处理的入口,负责将 SQL 文本转换为结构化的解析树。其设计体现了以下核心原则:
- 分离关注点:词法、语法、语义三阶段分离,Parser 仅关注语法正确性
- 可扩展性:语法规则集中在
gram.y,易于添加新语法 - 错误友好:精确的位置跟踪,便于错误诊断
- 性能优化:完美哈希表、Token 前瞻、准备语句缓存
理解 Parser 模块的实现细节,有助于:
- 诊断 SQL 语法错误
- 扩展 PostgreSQL 语法(自定义 SQL 方言)
- 优化高频查询的解析性能
- 开发 SQL 工具(如格式化、静态分析)