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— 返回访问方法的函数指针结构TableAmRoutinetuple_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 METHOD 和 CREATE TABLE ... USING 两条语句能正常执行,说明注册链路完整。真正的存储引擎需要逐个实现 TableAmRoutine 中约 40 个回调——这是工程量的核心所在。
醒来之后,要注意什么
TAM API 的打开意味着 PostgreSQL 正在从"单一存储引擎"走向"可插拔存储引擎"架构。但几个现实约束需要正视:
与索引的耦合。heap 的 MVCC 信息(xmin/xmax、infomask)被索引扫描直接依赖。如果你的 TAM 用 undo log 或完全不同的可见性模型,索引访问方法也需要适配,否则索引扫描无法正确判断行可见性。zheap 补丁集之所以庞大,很大一部分就是在处理这个交互。
逻辑解码与复制。逻辑解码依赖 heap 的 tuple 格式来提取变更。替代存储引擎必须提供自己的解码接口,否则逻辑复制会断裂。
工具链兼容。pg_dump、pg_upgrade、VACUUM、ANALYZE 等工具都假设了 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 将不再只是"一个存储引擎的数据库"——这值得持续关注。