事务中的 Postgres 锁

Posted

技术标签:

【中文标题】事务中的 Postgres 锁【英文标题】:Postgres locks within a transaction 【发布时间】:2016-11-22 21:56:01 【问题描述】:

我无法理解锁如何与 Postgres 中的事务交互。

当我运行这个(长)查询时,我对发生的高度锁定感到惊讶:

BEGIN;
TRUNCATE foo;
\COPY foo FROM 'backup.txt';
COMMIT;

\COPY 的documentation 没有提到它需要什么级别的锁,但this post 表明它只获得一个 RowExclusiveLock。但是当我在\COPY 期间运行此查询时:

SELECT mode, granted FROM pg_locks
WHERE relation='foo'::regclass::oid;

我明白了:

mode    granted
RowExclusiveLock    true
ShareLock   true
AccessExclusiveLock true

AccessExclusiveLock 到底是从哪里来的?我假设它来自TRUNCATE,也就是requires an AccessExclusiveLock。但是TRUNCATE 很快完成,所以我希望锁也能很快释放。这给我留下了几个问题。

当事务中的命令获取锁时,该锁是否在命令结束时(事务结束之前)释放?如果是这样,为什么我会观察到上述行为?如果不是,为什么不呢?其实既然transactions don't touch the table until the COMMIT,为什么事务中的TRUNCATE还需要阻塞表呢?

我在 PG 中的 documentation for transactions 中没有看到任何关于此的讨论。

【问题讨论】:

【参考方案1】:

这里有几个误解需要澄清。

首先,事务在提交之前确实接触了表。您引用的评论说ROLLBACK(以及COMMIT)不要碰桌子,这是不同的。他们在提交日志pg_clog)中记录事务状态,COMMIT 将事务日志刷新到磁盘(其中一个值得注意的例外是TRUNCATE,这与您的问题相关: 旧表一直保留到事务结束,并在COMMIT 期间被删除。

如果所有更改都被推迟到COMMIT 并且不会使用任何锁,那么COMMIT 将非常昂贵并且由于并发修改而经常会失败。事务必须像以前一样记住数据库的状态,并检查更改是否仍然适用。这种处理并发的方式称为optimistic concurreny control,虽然它对于应用程序来说是一个不错的策略,但它不适用于关系数据库,COMMIT 应该是高效的并且不应该失败(除非有重大问题与基础设施)。

所以关系数据库使用的是悲观并发控制锁定,即它们在访问数据库对象之前锁定它以防止并发活动妨碍它们。

第二,关系型数据库使用two-phase locking,其中的锁(至少是用户可见的,所谓的重量级锁)一直保持到结束交易。 这对于保持事务的逻辑顺序 (serializable) 和一致是必要的(但还不够)。如果您释放锁,而其他人删除了您插入但未提交的行通过外键约束引用的行怎么办?

回答问题

所有这一切的结果是您的表将保持ACCESS EXCLUSIVE 锁定从TRUNCATE 直到事务结束。为什么这不是很明显吗?如果在TRUNCATE 之后允许其他事务甚至读取表,他们会发现它是空的,因为TRUNCATE 确实清空了表并且不遵守MVCC 语义。这样的dirty read(可能尚未回滚的未提交数据)是不允许的。

如果您确实需要在重新填充期间对表格进行读取访问,您可以使用DELETE 而不是TRUNCATE。缺点是这是一个更昂贵的操作,它会留下很多“死元组”,必须通过 autovacuum 删除,从而导致大量空白空间(table bloat) .但是,如果您愿意忍受一个臃肿的表和索引,以至于表和索引扫描至少需要两倍的时间,那么这是一种选择。

【讨论】:

谢谢@Laurenz!不过,我仍在努力了解事务中的TRUNCATE 是如何工作的。如果“TRUNCATE 真的清空了桌子”,并且“ROLLBACK [doesn't] touch the table”,那么TRUNCATE 怎么回滚呢? 我也很难理解您所说的“脏读”是什么意思(我在docs 中看到过它),但这可能需要自己的问题。 我解释了“脏读”并添加了一个链接。你说得对,我对TRUNCATE 的解释有点不一致。如in the code 所述,旧表不会立即被丢弃,而是在提交时被丢弃,因此在这种情况下COMMIT 不会完全接触表,而是将其删除。我会尽量在我的解释中更清楚地说明这一点。

以上是关于事务中的 Postgres 锁的主要内容,如果未能解决你的问题,请参考以下文章

Postgres 写/读锁

Spring 事务和 postgres(VACUUM,在事务外部运行)

node.js + postgres 数据库事务管理

没有可序列化事务的 Postgres 序列化错误

Spring Batch 无法通过 DB (postgres) 获取作业锁

带有事务块的 Python Postgres 查询