为什么PostgreSQL的 TRUNCATE TABLE在 MVCC下不安全?

后端开发 2026-07-11

在调试我们系统中的一个微妙问题时,我意识到 TRUNCATE TABLE 在并发事务看到空表方面引发了问题。

重新查看文档,我发现文档明确说明它并非MVCC安全。

TRUNCATE 不是MVCC安全的。截断后,对使用截断发生前所创建的快照的并发事务来说,该表将显示为空。更多细节请参阅第13.5节

为什么会这样?

解决方案

如果你不需要一次清除多张表,并且你确实需要MVCC安全,请使用一个简单的未限定的 deletedb<>fiddle的演示

delete from yourtable;

truncate 的多目标特性必须通过如下方式,设置一系列单独的 delete 来模拟。如果你需要把它们全部作为一个查询来执行,请将它们包装在CTE中。

Delete 会遍历每个保存表数据的文件,并留下一个带有你的事务标识符的标记,表示所有比你新的事务应当发现该表已被清空。所有仍在进行中的事务的并发会话可以忽略该标记,并继续使用它们之前的快照,直到它们 commit/rollback

Truncate 相反地只是创建一个空文件,并将表重新链接到它,从而分离并让旧文件进入垃圾回收——在这里,每个人的MVCC “书签” 规定谁看到什么。你可以在 src/backend/commands/tablecmds.c:2230 中看到它。

c /*(...) * Create a new empty storage file for the relation, and assign it * as the relfilenumber value. The old storage file is scheduled * for deletion at commit. */ RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);

它不是MVCC安全的,但它是事务安全的,因此尽管表的“看起来像被清空”是即时的,磁盘空间的释放实际上是在围绕 truncate 的事务提交时才会安排发生,且只有在该事务提交时才发生,而不是在 truncate 命令完成后立即发生。

c /* * Schedule unlinking of the old storage at transaction commit, except * when performing a binary upgrade, when we must do it immediately. */ if (IsBinaryUpgrade) {/*(...)*/ } else { /* Not a binary upgrade, so just schedule it to happen later. */ RelationDropStorage(relation); }


作为不切换到普通的 delete 的替代方案,你可以确保使用你计划对其进行 truncate 的表的其他会话请求并对其锁定一些锁,直到它们完成。一个 truncate 将尝试获取一个 access exclusive 锁,因此它只会等待在它之前发起的会话完成对目标对象的操作,并阻塞新来的会话。

TRUNCATE 在它操作的每张表上获取一个 ACCESS EXCLUSIVE 锁,这会阻塞该表的所有其他并发操作。当指定 RESTART IDENTITY 时,任何需要重启的序列也将被同样的独占锁锁定。如果需要对同一表进行并发访问,则应改用 DELETE 命令。

ACCESS EXCLUSIVE (AccessExclusiveLock)

与所有模式的锁冲突(ACCESS SHAREROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVE、和 ACCESS EXCLUSIVE)。此模式保证持有者在任何方面都是唯一访问该表的事务。 通过 DROP TABLETRUNCATEREINDEXCLUSTERVACUUM FULLREFRESH MATERIALIZED VIEW(不带 CONCURRENTLY)命令获得。许多形式的 ALTER INDEXALTER TABLE 也在此级别获取锁。这也是对未明确指定模式的 LOCK TABLE 语句的默认锁模式。

提示
只有 ACCESS EXCLUSIVE 锁会阻止一个 SELECT(不带 FOR UPDATE/SHARE)语句。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章