2016-11-22 130 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」並不完全觸及表,而是將其刪除。我會盡力在我的解釋中更清楚。 –