12.3. 明確鎖定

PostgreSQL 提供了各種各樣的鎖模式用於控制對資料表中的資料的並發訪問。 這些模式可以用於在 MVCC 無法給出期望的行為的時候用於應用控制的鎖定。 同樣,大多數 PostgreSQL 命令自動施加恰當的鎖以保證被引用的資料表在命令執行的時候不會以一種不兼容的方式被刪除或者修改。 (比如,在存在其它並發操作的時候,ALTER TABLE 是不能在同一個資料表上面執行的。)

要檢查一列目前資料庫伺服器裡正在持有的鎖,我們可以使用系統視圖 pg_locksSection 41.33)。有關監控鎖管理器子系統的狀態的更多訊息,請參考 Chapter 23

12.3.1. 資料表級鎖

下面的列資料表顯示了可用的鎖模式和它們被 PostgreSQL 自動使用的環境。 您也可以用命令 LOCK 明確獲取這些鎖。 請注意所有這些鎖模式都是資料表級鎖,即使它們的名字包含單詞 "row";這些鎖模式的名稱是歷史造成的。 從某種角度而言,這些名字反應了每種鎖模式的典型用法 — 但是語意都是一樣的。 兩種鎖模式之間真正的區別是它們有著不同的衝突鎖集合。 兩個交易在同一時刻不能在同一個資料表上持有相互衝突的鎖。 (不過,一個交易決不會和自身衝突。比如,它可以在一個資料表上請求 ACCESS EXCLUSIVE 然後稍後的時候請求 ACCESS SHARE。) 非衝突鎖模式可以由許多交易並發地持有。 請特別注意有些鎖模式是自衝突的(比如,在任意時刻 ACCESS EXCLUSIVE 模式就不能夠被多個交易擁有) 而其它地都不是自衝突的(比如,ACCESS SHARE 可以被多個交易持有)。 一旦請求到了某種鎖,那麼該鎖模式將持續到交易結束。

資料表級鎖模式

ACCESS SHARE

只與 ACCESS EXCLUSIVE 衝突。

SELECTANALYZE 命令在被引用的資料表上請求一個這種鎖。 通常,任何只讀取資料表而不修改它的命令都請求這種鎖模式。

ROW SHARE

EXCLUSIVEACCESS EXCLUSIVE模式衝突。

SELECT FOR UPDATE 命令在目標資料表上需要一個這樣模式的鎖(加上在所有被引用但沒有 FOR UPDATE 的資料表上的 ACCESS SHARE 鎖)。

ROW EXCLUSIVE

SHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 模式衝突。

命令 UPDATEDELETE, 和 INSERT 自動請求這個鎖模式。 (加上所有其它被引用的資料表上的 ACCESS SHARE 鎖)。 通常,這種鎖將被任何修改資料表中資料的查詢請求。

SHARE UPDATE EXCLUSIVE

SHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVE, 和 ACCESS EXCLUSIVE 模式衝突。 這個模式保護一個資料表不被並發模式改變和 VACUUM

VACUUM(不帶 FULL 選項)請求這樣的鎖。

SHARE

ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 模式衝突。 這個模式避免資料表的並發資料修改。

CREATE INDEX 語句要求這樣的鎖模式。

SHARE ROW EXCLUSIVE

ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVE, 和 ACCESS EXCLUSIVE 模式衝突。

任何 PostgreSQL 命令都不會自動請求這樣的鎖模式。

EXCLUSIVE LOCK

ROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 模式衝突。 這個模式只允許並發 ACCESS SHARE 鎖,也就是說, 只有對資料表的讀動作可以和持有這個鎖模式的交易並行執行。

任何 PostgreSQL 命令都不會自動請求這樣的鎖模式.

ACCESS EXCLUSIVE

與所有模式衝突。 ( ACCESS SHARE, ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE, 和 ACCESS EXCLUSIVE). 這個模式保證其所有者(交易)是可以用任意方式訪問該資料表的唯一交易。

ALTER TABLEDROP TABLEREINDEXCLUSTERVACUUM FULL 命令要求這樣的鎖。 在 LOCK TABLE 命令沒有明確聲明需要的鎖模式時,它也是預設鎖模式。

提示: 只有 ACCESS EXCLUSIVE 阻塞 SELECT (沒有 FOR UPDATE語句)。

12.3.2. 行級鎖

除了資料表級鎖以外,還有行級鎖。 特定行上的行級鎖是在行被更新的時候自動請求的(或者被刪除時或標記為更新)。 鎖一直保持到交易提交或者回滾。 行級鎖不影響對資料的查詢; 它們只阻塞對同一行的寫入。 要在不修改某行的前提下請求在該行的行級鎖,用 SELECT FOR UPDATE 選取該行。請注意一旦我們請求了特定的行級鎖, 那麼該交易就可以多次對該行進行更新而不用擔心衝突。

PostgreSQL 不會在內存裡保存任何關於已修改行的訊息, 因此對一次鎖定的行數沒有限制。 不過,鎖住一行會導致一次磁盤寫;因此,像 SELECT FOR UPDATE 將修改選中的行以標記它們, 因此會導致磁盤寫。

除了資料表級別的和行級別的鎖以外, 頁面級別的共享/排他銷也用於控制對共享緩衝池中資料表頁面的讀/寫訪問。 這些鎖在抓取或者更新一行後馬上被釋放。 應用程序員通常不需要關心頁級鎖,我們在這裡提到它們只是為了完整。

12.3.3. 死鎖

明確鎖定的使用可能會增加死鎖的可能性, 死鎖是是指兩個(或多個)交易相互持有對方期待的鎖。比如, 如果交易 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 將偵測這樣的條件並退出其中一個交易。

防止死鎖的最好方法通常是保證所有使用一個資料庫的應用都以一致的順序在多個對象上請求鎖定。 在上面的例子裡,如果兩個交易以同樣的順序更新那些行,那麼就不會發生死鎖。 我們也要保證在一個對像上請求的第一個鎖是該對像需要的最高的鎖模式。 如果我們無法提前核實這些問題,那麼我們可以透過在現場重新嘗試因死鎖而退出的交易的方法來處理。

只要沒有檢測到死鎖條件,一個等待資料表級鎖或者行級鎖的交易將等待衝突鎖的釋放不確定的時間。 這就意味著一個應用持有打開的交易時間太長可不是什麼好事情(比如鎖,等待用戶輸入)。