支援的版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3

13.3. 顯式鎖定 #

PostgreSQL 提供了多種鎖定模式,以控制對資料表中資料的並行存取。在以下情況下,這些模式可用於應用程式控制的鎖定:MVCC無法提供所需的行為。此外,大多數 PostgreSQL 指令會自動取得適當模式的鎖定,以確保在指令執行時,參考的資料表不會以不相容的方式被刪除或修改。(例如,TRUNCATE 無法安全地與同一資料表上的其他操作同時執行,因此它會取得資料表的 ACCESS EXCLUSIVE 鎖定以強制執行。)

若要檢查資料庫伺服器中目前未完成鎖定的清單,請使用 pg_locks 系統檢視表。有關監控鎖定管理器子系統狀態的更多資訊,請參閱第 27 章

13.3.1. 表層級鎖定 #

下面的清單顯示了可用的鎖定模式以及 PostgreSQL 自動使用它們的上下文。您也可以使用 LOCK 指令顯式地取得這些鎖定中的任何一個。請記住,所有這些鎖定模式都是表層級鎖定,即使名稱包含「row」(列)這個詞;鎖定模式的名稱是歷史遺留的。在某種程度上,這些名稱反映了每種鎖定模式的典型用法,但語意都是相同的。一種鎖定模式和另一種鎖定模式之間唯一真正的區別是每種鎖定模式與之衝突的鎖定模式集合(請參閱表 13.2)。兩個交易不能同時在同一個資料表上持有衝突模式的鎖定。(但是,一個交易永遠不會與自身衝突。例如,它可能會取得 ACCESS EXCLUSIVE 鎖定,然後在同一資料表上取得 ACCESS SHARE 鎖定。)非衝突的鎖定模式可以由許多交易同時持有。特別要注意的是,某些鎖定模式是自我衝突的(例如,ACCESS EXCLUSIVE 鎖定一次只能由一個交易持有),而另一些鎖定模式則不是自我衝突的(例如,ACCESS SHARE 鎖定可以由多個交易持有)。

表層級鎖定模式

ACCESS SHARE (AccessShareLock)

僅與 ACCESS EXCLUSIVE 鎖定模式衝突。

SELECT 指令在參考的資料表上取得此模式的鎖定。通常,任何僅讀取資料表而不修改它的查詢都將取得此鎖定模式。

ROW SHARE (RowShareLock)

EXCLUSIVEACCESS EXCLUSIVE 鎖定模式衝突。

SELECT 指令在指定 FOR UPDATEFOR NO KEY UPDATEFOR SHAREFOR KEY SHARE 選項之一的所有資料表上取得此模式的鎖定(除了在任何其他未指定任何顯式 FOR ... 鎖定選項的參考資料表上取得 ACCESS SHARE 鎖定)。

ROW EXCLUSIVE (RowExclusiveLock)

SHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 鎖定模式衝突。

指令 UPDATEDELETEINSERTMERGE 在目標資料表上取得此鎖定模式(除了在任何其他參考資料表上取得 ACCESS SHARE 鎖定)。通常,任何修改資料表中資料的指令都將取得此鎖定模式。

SHARE UPDATE EXCLUSIVE (ShareUpdateExclusiveLock)

SHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 鎖定模式衝突。此模式保護資料表,防止並行的 schema 變更和 VACUUM 執行。

VACUUM(不含 FULL)、ANALYZECREATE INDEX CONCURRENTLYCREATE STATISTICSCOMMENT ONREINDEX CONCURRENTLY 以及某些 ALTER INDEXALTER TABLE 變體取得(完整細節請參閱這些指令的文件)。

SHARE (ShareLock)

ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 鎖定模式衝突。此模式保護資料表,防止並行的資料變更。

CREATE INDEX(不含 CONCURRENTLY)取得。

SHARE ROW EXCLUSIVE (ShareRowExclusiveLock)

ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 鎖定模式衝突。此模式保護資料表,防止並行的資料變更,且具有互斥性,因此一次只能有一個 session 持有它。

CREATE TRIGGER 和某些形式的 ALTER TABLE 取得。

EXCLUSIVE (ExclusiveLock)

ROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 鎖定模式衝突。此模式僅允許並行的 ACCESS SHARE 鎖定,也就是說,只有資料表的讀取可以與持有此鎖定模式的交易並行進行。

REFRESH MATERIALIZED VIEW CONCURRENTLY 取得。

ACCESS EXCLUSIVE (AccessExclusiveLock)

與所有模式的鎖定衝突(ACCESS SHAREROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE)。此模式保證持有者是唯一以任何方式存取資料表的交易。

DROP TABLETRUNCATEREINDEXCLUSTERVACUUM FULLREFRESH MATERIALIZED VIEW(不含 CONCURRENTLY)指令取得。許多形式的 ALTER INDEXALTER TABLE 也會在此層級取得鎖定。這也是 LOCK TABLE 陳述式未明確指定模式時的預設鎖定模式。

提示

只有 ACCESS EXCLUSIVE 鎖定會阻止 SELECT(不含 FOR UPDATE/SHARE)陳述式。

一旦取得鎖定,通常會持有到交易結束。但是,如果在建立儲存點後取得鎖定,則在回滾到儲存點時,會立即釋放鎖定。這與 ROLLBACK 取消自儲存點以來所有指令效果的原則一致。對於在 PL/pgSQL 例外狀況區塊中取得的鎖定也是如此:從區塊中發生的錯誤會釋放在其中取得的鎖定。

表 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 以取得完整的列級鎖定衝突表。請注意,一個交易可以在同一列上持有衝突的鎖定,即使是在不同的子交易中也是如此;但除此之外,兩個交易永遠不能在同一列上持有衝突的鎖定。列級鎖定不會影響資料查詢;它們只會阻止對同一列的寫入者和鎖定者。列級鎖定會在交易結束時或儲存點回滾期間釋放,就像資料表層級的鎖定一樣。

列級鎖定模式

FOR UPDATE

FOR UPDATE 會導致 SELECT 陳述式擷取的列被鎖定,如同要進行更新一樣。這可以防止其他交易在目前交易結束之前鎖定、修改或刪除它們。也就是說,其他嘗試 UPDATEDELETESELECT FOR UPDATESELECT FOR NO KEY UPDATESELECT FOR SHARESELECT FOR KEY SHARE 這些列的交易將會被阻止,直到目前交易結束;相反地,SELECT FOR UPDATE 將會等待一個同時進行的交易,該交易已在同一列上執行了任何這些指令,然後將會鎖定並傳回更新後的列(如果該列已被刪除,則不傳回任何列)。但是,在 REPEATABLE READSERIALIZABLE 交易中,如果要鎖定的列自交易開始以來已發生變更,則會拋出錯誤。有關更多討論,請參閱 Section 13.4

FOR UPDATE 鎖定模式也會由對列的任何 DELETE 取得,也會由修改某些欄位值的 UPDATE 取得。目前,被認為用於 UPDATE 情況的欄位集合是那些在其上具有可用於外部鍵的唯一索引的欄位(因此,不考慮部分索引和表達式索引),但將來可能會變更。

FOR NO KEY UPDATE

行為類似於 FOR UPDATE,但取得的鎖定較弱:此鎖定不會阻止嘗試在同一列上取得鎖定的 SELECT FOR KEY SHARE 指令。此鎖定模式也會由任何不取得 FOR UPDATE 鎖定的 UPDATE 取得。

FOR SHARE

其行為與 FOR NO KEY UPDATE 類似,差別在於它在每個檢索到的列上取得的是共享鎖定(shared lock),而不是獨佔鎖定(exclusive lock)。共享鎖定會阻止其他交易對這些列執行 UPDATEDELETESELECT FOR UPDATESELECT FOR NO KEY UPDATE,但不會阻止它們執行 SELECT FOR SHARESELECT FOR KEY SHARE

FOR KEY SHARE

其行為與 FOR SHARE 類似,差別在於鎖定強度較弱:SELECT FOR UPDATE 會被阻止,但 SELECT FOR NO KEY UPDATE 不會。鍵共享鎖定會阻止其他交易執行 DELETE 或任何會變更鍵值的 UPDATE,但不會阻止其他 UPDATE,也不會阻止 SELECT FOR NO KEY UPDATESELECT FOR SHARESELECT FOR KEY SHARE

PostgreSQL 不會在記憶體中記住任何關於已修改列的資訊,因此一次鎖定的列數沒有限制。然而,鎖定一列可能會導致磁碟寫入,例如,SELECT FOR UPDATE 會修改選定的列以標記它們已被鎖定,因此會導致磁碟寫入。

表 13.3. 衝突的列級鎖定

請求的鎖定模式 目前的鎖定模式
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. 頁面級鎖定 #

除了表和列鎖定之外,頁面級共享/獨佔鎖定也用於控制對共享緩衝池中表格頁面的讀/寫存取。這些鎖定會在檢索或更新列後立即釋放。應用程式開發人員通常不需要關注頁面級鎖定,但在此為完整性起見提及它們。

13.3.4. 死鎖 #

使用明確的鎖定可能會增加死鎖的可能性,其中兩個(或多個)交易各自持有對方想要的鎖定。例如,如果交易 1 取得表 A 的獨佔鎖定,然後嘗試取得表 B 的獨佔鎖定,而交易 2 已經獨佔鎖定表 B,現在想要表 A 的獨佔鎖定,那麼任何一方都無法繼續。PostgreSQL 會自動偵測死鎖情況,並通過中止其中一個相關交易來解決它們,允許其他交易完成。(哪個交易會被中止很難預測,不應該依賴它。)

請注意,死鎖也可能因列級鎖定而發生(因此,即使未使用明確鎖定,它們也可能發生)。考慮兩個並行交易修改表格的情況。第一個交易執行

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

這會在具有指定帳號的列上取得列級鎖定。然後,第二個交易執行

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

第一個 UPDATE 陳述式成功地在指定的列上取得列級鎖定,因此它成功地更新了該列。然而,第二個 UPDATE 陳述式發現它試圖更新的列已經被鎖定,因此它會等待取得鎖定的交易完成。交易二現在正在等待交易一完成後才能繼續執行。現在,交易一執行

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

交易一嘗試在指定的列上取得列級鎖定,但它無法做到:交易二已經持有這樣的鎖定。因此它等待交易二完成。因此,交易一被交易二阻塞,而交易二被交易一阻塞:死鎖情況。PostgreSQL 將偵測到這種情況並中止其中一個交易。

防止死鎖的最佳方法通常是避免它們,方法是確保使用資料庫的所有應用程式都以一致的順序取得多個物件上的鎖定。在上面的範例中,如果兩個交易都以相同的順序更新了這些列,就不會發生死鎖。還應確保在交易中取得物件的第一個鎖定是該物件所需的最嚴格模式。如果無法提前驗證這一點,那麼可以通過重試因死鎖而中止的交易來即時處理死鎖。

只要沒有偵測到死鎖情況,尋求表級或列級鎖定的交易將無限期地等待衝突的鎖定被釋放。這意味著應用程式長時間保持交易開啟(例如,在等待使用者輸入時)是一個壞主意。

13.3.5. 建議鎖定 #

PostgreSQL 提供了一種建立具有應用程式定義含義的鎖定的方法。這些被稱為建議鎖定,因為系統不強制使用它們 —— 這取決於應用程式是否正確使用它們。建議鎖定對於不適合 MVCC 模型的鎖定策略很有用。例如,建議鎖定的一個常見用途是模擬所謂的平面檔案資料管理系統的悲觀鎖定策略。雖然可以使用儲存在表格中的標誌來達到相同的目的,但建議鎖定更快,避免了表格膨脹,並且在會話結束時會自動被伺服器清除。

PostgreSQL 中有兩種方法可以取得建議鎖定:在會話級別或在交易級別。一旦在會話級別取得,建議鎖定將一直保持到顯式釋放或會話結束。與標準鎖定請求不同,會話級別的建議鎖定請求不遵守交易語義:在稍後回滾的交易期間取得的鎖定在回滾後仍將保持,同樣,即使呼叫交易稍後失敗,解鎖也是有效的。鎖定可以被其擁有的進程多次取得;對於每個已完成的鎖定請求,必須有一個相應的解鎖請求,然後才能真正釋放鎖定。另一方面,交易級別的鎖定請求的行為更像常規鎖定請求:它們在交易結束時自動釋放,並且沒有顯式的解鎖操作。對於建議鎖定的短期使用,此行為通常比會話級別的行為更方便。同一個建議鎖定標識符的會話級別和交易級別鎖定請求將以預期的方式相互阻塞。如果一個會話已經持有給定的建議鎖定,則它發出的額外請求總是會成功,即使其他會話正在等待該鎖定;無論現有鎖定持有和新請求是在會話級別還是交易級別,此聲明都成立。

PostgreSQL 中的所有鎖定一樣,可以在 pg_locks 系統視圖中找到任何會話當前持有的建議鎖定的完整列表。

建議鎖定和常規鎖定都儲存在共享記憶體池中,該記憶體池的大小由配置變數 max_locks_per_transactionmax_connections 定義。必須小心不要耗盡此記憶體,否則伺服器將無法授予任何鎖定。這對伺服器可授予的建議鎖定數量施加了上限,通常在數萬到數十萬之間,具體取決於伺服器的配置方式。

在某些情況下,使用建議鎖定方法,尤其是在涉及明確排序和 LIMIT 子句的查詢中,必須小心控制由於 SQL 表達式的評估順序而取得的鎖定。例如

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

在上述查詢中,第二種形式比較危險,因為無法保證 LIMIT 在鎖定函數執行之前被應用。 這可能導致取得一些應用程式並未預期的鎖,因此將無法釋放(直到會話結束)。 從應用程式的角度來看,這些鎖會是懸而未決的,儘管仍然可以在 pg_locks 中看到。

用於操作諮詢鎖的函數在第 9.28.10 節中描述。

提交更正

如果您在文件中發現任何不正確、與特定功能的使用經驗不符或需要進一步澄清的地方,請使用此表單回報文件問題。