Mats Kindahl:构建表访问方法,第6部分:TRUNCATE

💡 原文英文,约1900词,阅读约需7分钟。
📝

内容提要

本文介绍PostgreSQL表访问方法中TRUNCATE的处理:TRUNCATE需用新arena替换旧arena,并保证事务回滚安全——提交时释放旧arena,回滚时释放新arena。若表在同一(子)事务中创建或已被截断,则走原地截断路径,直接重置游标。实现需将缓存条目保留至事务结束,并处理保存点回滚后无缓存条目的情况。

🔎

延伸解读

TRUNCATE 为何不能直接重置游标

文章指出,TRUNCATE 可能运行在会回滚的事务中。若直接重置 arena 的 tuple_free 游标,回滚后 relfilenode 虽恢复,但游标不会恢复,TRUNCATE 后插入的行会覆盖旧数据。同理,立即释放旧 arena 也不行,因为已释放的段无法恢复。因此旧 arena 必须保持原样,直到事务确认提交。

两个 arena 的释放时机取决于事务结果

回调为 TRUNCATE 安排两条清理记录:新 arena 在事务中止时释放,旧 arena 在事务提交时释放。这样提交后旧 arena 被回收、新 arena 保留;回滚后新 arena 被回收、旧 arena 继续持有真实数据。两条记录走与 CREATE/DROP 相同的清理列表和子事务回调,因此保存点回滚也能正确处理。

原地截断路径与缓存条目的保留

若表在同一(子)事务中创建或已被截断,Postgres 走 relation_nontransactional_truncate,不分配新 relfilenode,直接重置游标。为此,finalize 函数不再立即删除缓存条目,而是留到事务结束由回调统一移除。否则同一事务中 CREATE 后紧跟 TRUNCATE 会找不到条目而什么都不做。

保存点回滚后条目缺失的兜底处理

保存点内 TRUNCATE 回滚时,其缓存条目会随新建 arena 一起被移除,但原 arena 仍是活跃的。之后释放保存点再 TRUNCATE 同一张表,会走原地路径却找不到条目。此时回调回退到 relfilenode,它仍是 arena 的句柄,据此映射共享内存段并重置 tuple_free,保证截断仍然生效。

❓

Q&A

PostgreSQL 表访问方法中,TRUNCATE 操作如何安全地替换表的 arena?

TRUNCATE 会为表创建一个新的 arena,并安排清理:如果事务提交,则释放旧 arena;如果事务回滚,则释放新 arena。这样确保旧 arena 在事务提交前保持原样,避免回滚时数据丢失。

为什么不能直接重置 arena 的游标来实现 TRUNCATE?

因为 TRUNCATE 可能在会回滚的事务中执行,回滚后旧 arena 仍持有真实数据。重置游标会丢失数据,且回滚无法恢复游标,导致后续插入覆盖旧数据。

在同一个事务中先创建表再 TRUNCATE,PostgreSQL 会走哪条路径?

会走原地截断路径(in-place path),调用 relation_nontransactional_truncate,直接重置 arena 的游标,因为回滚会丢弃整个表,无需保留旧数据。

TRUNCATE 在保存点回滚后,缓存条目缺失时如何处理?

如果 TRUNCATE 在保存点内回滚,其缓存条目会被移除。之后对同一事务中早先创建的表再次 TRUNCATE 时,会走原地路径,此时缓存条目可能不存在,回调会通过 relfilenode 找到 arena 并重置游标。

memtable_ProcessUtility 如何扩展以支持 TRUNCATE?

它在调用标准 ProcessUtility 后,检查语句是否为 TruncateStmt,若是则遍历所有关系,对每个关系调用 memtable_finalize_new_relation,将 pg_class 中的 relfilenode 更新为 DSM 句柄。

TRUNCATE 时如何确保旧 arena 在事务提交前不被释放?

在 relation_set_new_filelocator 回调中,会捕获旧 arena 的句柄,并安排清理:新 arena 在事务中止时释放,旧 arena 在事务提交时释放。这样旧 arena 会一直保留到事务确定提交。

🏷️

标签

➡️

继续阅读