PostgreSQL 表访问方法 API:沉寂多年,终于苏醒

2026-05-20 21 预计阅读时间:1 分钟
来源:postgr.es AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:10 分钟

PostgreSQL 12 引入了 Table Access Method(TAM)API,理论上允许第三方存储引擎以扩展方式接入 PostgreSQL。但此后数年,这个 API 一直处于"有框架、无实战"的状态——唯一可用的访问方法就是默认的 heap。直到最近,多个替代存储引擎开始真正落地,TAM API 才算真正醒来。

什么是 Table Access Method?

Table Access Method 决定了表中数据在磁盘上的物理组织方式。长期以来,PostgreSQL 只有一种存储格式:heap——每行数据按插入顺序写入 8KB 页面,更新产生新版本,旧版本留在原位等待 VACUUM 回收。这套机制简单可靠,但在高更新负载下,表膨胀和 VACUUM 压力是公认的痛点。

TAM API 把"heap 怎么存"这件事从核心代码中抽离成一组回调函数接口:

  • amhandler — 返回访问方法的函数指针结构 TableAmRoutine
  • tuple_insert / tuple_delete / tuple_update — 行级操作
  • tuple_satisfies_snapshot — 可见性判断
  • relation_set_new_filenode — 物理文件管理
  • scan_begin / scan_getnextslot — 扫描迭代

核心执行器不再直接调用 heap 的硬编码逻辑,而是通过 TableAmRoutine 结构体分发到具体实现。这意味着你可以写一个扩展,用完全不同的页面布局、MVCC 机制或压缩策略来存表——只要实现了这些回调。

查看当前系统中的访问方法

先看看你的 PostgreSQL 实例里有哪些访问方法:

-- 查看 pg_am 中注册的所有 table access method
SELECT amname, amtype, amhandler
  FROM pg_am
 WHERE amtype = 't';

在标准 PostgreSQL 16/17 中,结果只有一行:

 amname | amtype |      amhandler
--------+--------+---------------------
 heap   | t      | heap_tableam_handler

amtype = 't' 表示这是表访问方法(区别于索引访问方法 amtype = 'i')。amhandler 是一个返回 TableAmRoutine 的函数 OID。

创建表时指定访问方法:

-- 默认行为,等价于 USING heap
CREATE TABLE orders (
    id      bigint PRIMARY KEY,
    status  text,
    amount  numeric
);

-- 显式指定(当前只有 heap 可选)
CREATE TABLE orders_heap (
    id      bigint PRIMARY KEY,
    status  text,
    amount  numeric
) USING heap;

语法已经就位,只是可选值长期为空。

谁在唤醒这个 API?

zheap——原地更新引擎

EDB(EnterpriseDB)开发的 zheap 是最早瞄准 TAM API 的项目。核心思路:

  • 原地更新(in-place update):更新时直接修改原行,不产生新版本,避免表膨胀。
  • undo log:旧数据写入 undo 区域,读操作通过 undo chain 判断可见性,而非 heap 的多版本链。
  • 更紧凑的页面:行内没有 xmin/xmax 等事务字段,页面利用率更高。

这套设计直接对标 Oracle/InnoDB 的 MVCC 模型。对高更新比例的业务(订单状态流转、库存扣减),理论上能大幅减少 VACUUM 负担。

zheap 曾在 PG 12–13 期间以补丁集形式提交,但因 undo 逻辑与现有子系统(逻辑解码、DDL、索引并发)的交互复杂,多次被推迟合入。目前仍在持续开发,尚未进入主分支。

其他实验性实现

社区中还出现了几个方向探索:

  • 列存原型:把表按列而非按行存储,适合分析型查询。通过 TAM API 接入后,同一张表可以在创建时选择行存或列存。
  • 压缩存储:页面内做字典压缩或前缀压缩,减少 I/O,代价是 CPU 开销增加。
  • 内存表:数据只驻留内存,磁盘仅做持久化备份,适合缓存型场景。

这些项目多数还在概念验证阶段,但它们的存在证明 TAM API 的扩展点确实可用,不是纸上架构。

用扩展注册一个自定义访问方法

下面是一个最小化的概念示例——注册一个"空壳"访问方法,展示 TAM API 的接入流程。这不是一个可运行的存储引擎,但能让你看清注册机制。

第一步:写 handler 函数(C 代码)

// my_tableam.c — 最小化 TableAmRoutine handler
#include "postgres.h"
#include "access/tableam.h"
#include "fmgr.h"

PG_MODULE_MAGIC;

// 声明 handler
PG_FUNCTION_INFO_V1(my_tableam_handler);

Datum
my_tableam_handler(PG_FUNCTION_ARGS)
{
    TableAmRoutine *routine = makeNode(TableAmRoutine);

    // 所有回调暂时指向 heap 的实现(占位)
    // 实际项目中你需要逐个替换为自己的逻辑
    routine->type = T_TableAmRoutine;

    // 关键回调示例(这里只是示意,实际需要填充真实函数指针)
    // routine->tuple_insert  = my_tuple_insert;
    // routine->tuple_delete  = my_tuple_delete;
    // routine->tuple_update  = my_tuple_update;
    // routine->scan_begin    = my_scan_begin;
    // routine->scan_getnextslot = my_scan_getnextslot;

    PG_RETURN_POINTER(routine);
}

第二步:编译为共享库

# 假设 PG 安装在 /usr/pgsql-17
pg_config --pgxs        # 确认路径

# Makefile
cat > Makefile << 'EOF'
MODULES = my_tableam
EXTENSION = my_tableam
DATA = my_tableam--1.0.sql

PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
EOF

make && make install

第三步:SQL 扩展脚本,注册访问方法

-- my_tableam--1.0.sql
CREATE FUNCTION my_tableam_handler()
    RETURNS table_am_handler
    LANGUAGE C
    AS 'MODULE_PATHNAME';

CREATE ACCESS METHOD my_tableam
    TYPE table
    HANDLER my_tableam_handler;

第四步:安装扩展并建表

CREATE EXTENSION my_tableam;

-- 现在 USING 后面多了一个选项
CREATE TABLE test_my (
    id   int,
    data text
) USING my_tableam;

运行到这一步时,因为回调函数只做了占位,实际 DML 会崩溃。但 CREATE ACCESS METHODCREATE TABLE ... USING 两条语句能正常执行,说明注册链路完整。真正的存储引擎需要逐个实现 TableAmRoutine 中约 40 个回调——这是工程量的核心所在。

醒来之后,要注意什么

TAM API 的打开意味着 PostgreSQL 正在从"单一存储引擎"走向"可插拔存储引擎"架构。但几个现实约束需要正视:

与索引的耦合。heap 的 MVCC 信息(xmin/xmax、infomask)被索引扫描直接依赖。如果你的 TAM 用 undo log 或完全不同的可见性模型,索引访问方法也需要适配,否则索引扫描无法正确判断行可见性。zheap 补丁集之所以庞大,很大一部分就是在处理这个交互。

逻辑解码与复制。逻辑解码依赖 heap 的 tuple 格式来提取变更。替代存储引擎必须提供自己的解码接口,否则逻辑复制会断裂。

工具链兼容pg_dumppg_upgradeVACUUMANALYZE 等工具都假设了 heap 的物理结构。TAM 实现需要确保这些工具至少能安全地忽略或正确处理非 heap 表。

性能基准不可照搬。原地更新在 OLTP 场景下可能优于 heap,但读密集场景下 undo chain 遍历可能比 heap 的单行多版本更慢。每种 TAM 都有自己的甜点区,不要假设"新的一定更好"。

检查清单

如果你的团队考虑试用替代存储引擎(当它们正式发布后),先确认这几项:

  • ✅ 该 TAM 是否已通过 CREATE ACCESS METHOD 注册,pg_am 中可见?
  • ✅ 逻辑复制和物理复制是否完整支持?
  • pg_upgrade 能否跨版本迁移该类型的表?
  • ✅ 索引类型(B-tree、GIN、BRIN)是否全部兼容?
  • ✅ 是否有独立的 VACUUM/清理策略,还是依赖核心通用逻辑?
  • ✅ 在你的更新/查询比例下,是否有公开基准数据?

TAM API 的苏醒是 PostgreSQL 存储架构的一次真正松动。它不会立刻取代 heap,但它给了社区一个工程化的接口去验证不同的存储假设。当第一个生产级替代引擎合入主分支时,PostgreSQL 将不再只是"一个存储引擎的数据库"——这值得持续关注。


相关推荐