事务中的Postgres锁定

时间:2016-11-22 21:56:01

标签: postgresql transactions locking postgresql-9.4

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

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

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

\COPY的{​​{3}}没有提到它需要什么级别的锁,但documentation表示它只获得RowExclusiveLock。但是当我在\COPY

期间运行此查询时
SELECT mode, granted FROM pg_locks
WHERE relation='foo'::regclass::oid;

我明白了:

mode    granted
RowExclusiveLock    true
ShareLock   true
AccessExclusiveLock true

哪里有AccessExclusiveLock来自哪里?我假设它来自TRUNCATEthis post。但是TRUNCATE很快就完成了,所以我希望锁定能够快速释放。这给我留下了一些问题。

当事务中的命令获取锁时,该命令结束时(在事务结束之前)是否释放了锁?如果是这样,为什么我会观察到上述行为?如果没有,为什么不呢?事实上,自requires an AccessExclusiveLock以来,为什么事务中的TRUNCATE需要阻止该表?

我在PG的transactions don't touch the table until the COMMIT documentation中没有看到对此的讨论。

1 个答案:

答案 0 :(得分:9)

这里有一些误解可以解决。

首先,事务在提交之前触摸该表。您引用的评论说,ROLLBACK(以及COMMIT也不会触及表格,这是不同的。它们在提交日志中记录事务状态(在pg_clog中),COMMIT将事务日志刷新到磁盘(一个值得注意的例外是TRUNCATE,与您的问题相关:旧表保留到交易结束,并在COMMIT期间被删除。

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

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

第二,关系数据库使用two-phase locking,其中锁定(至少是用户可见的,所谓的重量级锁)始终保持不变交易结束。 这是保持事务按逻辑顺序(可序列化)并保持一致的必要条件(但不充分)。如果你释放一个锁,然后其他人删除你插入但未提交的行通过外键约束引用的行,该怎么办?

回答问题

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

如果您在重新填充期间确实需要对表格的读取权限,则可以使用DELETE代替TRUNCATE。缺点是这是一个更昂贵的操作,将使表中有许多必须通过autovacuum删除的“死元组”,导致大量空白空间(表膨胀) 。但是,如果您愿意使用表格和索引,这些表格和索引扫描至少需要两倍的时间,那么这是一个选项。