事务中的 Postgres 锁

Postgres locks within a transaction

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

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

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

documentation for \COPY doesn't mention what level of lock it requires, but this post表示只获取一个RowExclusiveLock。但是当我 运行 这个查询在 \COPY:

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

我明白了:

mode    granted
RowExclusiveLock    true
ShareLock   true
AccessExclusiveLock true

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

当事务中的命令获取锁时,是否会在命令结束时(事务结束前)释放该锁?如果是这样,为什么我会观察到上述行为?如果不是,为什么不呢?事实上,既然 ,为什么交易中的 TRUNCATE 需要完全阻止 table?

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

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

首先,事务确实在提交之前触及table。您引用的评论说 ROLLBACK(还有 COMMIT)不要触及 table,这是不同的。他们在commit log(在pg_clog)中记录事务状态,并且COMMIT将事务日志刷新到磁盘(一个没有table异常到这是 TRUNCATE,这与您的问题相关:旧的 table 一直保留到交易结束,并在 COMMIT 期间被删除。

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

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

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

问题的答案

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

如果在重新填充期间您确实需要对 table 的读取权限,您可以使用 DELETE 而不是 TRUNCATE。不利的一面是,这是一个更昂贵的操作,它会给 table 留下很多必须通过 autovacuum 删除的“死元组”,从而导致很多空的 space (table 臃肿)。但是,如果您愿意忍受 table 和膨胀的索引,以至于 table 和索引扫描至少需要两倍的时间,这是一个选择。