PostgreSQL CREATE TABLE 执行流程深度解析
摘要
本文深入剖析 PostgreSQL 源码,详细阐述一条 CREATE TABLE SQL 语句从客户端发送到数据库后的完整执行流程。文章涵盖语法解析、语义转换、查询重写、查询规划、执行引擎以及表创建的核心数据结构,帮助读者全面理解 PostgreSQL 如何将用户的表定义转化为数据库系统中的物理存储和系统表元数据。
1. 执行流程概览
cpp
┌─────────────────────────────────────────────────────────────────────────────┐
│ CREATE TABLE 执行流程总览 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ SQL 文本 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ [1] 语法解析阶段 │ │
│ │ raw_parser() → parsetree │ │
│ │ gram.y → CreateStmt (未做语义分析) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ [2] 分析与重写阶段 │ │
│ │ pg_analyze_and_rewrite_fixedparams() │ │
│ │ → CreateStmt 被标记为 CMD_UTILITY │ │
│ │ → 完全跳过 QueryRewrite() 重写阶段! │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ [3] 规划阶段 │ │
│ │ pg_plan_queries() → 跳过优化器(无规划) │ │
│ │ → PlannedStmt with utilityStmt │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ [4] 执行阶段 │ │
│ │ PortalRun() → ProcessUtility() │ │
│ │ → transformCreateStmt() │ │
│ │ → DefineRelation() │ │
│ │ ├── 更新 pg_class, pg_attribute │ │
│ │ ├── 创建物理存储文件 │ │
│ │ └── 存储约束与默认值 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘2. 核心数据结构
2.1 CreateStmt - 表定义节点
c
// src/include/nodes/parsenodes.h:2672-2689
typedef struct CreateStmt {
NodeTag type;
RangeVar *relation; // 表名(带模式)
List *tableElts; // 列定义和约束列表
List *inhRelations; // INHERITS 子句(继承父表)
PartitionBoundSpec *partbound; // 分区边界定义
PartitionSpec *partspec; // 分区规范
TypeName *ofTypename; // OF typename (类型化表)
List *constraints; // CHECK 约束(转换后)
List *nnconstraints; // NOT NULL 约束(转换后)
List *options; // WITH 子句选项
OnCommitAction oncommit; // ON COMMIT 动作
char *tablespacename; // TABLESPACE 表空间名
char *accessMethod; // USING 访问方法(默认heap)
bool if_not_exists; // IF NOT EXISTS 标志
} CreateStmt;2.2 ColumnDef - 列定义
c
// src/include/nodes/parsenodes.h:725-748
typedef struct ColumnDef {
NodeTag type;
char *colname; // 列名
TypeName *typeName; // 数据类型
List *constraints; // 列级约束
Node *raw_default; // 原始默认值表达式(未转换)
Node *cooked_default; // 转换后的默认值表达式
bool is_not_null; // NOT NULL 标志
char identity; // IDENTITY 设置
Oid collOid; // 排序规则 OID
ParseLoc location; // 解析位置
} ColumnDef;2.3 Constraint - 约束定义
c
// src/include/nodes/parsenodes.h:2753-2799
typedef struct Constraint {
NodeTag type;
ConstrType contype; // 约束类型
// 约束类型枚举值:
// CONSTR_NULL - NULL 约束(非标准)
// CONSTR_NOTNULL - NOT NULL
// CONSTR_DEFAULT - 默认值
// CONSTR_IDENTITY - IDENTITY 列
// CONSTR_GENERATED - 生成列
// CONSTR_CHECK - CHECK 约束
// CONSTR_PRIMARY - 主键
// CONSTR_UNIQUE - 唯一约束
// CONSTR_EXCLUSION - EXCLUDE 约束
// CONSTR_FOREIGN - 外键
char *conname; // 约束名
List *keys; // 列名列表(主键/唯一/非空)
Node *raw_expr; // CHECK/DEFAULT 原始表达式
char *cooked_expr; // CHECK/DEFAULT 转换后表达式
// 外键相关字段
RangeVar *pktable; // 引用的主表
List *fk_attrs; // 外键列
List *pk_attrs; // 主键列
ParseLoc location;
} Constraint;3. 语法解析阶段
3.1 解析入口函数
c
// src/backend/parser/parser.c
List *
raw_parser(const char *query_string)
{
// 1. 初始化词法分析器
scanner_init(query_string, &yyg.extra);
// 2. 初始化语法分析器
parser_init(yyscanner);
// 3. 调用 Bison 生成的语法分析器
base_yyparse(yyscanner);
// 4. 返回语法树列表
return yyg.result;
}3.2 语法规则 (gram.y:3577-3701)
cpp
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptWith OnCommitAttribute OptTableSpace
{
// 构建 CreateStmt 节点
$$ = makeNode(CreateStmt);
$$->relation = $4;
$$->tableElts = $6;
$$->oncommit = $8;
$$->tablespacename = $9;
}
| CREATE TABLE qualified_name ...
;3.3 解析流程图
cpp
SQL: CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE,
age INTEGER DEFAULT 18,
created_at TIMESTAMP DEFAULT NOW()
)
│
▼
┌──────────────────────────────────────────────────────────────┐
│ gram.y 语法规则 │
├──────────────────────────────────────────────────────────────┤
│ │
│ CreateStmt │
│ │ │
│ ├── relation: "users" │
│ │ │
│ └── tableElts: [ColumnDef, ColumnDef, ...] │
│ │
│ ColumnDef (id) ColumnDef (name) │
│ ├── colname: "id" ├── colname: "name" │
│ ├── typeName: "serial" ├── typeName: "varchar│
│ └── constraints: [PRIMARY KEY] └── constraints: [...]│
│ │
└──────────────────────────────────────────────────────────────┘
│
▼
返回 parsetree: List of Node* (CreateStmt)
├── 无语义分析
├── 表达式未转换
└── 类型未解析4. 分析与重写阶段
4.1 关键发现: CREATE TABLE 跳过重写
这是一个重要的设计决策:DDL 语句完全跳过查询重写阶段。
c
// src/backend/tcop/postgres.c:825-840
List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
if (query->commandType == CMD_UTILITY)
{
/* 不重写 utility 语句,直接返回 */
querytree_list = list_make1(query);
}
else
{
/* 只有 DML 才进行重写 */
querytree_list = QueryRewrite(query);
}
return querytree_list;
}4.2 语句分类
c
// src/backend/parser/analyze.c:506-515
switch (nodeTag(parseTree))
{
case T_CreateStmt:
// CREATE TABLE 被归类为 CMD_UTILITY
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *) parseTree;
break;
case T_SelectStmt:
// SELECT 被归类为 CMD_SELECT
// 会进入查询优化器
break;
default:
// 其他 DDL 也是 CMD_UTILITY
result->commandType = CMD_UTILITY;
}4.3 为什么跳过规划?
c
// src/backend/tcop/postgres.c:915-917
if (querytree->commandType == CMD_UTILITY)
return NULL; // 跳过 planner(),无需生成执行计划
// 原因:
// 1. CREATE TABLE 没有数据访问,无需优化路径
// 2. CREATE TABLE 是元数据操作,步骤固定
// 3. 规划会浪费时间5. 执行阶段详解
5.1 执行入口链
cpp
exec_simple_query()
│
▼
PortalRun() // src/backend/tcop/pquery.c
│
├── ChoosePortalStrategy() // → PORTAL_MULTI_QUERY
│
▼
PortalRunMulti()
│
├── PortalRunUtility() // 执行 DDL
│
▼
ProcessUtility() // src/backend/tcop/utility.c:499
│
▼
standard_ProcessUtility() // src/backend/tcop/utility.c:543
│
▼
ProcessUtilitySlow() // src/backend/tcop/utility.c:1100
│
▼
case T_CreateStmt: // src/backend/tcop/utility.c:1136-1269
│
├── transformCreateStmt() // 语义转换
│
▼
DefineRelation() // src/backend/commands/tablecmds.c:713
│
├── 命名空间检查
├── 权限验证
├── 构建元组描述符
│
▼
heap_create_with_catalog() // src/backend/catalog/heap.c:1105
│
├── 创建 pg_class 元组
├── 创建 pg_attribute 元组
├── 创建物理文件
│
▼
StoreConstraints() // 存储约束
│
└── 事务提交/回滚处理5.2 语义转换: transformCreateStmt()
c
// src/backend/parser/parse_utilcmd.c:164-375
List *
transformCreateStmt(ParseState *pstate, CreateStmt *stmt)
{
CreateStmtContext cxt;
ListCell *listptr;
// 1. 初始化上下文
initCreateStmtContext(pstate, stmt, &cxt);
// 2. 遍历所有表元素(列和约束)
foreach(listptr, stmt->tableElts)
{
Node *element = lfirst(listptr);
if (IsA(element, ColumnDef))
{
// 转换列定义
transformColumnDefinition(pstate, &cxt,
(ColumnDef *) element);
}
else if (IsA(element, Constraint))
{
// 转换表级约束
transformTableConstraint(pstate, &cxt,
(Constraint *) element);
}
else if (IsA(element, TableLikeClause))
{
// 处理 LIKE 子句
transformTableLikeClause(pstate, &cxt,
(TableLikeClause *) element);
}
}
// 3. 处理 NOT NULL 约束
transformNotNullConstraints(pstate, &cxt);
// 4. 处理索引约束(PRIMARY KEY, UNIQUE)
transformIndexConstraints(pstate, &cxt);
// 5. 处理外键约束
transformFKConstraints(pstate, &cxt);
// 6. 处理 CHECK 约束
transformCheckConstraints(pstate, &cxt);
return list_make1(stmt);
}5.3 核心执行: DefineRelation()
c
// src/backend/commands/tablecmds.c:713-1315
ObjectAddress
DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
{
Relation rel;
Oid relid;
TupleDesc desc;
CreateStmtContext cxt;
// ===== 步骤1: 权限和命名空间检查 =====
namespaceId = RangeVarGetAndCheckCreationNamespace(
stmt->relation, NoLock, &lockmode);
// ===== 步骤2: 处理继承(构建属性列表) =====
MergeAttributes(stmt, parentTpl, &attrList, &constrList);
// ===== 步骤3: 构建元组描述符 =====
desc = BuildDescForRelation(stmt, attrList);
// ===== 步骤4: 创建表和目录 =====
relid = heap_create_with_catalog(
stmt->relation->relname, // 表名
namespaceId, // 模式 OID
ownerId, // 所有者 OID
InvalidOid, // 表空间(默认)
relid, // 关系 OID
relkind, // 关系类型
stmt->accessMethod, // 访问方法
NULL, // 列属性
desc, // 元组描述符
NIL, // 约束
stmt->oncommit,
stmt->create_storage,
true, // is partitioned
false); // is typed table
// ===== 步骤5: 存储继承信息 =====
StoreCatalogInheritance(relid, parentOidList,
partitionKey, partbound);
return ObjectAddress { .classId = RelationRelationId,
.objectId = relid };
}6. 系统表更新
6.1 heap_create_with_catalog 详解
c
// src/backend/catalog/heap.c:1105-1500
Oid
heap_create_with_catalog(const char *relname,
Oid namespace,
Oid relowner,
Oid reltablespace,
...)
{
Oid relid;
Relation pg_class_desc;
// ===== 1. 属性名称和类型验证 =====
CheckAttributeNamesTypes(columnDefs, relkind);
// ===== 2. 分配新的关系 OID =====
relid = GetNewRelFileNumber(reltablespace, ...);
// ===== 3. 创建物理存储 =====
if (create_storage)
RelationCreateStorage(relid, reltablespace, persistent);
// ===== 4. 创建 pg_type 条目(复合类型) =====
typeOid = AddNewRelationType(relname, ...);
// ===== 5. 在 pg_class 中插入元组 =====
AddNewRelationTuple(pg_class_desc, relid, relname, ...);
// ===== 6. 在 pg_attribute 中插入列定义 =====
AddNewAttributeTuples(relid, columnDefs);
// ===== 7. 命令计数递增(使可见) =====
CommandCounterIncrement();
// ===== 8. 存储约束和默认值 =====
StoreConstraints(constraints, relid);
return relid;
}6.2 pg_class 字段说明
| 字段 | 说明 | 示例值 |
|---|---|---|
relname | 表名 | "users" |
relnamespace | 模式 OID | 2200 (public) |
relowner | 所有者 OID | 10 (postgres) |
relkind | 关系类型 | 'r' (ordinary table) |
relfilenode | 物理文件节点 | 12345 |
reltablespace | 表空间 OID | 1663 (pg_default) |
relnatts | 列数 | 5 |
reltype | 行类型 OID | 12346 |
6.3 pg_attribute 系统属性
每个表还会自动创建以下系统列:
| 列名 | 类型 | 说明 |
|---|---|---|
tableoid | oid | 表 OID(用于继承) |
xmin | xid | 插入事务 ID |
xmax | xid | 删除事务 ID |
cmin | cid | 命令 ID |
cmax | cid | 命令 ID |
ctid | tid | 元组物理位置 |
7. 物理存储创建
7.1 存储创建流程
cpp
heap_create_with_catalog()
│
▼
RelationCreateStorage()
│
▼
smgrcreate() // src/backend/storage/smgr/smgr.c
│
▼
mdcreate() // src/backend/storage/smgr/md.c:190
│
├── TablespaceCreateDbspace() // 确保表空间目录存在
│
├── relpath() // 生成文件路径
│ 例: base/5/12345
│
└── open(O_CREAT | O_EXCL) // 创建空文件7.2 文件路径格式
shell
$PGDATA/
└── base/
└── {database_oid}/
└── {relfilenode}
示例: /var/lib/postgresql/data/base/5/123457.3 UNLOGGED 表的特殊处理
c
// 对于 UNLOGGED 表,还会创建初始化分叉
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
// 创建恢复用的初始化分叉
smgrcreate(srel, INIT_FORKNUM, false);
log_smgrcreate(newrlocator, INIT_FORKNUM);
}8. 约束处理
8.1 StoreConstraints 函数
c
// src/backend/catalog/heap.c:2237-2286
void
StoreConstraints(List *constraints, Oid relid)
{
ListCell *lc;
foreach(lc, constraints)
{
Constraint *constr = lfirst(lc);
switch (constr->contype)
{
case CONSTR_CHECK:
StoreRelCheck(relid, constr); // → pg_constraint
break;
case CONSTR_NOTNULL:
StoreRelNotNull(relid, constr); // → pg_constraint
break;
case CONSTR_DEFAULT:
StoreAttrDefault(relid, attnum, // → pg_attrdef
constr->cooked_expr);
break;
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
// 通过创建索引实现
index_create(relid, constr->conname, ...);
break;
case CONSTR_FOREIGN:
// 延迟到外键约束创建阶段
StoreFkConstraint(relid, constr);
break;
}
}
}8.2 CHECK 约束存储
c
// CHECK 约束表达式被序列化为字符串存储
char *
StoreRelCheck(Oid relid, Constraint *constr)
{
// 将表达式节点转换为字符串
char *expr_str = nodeToString(constr->cooked_expr);
// 插入 pg_constraint 系统表
TupleDesc desc = RelationDescGetTupleDesc(ConstraintRelationId);
HeapTuple tuple = heap_form_tuple(desc, values, nulls);
CatalogTupleInsert(ConstraintRelationId, tuple);
}9. 事务语义
9.1 DDL 的事务特性
c
// 创建表是一个原子操作
BeginTransactionBlock()
│
▼
// ... 表创建过程 ...
│
▼
CommandCounterIncrement(); // 使目录变更可见
// 如果需要多个可见点,可能多次 CCI
CommandCounterIncrement();
// ...
EndTransactionBlock()9.2 临时表的 ON COMMIT 行为
c
// 在 heap_create_with_catalog 中
if (oncommit != ONCOMMIT_NOOP)
{
// 注册提交时的动作
register_on_commit_action(relid, oncommit);
}
// 可能的值:
// ONCOMMIT_NOOP - 无特殊动作
// ONCOMMIT_DELETE_ROWS - 提交时删除所有行
// ONCOMMIT_PRESERVE_ROWS - 保留行(默认)
// ONCOMMIT_DROP - 提交时删除表9.3 回滚处理
c
// 物理文件创建在事务期间
// 如果事务回滚,需要删除这些文件
typedef struct PendingRelDelete
{
Oid relid; // 待删除的关系 OID
Oid tablespace; // 表空间
bool atCommit; // true=提交时删除, false=回滚时删除
struct PendingRelDelete *next;
} PendingRelDelete;
// 在 AtEOXact_RelationCache() 中处理
void
AtEOXact_RelationCache(bool isCommit)
{
PendingRelDelete *p;
for (p = pendingDeletes; p != NULL; p = p->next)
{
if (isCommit ? p->atCommit : !p->atCommit)
{
// 删除关系缓存条目和物理文件
smgrdounlinkall(p->relid, p->tablespace);
}
}
}10. 完整调用序列图
shell
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 客户端 │ │ PostgreSQL │ │ Parser │ │ Planner │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
│ CREATE TABLE ... │ │ │
│──────────────────>│ │ │
│ │ │ │
│ exec_simple_query() │ │
│ │ │ │
│ │ raw_parser() │ │
│ │──────────────────>│ │
│ │<──────────────────│ │
│ │ parsetree │ │
│ │ │ │
│ │ pg_analyze_*() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ CMD_UTILITY ────┼──→ 跳过 QueryRewrite
│ │ │ │ │
│ │ ▼ │ │
│ │ 跳过 Planner │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ PortalRun() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ProcessUtility() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ transformCreate │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ DefineRelation() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ heap_create_*() │ │
│ │ │ │ │ │
│ │ │ ▼ │ │
│ │ │ pg_class │ │
│ │ │ pg_attr* │ │
│ │ │ │ │ │
│ │ │ ▼ │ │
│ │ │ 物理文件 │ │
│ │ │ │ │ │
│ │ │ ▼ │ │
│ │ │ pg_const* │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ CommandCounter │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 事务提交 │ │
│ │ │ │
│ │ COMMAND COMPLETE│ │
│<──────────────────│ │ │
│ │ │ │11. 关键源码文件索引
| 文件路径 | 关键函数 | 功能描述 |
|---|---|---|
src/backend/parser/parser.c | raw_parser() | 语法解析入口 |
src/backend/parser/gram.y | CreateStmt | 语法规则定义 |
src/backend/parser/parse_utilcmd.c:164 | transformCreateStmt() | 语义转换 |
src/backend/parser/analyze.c:506 | 语句分类 | CMD_UTILITY 判断 |
src/backend/tcop/postgres.c:825 | pg_rewrite_query() | 跳过重写 |
src/backend/tcop/utility.c:1136 | case T_CreateStmt | 执行入口 |
src/backend/commands/tablecmds.c:713 | DefineRelation() | 表创建主函数 |
src/backend/catalog/heap.c:1105 | heap_create_with_catalog() | 系统表更新 |
src/backend/catalog/heap.c:820 | AddNewAttributeTuples() | 列定义存储 |
src/backend/catalog/heap.c:2237 | StoreConstraints() | 约束存储 |
src/backend/catalog/storage.c:120 | RelationCreateStorage() | 物理存储创建 |
src/backend/storage/smgr/md.c:190 | mdcreate() | 文件创建 |
src/include/nodes/parsenodes.h:2672 | CreateStmt | 数据结构定义 |
12. 总结
CREATE TABLE 执行的核心要点
语法解析: Bison 语法解析器将 SQL 文本转换为
CreateStmt语法树语句分类:
CreateStmt被分类为CMD_UTILITY,完全跳过查询重写阶段跳过规划: 由于是 DDL 操作,无需查询优化,直接进入执行阶段
语义转换:
transformCreateStmt()完成类型解析、约束转换等语义处理系统表更新:
pg_class: 存储表元数据pg_attribute: 存储列定义pg_type: 创建复合类型
物理存储: 创建实际的磁盘文件存储数据
事务保护: 所有操作在事务中进行,支持完整回滚
设计哲学
PostgreSQL 将 DDL 视为"Utility 语句"而非"Query",这是一个重要的架构决策:
- 性能: 避免不必要的重写和优化开销
- 简洁性: DDL 执行路径更直接
- 一致性: 元数据变更与数据操作使用相同的原子性保证
本文档基于 PostgreSQL 源码分析编写,涵盖从 SQL 文本到物理表创建的完整流程。