PostgreSQL 顯式鎖定

2021-08-27 15:25 更新
13.3.1. 表級鎖
13.3.2. 行級鎖
13.3.3. 頁級鎖
13.3.4. 死鎖
13.3.5. 咨詢鎖

PostgreSQL提供了多種鎖模式用于控制對表中數(shù)據(jù)的并發(fā)訪問。 這些模式可以用于在MVCC無法給出期望行為的情境中由應(yīng)用控制的鎖。 同樣,大多數(shù)PostgreSQL命令會自動要求恰當(dāng)?shù)逆i以保證被引用的表在命令的執(zhí)行過程中 不會以一種不兼容的方式刪除或修改(例如,TRUNCATE無法安全地與同一表中上的其他操作并發(fā)地執(zhí)行,因此它在表上獲得一個(gè)排他鎖來強(qiáng)制這種行為)。

要檢查在一個(gè)數(shù)據(jù)庫服務(wù)器中當(dāng)前未解除的鎖列表,可以使用pg_locks系統(tǒng)視圖。 有關(guān)監(jiān)控鎖管理器子系統(tǒng)狀態(tài)的更多信息,請參考第 27 章

13.3.1. 表級鎖

下面的列表顯示了可用的鎖模式和PostgreSQL自動使用它們的場合。 你也可以用LOCK命令顯式獲得這些鎖。請記住所有這些鎖模式都是表級鎖,即使它們的名字包含row單詞(這些名稱是歷史遺產(chǎn))。 在一定程度上,這些名字反應(yīng)了每種鎖模式的典型用法 — 但是語意卻都是一樣的。 兩種鎖模式之間真正的區(qū)別是它們有著不同的沖突鎖模式集合(參考本文中表 13.2)。 兩個(gè)事務(wù)在同一時(shí)刻不能在同一個(gè)表上持有屬于相互沖突模式的鎖(但是,一個(gè)事務(wù)決不會和自身沖突。例如,它可以在同一個(gè)表上獲得 ACCESS EXCLUSIVE鎖然后接著獲取ACCESS SHARE鎖)。非沖突鎖模式可以由許多事務(wù)同時(shí)持有。 請?zhí)貏e注意有些鎖模式是自沖突的(例如,在一個(gè)時(shí)刻ACCESS EXCLUSIVE鎖不能被多于一個(gè)事務(wù)持有)而其他鎖模式不是自沖突的(例如,ACCESS SHARE鎖可以被多個(gè)事務(wù)持有)。

表級鎖模式

ACCESS SHARE

只與ACCESS EXCLUSIVE鎖模式?jīng)_突。

SELECT命令在被引用的表上獲得一個(gè)這種模式的鎖。通常,任何只讀取表而不修改它的查詢都將獲得這種鎖模式。

ROW SHARE

EXCLUSIVEACCESS EXCLUSIVE鎖模式?jīng)_突。

SELECT FOR UPDATESELECT FOR SHARE命令在目標(biāo)表上取得一個(gè)這種模式的鎖 (加上在被引用但沒有選擇FOR UPDATE/FOR SHARE的任何其他表上的ACCESS SHARE鎖)。

ROW EXCLUSIVE

SHARESHARE ROW EXCLUSIVE、EXCLUSIVEACCESS EXCLUSIVE鎖模式?jīng)_突。

命令UPDATE、DELETEINSERT在目標(biāo)表上取得這種鎖模式(加上在任何其他被引用表上的ACCESS SHARE鎖)。通常,這種鎖模式將被任何修改表中數(shù)據(jù)的命令取得。

SHARE UPDATE EXCLUSIVE

SHARE UPDATE EXCLUSIVE、SHARE、SHARE ROW EXCLUSIVE、EXCLUSIVEACCESS EXCLUSIVE鎖模式?jīng)_突。這種模式保護(hù)一個(gè)表不受并發(fā)模式改變和 VACUUM運(yùn)行的影響。

VACUUM(不帶FULL)、ANALYZE、 CREATE INDEX CONCURRENTLY、REINDEX CONCURRENTLY、 CREATE STATISTICS以及某些 ALTER INDEXALTER TABLE的變體獲得(完整的詳細(xì)請參考ALTER INDEX ALTER TABLE )。

SHARE

ROW EXCLUSIVESHARE UPDATE EXCLUSIVE、SHARE ROW EXCLUSIVE、EXCLUSIVEACCESS EXCLUSIVE鎖模式?jīng)_突。這種模式保護(hù)一個(gè)表不受并發(fā)數(shù)據(jù)改變的影響。

CREATE INDEX(不帶CONCURRENTLY)取得。

SHARE ROW EXCLUSIVE

ROW EXCLUSIVE、SHARE UPDATE EXCLUSIVE、SHARE、SHARE ROW EXCLUSIVE、EXCLUSIVEACCESS EXCLUSIVE鎖模式?jīng)_突。這種模式保護(hù)一個(gè)表不受并發(fā)數(shù)據(jù)修改所影響,并且是自排他的,這樣在一個(gè)時(shí)刻只能有一個(gè)會話持有它。

CREATE TRIGGER和某些形式的 ALTER TABLE所獲得(見 ALTER TABLE)。

EXCLUSIVE

ROW SHARE、ROW EXCLUSIVE、SHARE UPDATE EXCLUSIVE、SHARESHARE ROW EXCLUSIVE、EXCLUSIVEACCESS EXCLUSIVE鎖模式?jīng)_突。這種模式只允許并發(fā)的ACCESS SHARE鎖,即只有來自于表的讀操作可以與一個(gè)持有該鎖模式的事務(wù)并行處理。

REFRESH MATERIALIZED VIEW CONCURRENTLY獲得。

ACCESS EXCLUSIVE

與所有模式的鎖沖突(ACCESS SHARE、ROW SHARE、ROW EXCLUSIVESHARE UPDATE EXCLUSIVE、SHARE、SHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE)。這種模式保證持有者是訪問該表的唯一事務(wù)。

ALTER TABLE、DROP TABLETRUNCATE、REINDEXCLUSTER、VACUUM FULLREFRESH MATERIALIZED VIEW(不帶 CONCURRENTLY)命令獲取。 很多形式的ALTER INDEXALTER TABLE也在這個(gè)層面上獲得鎖(見ALTER TABLE)。這也是未顯式指定模式的 LOCK TABLE命令的默認(rèn)鎖模式。

提示

只有一個(gè)ACCESS EXCLUSIVE鎖阻塞一個(gè)SELECT(不帶FOR UPDATE/SHARE)語句。

一旦被獲取,一個(gè)鎖通常將被持有直到事務(wù)結(jié)束。 但是如果在建立保存點(diǎn)之后才獲得鎖,那么在回滾到這個(gè)保存點(diǎn)的時(shí)候?qū)⒘⒓瘁尫旁撴i。 這與ROLLBACK取消保存點(diǎn)之后所有的影響的原則保持一致。 同樣的原則也適用于在PL/pgSQL異常塊中獲得的鎖:一個(gè)跳出塊的錯(cuò)誤將釋放在塊中獲得的鎖。

表 13.2. 沖突的鎖模式

請求的鎖模式 已存在的鎖模式
ACCESS SHARE ROW SHARE ROW EXCL. SHARE UPDATE EXCL. SHARE SHARE ROW EXCL. EXCL. ACCESS EXCL.
ACCESS SHARE               X
ROW SHARE             X X
ROW EXCL.         X X X X
SHARE UPDATE EXCL.       X X X X X
SHARE     X X   X X X
SHARE ROW EXCL.     X X X X X X
EXCL.   X X X X X X X
ACCESS EXCL. X X X X X X X X

13.3.2. 行級鎖

除了表級鎖以外,還有行級鎖,在下文列出了行級鎖以及在哪些情境下PostgreSQL會自動使用它們。 行級鎖的完整沖突表請見本文中的表 13.3。注意一個(gè)事務(wù)可能會在相同的行上保持沖突的鎖,甚至是在不同的子事務(wù)中。 但是除此之外,兩個(gè)事務(wù)永遠(yuǎn)不可能在相同的行上持有沖突的鎖。行級鎖不影響數(shù)據(jù)查詢,它們只阻塞對同一行的 寫入者和加鎖者 。 行級鎖在事務(wù)結(jié)束時(shí)或保存點(diǎn)回滾的時(shí)候釋放,就像表級鎖一樣。

行級鎖模式

FOR UPDATE

FOR UPDATE會導(dǎo)致由SELECT語句檢索到的行被鎖定,就好像它們要被更新。這可以阻止它們被其他事務(wù)鎖定、修改或者刪除,一直到當(dāng)前事務(wù)結(jié)束。也就是說其他嘗試UPDATE、DELETESELECT FOR UPDATE、 SELECT FOR NO KEY UPDATE、SELECT FOR SHARE或者SELECT FOR KEY SHARE這些行的事務(wù)將被阻塞,直到當(dāng)前事務(wù)結(jié)束。反過來,SELECT FOR UPDATE將等待已經(jīng)在相同行上運(yùn)行以上這些命令的并發(fā)事務(wù),并且接著鎖定并且返回被更新的行(或者沒有行,因?yàn)樾锌赡芤驯粍h除)。不過,在一個(gè) REPEATABLE READSERIALIZABLE事務(wù)中,如果一個(gè)要被鎖定的行在事務(wù)開始后被更改,將會拋出一個(gè)錯(cuò)誤。進(jìn)一步的討論請見第 13.4 節(jié)。

任何在一行上的DELETE命令也會獲得FOR UPDATE鎖模式,以及修改某些列的值的UPDATE也會獲得該鎖模式。 當(dāng)前UPDATE情況中被考慮的列集合是那些具有能用于外鍵的唯一索引的列(所以部分索引和表達(dá)式索引不被考慮),但是這種要求未來有可能會改變。

FOR NO KEY UPDATE

行為與FOR UPDATE類似,不過獲得的鎖較弱:這種鎖將不會阻塞嘗試在相同行上獲得鎖的SELECT FOR KEY SHARE命令。任何不獲取FOR UPDATE鎖的UPDATE也會獲得這種鎖模式。

FOR SHARE

行為與FOR NO KEY UPDATE類似,不過它在每個(gè)檢索到的行上獲得一個(gè)共享鎖而不是排他鎖。一個(gè)共享鎖會阻塞其他事務(wù)在這些行上執(zhí)行UPDATE、DELETE、SELECT FOR UPDATE或者SELECT FOR NO KEY UPDATE,但是它不會阻止它們執(zhí)行 SELECT FOR SHARE或者SELECT FOR KEY SHARE

FOR KEY SHARE

行為與FOR SHARE類似,不過鎖較弱:SELECT FOR UPDATE會被阻塞,但是SELECT FOR NO KEY UPDATE不會被阻塞。一個(gè)鍵共享鎖會阻塞其他事務(wù)執(zhí)行修改鍵值的DELETE或者UPDATE,但不會阻塞其他 UPDATE,也不會阻止SELECT FOR NO KEY UPDATE、SELECT FOR SHARE或者SELECT FOR KEY SHARE

PostgreSQL不會在內(nèi)存里保存任何關(guān)于已修改行的信息,因此對一次鎖定的行數(shù)沒有限制。 不過,鎖住一行會導(dǎo)致一次磁盤寫,例如, SELECT FOR UPDATE將修改選中的行以標(biāo)記它們被鎖住,并且因此會導(dǎo)致磁盤寫入。

表 13.3. 沖突的行級鎖

要求的鎖模式 當(dāng)前的鎖模式
FOR KEY SHARE FOR SHARE FOR NO KEY UPDATE FOR UPDATE
FOR KEY SHARE       X
FOR SHARE     X X
FOR NO KEY UPDATE   X X X
FOR UPDATE X X X X

13.3.3. 頁級鎖

除了表級別和行級別的鎖以外,頁面級別的共享/排他鎖被用來控制對共享緩沖池中表頁面的讀/寫。 這些鎖在行被抓取或者更新后馬上被釋放。應(yīng)用開發(fā)者通常不需要關(guān)心頁級鎖,我們在這里提到它們只是為了完整。

13.3.4. 死鎖

顯式鎖定的使用可能會增加死鎖的可能性,死鎖是指兩個(gè)(或多個(gè))事務(wù)相互持有對方想要的鎖。例如,如果事務(wù) 1 在表 A 上獲得一個(gè)排他鎖,同時(shí)試圖獲取一個(gè)在表 B 上的排他鎖, 而事務(wù) 2 已經(jīng)持有表 B 的排他鎖,同時(shí)卻正在請求表 A 上的一個(gè)排他鎖,那么兩個(gè)事務(wù)就都不能進(jìn)行下去。PostgreSQL能夠自動檢測到死鎖情況并且會通過中斷其中一個(gè)事務(wù)從而允許其它事務(wù)完成來解決這個(gè)問題(具體哪個(gè)事務(wù)會被中斷是很難預(yù)測的,而且也不應(yīng)該依靠這樣的預(yù)測)。

要注意死鎖也可能會作為行級鎖的結(jié)果而發(fā)生(并且因此,它們即使在沒有使用顯式鎖定的情況下也會發(fā)生)??紤]如下情況,兩個(gè)并發(fā)事務(wù)在修改一個(gè)表。第一個(gè)事務(wù)執(zhí)行:

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 11111;

這樣就在指定帳號的行上獲得了一個(gè)行級鎖。然后,第二個(gè)事務(wù)執(zhí)行:

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 22222;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;

第一個(gè)UPDATE語句成功地在指定行上獲得了一個(gè)行級鎖,因此它成功更新了該行。 但是第二個(gè)UPDATE語句發(fā)現(xiàn)它試圖更新的行已經(jīng)被鎖住了,因此它等待持有該鎖的事務(wù)結(jié)束。事務(wù)二現(xiàn)在就在等待事務(wù)一結(jié)束,然后再繼續(xù)執(zhí)行。現(xiàn)在,事務(wù)一執(zhí)行:

UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;

事務(wù)一試圖在指定行上獲得一個(gè)行級鎖,但是它得不到:事務(wù)二已經(jīng)持有了這樣的鎖。所以它要等待事務(wù)二完成。因此,事務(wù)一被事務(wù)二阻塞,而事務(wù)二也被事務(wù)一阻塞:一個(gè)死鎖。 PostgreSQL將檢測這樣的情況并中斷其中一個(gè)事務(wù)。

防止死鎖的最好方法通常是保證所有使用一個(gè)數(shù)據(jù)庫的應(yīng)用都以一致的順序在多個(gè)對象上獲得鎖。在上面的例子里,如果兩個(gè)事務(wù)以同樣的順序更新那些行,那么就不會發(fā)生死鎖。 我們也應(yīng)該保證一個(gè)事務(wù)中在一個(gè)對象上獲得的第一個(gè)鎖是該對象需要的最嚴(yán)格的鎖模式。如果我們無法提前驗(yàn)證這些,那么可以通過重試因死鎖而中斷的事務(wù)來即時(shí)處理死鎖。

只要沒有檢測到死鎖情況,尋求一個(gè)表級或行級鎖的事務(wù)將無限等待沖突鎖被釋放。這意味著一個(gè)應(yīng)用長時(shí)間保持事務(wù)開啟不是什么好事(例如等待用戶輸入)。

13.3.5. 咨詢鎖

PostgreSQL提供了一種方法創(chuàng)建由應(yīng)用定義其含義的鎖。這種鎖被稱為咨詢鎖,因?yàn)橄到y(tǒng)并不強(qiáng)迫其使用 — 而是由應(yīng)用來保證其正確的使用。咨詢鎖可用于 MVCC 模型不適用的鎖定策略。例如,咨詢鎖的一種常用用法是模擬所謂平面文件數(shù)據(jù)管理系統(tǒng)典型的悲觀鎖策略。雖然一個(gè)存儲在表中的標(biāo)志可以被用于相同目的,但咨詢鎖更快、可以避免表膨脹并且會由服務(wù)器在會話結(jié)束時(shí)自動清理。

有兩種方法在PostgreSQL中獲取一個(gè)咨詢鎖:在會話級別或在事務(wù)級別。一旦在會話級別獲得了咨詢鎖,它將被保持直到被顯式釋放或會話結(jié)束。不同于標(biāo)準(zhǔn)鎖請求,會話級咨詢鎖請求不尊重事務(wù)語義:在一個(gè)后來被回滾的事務(wù)中得到的鎖在回滾后仍然被保持,并且同樣即使調(diào)用它的事務(wù)后來失敗一個(gè)解鎖也是有效的。一個(gè)鎖在它所屬的進(jìn)程中可以被獲取多次;對于每一個(gè)完成的鎖請求必須有一個(gè)相應(yīng)的解鎖請求,直至鎖被真正釋放。在另一方面,事務(wù)級鎖請求的行為更像普通鎖請求:在事務(wù)結(jié)束時(shí)會自動釋放它們,并且沒有顯式的解鎖操作。這種行為通常比會話級別的行為更方便,因?yàn)樗褂靡粋€(gè)咨詢鎖的時(shí)間更短。對于同一咨詢鎖標(biāo)識符的會話級別和事務(wù)級別的鎖請求按照期望將彼此阻塞。如果一個(gè)會話已經(jīng)持有了一個(gè)給定的咨詢鎖,由它發(fā)出的附加請求將總是成功,即使有其他會話在等待該鎖;不管現(xiàn)有的鎖和新請求是處在會話級別還是事務(wù)級別,這種說法都是真的。

和所有PostgreSQL中的鎖一樣,當(dāng)前被任何會話所持有的咨詢鎖的完整列表可以在pg_locks系統(tǒng)視圖中找到。

咨詢鎖和普通鎖都被存儲在一個(gè)共享內(nèi)存池中,它的尺寸由max_locks_per_transactionmax_connections配置變量定義。 必須當(dāng)心不要耗盡這些內(nèi)存,否則服務(wù)器將不能再授予任何鎖。這對服務(wù)器可以授予的咨詢鎖數(shù)量設(shè)置了一個(gè)上限,根據(jù)服務(wù)器的配置不同,這個(gè)限制通常是數(shù)萬到數(shù)十萬。

在使用咨詢鎖方法的特定情況下,特別是查詢中涉及顯式排序和LIMIT子句時(shí),由于 SQL 表達(dá)式被計(jì)算的順序,必須小心控制鎖的獲取。例如:

SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- danger!
SELECT pg_advisory_lock(q.id) FROM
(
  SELECT id FROM foo WHERE id > 12345 LIMIT 100
) q; -- ok

在上述查詢中,第二種形式是危險(xiǎn)的,因?yàn)椴荒鼙WC在鎖定函數(shù)被執(zhí)行之前應(yīng)用LIMIT。這可能導(dǎo)致獲得某些應(yīng)用不期望的鎖,并因此在會話結(jié)束之前無法釋放。 從應(yīng)用的角度來看,這樣的鎖將被掛起,雖然它們?nèi)匀辉?code class="structname">pg_locks中可見。

提供的操作咨詢鎖函數(shù)在第 9.27.10 節(jié)中描述。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號