2016-11-22 131 views
3

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

当我运行这个(长)查询,我被高度锁定的出现感到惊讶:

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

的​​为\COPY没有提到它需要什么级别的锁,但是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中看到transactions的任何讨论。

回答

8

这里有一些误解需要澄清。

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

如果所有更改都被阻止,直到COMMIT并且不会锁定,那么COMMIT会相当昂贵,并且由于并发修改而会经常失败。交易必须记住数据库的状态,并检查更改是否仍然适用。这种处理并发性的方式称为optimistic concurreny control,虽然它对于应用程序来说是一种体面的策略,但对于关系数据库来说效果不佳,其中COMMIT应该是有效的并且不应该失败(除非基础架构存在主要问题) 。

所以关系型数据库用的是悲观并发控制锁定,即他们他们访问,以防止他们的方式获得并发活动之前锁定的数据库对象。

,关系数据库使用two-phase locking,其中锁(至少是用户可见,所谓重量级锁)一直保持到事务结束。 这是必要的(但不足以保持逻辑顺序的交易(可序列化)并且一致。如果你释放一个锁,并且其他人删除了插入但未提交的行通过外键约束引用的行?

问题的答案

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

如果您在加注过程中确实需要读取表格,则可以使用DELETE而不是TRUNCATE。不足之处在于,这是一个非常昂贵的操作,它会在表格中留下很多“死元组”,这些元组必须通过autovacuum删除,导致大量空的空间(表膨胀)。但是,如果您愿意与表格和索引一起臃肿,这样表和索引扫描至少需要两次,那么这是一个选项。

+0

谢谢@Laurenz!尽管如此,我仍然很难理解交易中的TRUNCATE是如何工作的。如果“'TRUNCATE'确实清空了表格”,并且“'ROLLBACK' [不]触摸表格”,那么'TRUNCATE'如何回滚? –

+0

我也很难理解“脏读”是什么意思(我在[docs](https://www.postgresql.org/docs/current/static/transaction-iso.html)中看到它提到) ,但这可能需要自己的问题。 –

+0

我解释了“脏读”并添加了一个链接。你是对的,当谈到'TRUNCATE'时,我的解释有点不一致。正如[在代码中所述](https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/commands/tablecmds.c;h=f97bee5b0e464265e2af103053800900f40058ff;hb=HEAD #l1214),旧表不会立即抛出,而是在提交时,所以在这种情况下,“COMMIT”并不完全触及表,而是将其删除。我会尽力在我的解释中更清楚。 –