Skip to content

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模式 OID2200 (public)
relowner所有者 OID10 (postgres)
relkind关系类型'r' (ordinary table)
relfilenode物理文件节点12345
reltablespace表空间 OID1663 (pg_default)
relnatts列数5
reltype行类型 OID12346

6.3 pg_attribute 系统属性

每个表还会自动创建以下系统列:

列名类型说明
tableoidoid表 OID(用于继承)
xminxid插入事务 ID
xmaxxid删除事务 ID
cmincid命令 ID
cmaxcid命令 ID
ctidtid元组物理位置

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/12345

7.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.craw_parser()语法解析入口
src/backend/parser/gram.yCreateStmt语法规则定义
src/backend/parser/parse_utilcmd.c:164transformCreateStmt()语义转换
src/backend/parser/analyze.c:506语句分类CMD_UTILITY 判断
src/backend/tcop/postgres.c:825pg_rewrite_query()跳过重写
src/backend/tcop/utility.c:1136case T_CreateStmt执行入口
src/backend/commands/tablecmds.c:713DefineRelation()表创建主函数
src/backend/catalog/heap.c:1105heap_create_with_catalog()系统表更新
src/backend/catalog/heap.c:820AddNewAttributeTuples()列定义存储
src/backend/catalog/heap.c:2237StoreConstraints()约束存储
src/backend/catalog/storage.c:120RelationCreateStorage()物理存储创建
src/backend/storage/smgr/md.c:190mdcreate()文件创建
src/include/nodes/parsenodes.h:2672CreateStmt数据结构定义

12. 总结

CREATE TABLE 执行的核心要点

  1. 语法解析: Bison 语法解析器将 SQL 文本转换为 CreateStmt 语法树

  2. 语句分类: CreateStmt 被分类为 CMD_UTILITY,完全跳过查询重写阶段

  3. 跳过规划: 由于是 DDL 操作,无需查询优化,直接进入执行阶段

  4. 语义转换: transformCreateStmt() 完成类型解析、约束转换等语义处理

  5. 系统表更新:

    • pg_class: 存储表元数据
    • pg_attribute: 存储列定义
    • pg_type: 创建复合类型
  6. 物理存储: 创建实际的磁盘文件存储数据

  7. 事务保护: 所有操作在事务中进行,支持完整回滚

设计哲学

PostgreSQL 将 DDL 视为"Utility 语句"而非"Query",这是一个重要的架构决策:

  • 性能: 避免不必要的重写和优化开销
  • 简洁性: DDL 执行路径更直接
  • 一致性: 元数据变更与数据操作使用相同的原子性保证

本文档基于 PostgreSQL 源码分析编写,涵盖从 SQL 文本到物理表创建的完整流程。