我无法理解锁与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来自哪里?我假设它来自TRUNCATE
,this post。但是TRUNCATE
很快就完成了,所以我希望锁定能够快速释放。这给我留下了一些问题。
当事务中的命令获取锁时,该命令结束时(在事务结束之前)是否释放了锁?如果是这样,为什么我会观察到上述行为?如果没有,为什么不呢?事实上,自requires an AccessExclusiveLock以来,为什么事务中的TRUNCATE
需要阻止该表?
我在PG的transactions don't touch the table until the COMMIT
documentation中没有看到对此的讨论。
答案 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语义。
如果您在重新填充期间确实需要对表格的读取权限,则可以使用DELETE
代替TRUNCATE
。缺点是这是一个更昂贵的操作,将使表中有许多必须通过autovacuum删除的“死元组”,导致大量空白空间(表膨胀) 。但是,如果您愿意使用表格和索引,这些表格和索引扫描至少需要两倍的时间,那么这是一个选项。