12.3. 明確鎖定
PostgreSQL 提供了各種各樣的鎖模式用於控制對錶中的數據的併發訪問。 這些模式可以用於在 MVCC 無法給出期望的行爲的時候用於應用控制的鎖定。 同樣,大多數 PostgreSQL 命令自動施加恰當的鎖以保證被引用的表在命令執行的時候不會以一種不兼容的方式被刪除或者修改。 (比如,在存在其它併發操作的時候,ALTER TABLE 是不能在同一個表上面執行的。)
要檢查一列當前數據庫服務器里正在持有的鎖,我們可以使用系統視圖 pg_locks (Section 43.32 )。有關監控鎖管理器子系統的狀態的更多信息,請參考 Chapter 23 。
12.3.1. 表級鎖
下面的列表顯示了可用的鎖模式和它們被 PostgreSQL 自動使用的環境。 請注意所有這些鎖模式都是表級鎖,即使它們的名字包含單詞 "row" ;這些鎖模式的名稱是歷史造成的。 從某種角度而言,這些名字反應了每種鎖模式的典型用法 --- 但是語意都是一樣的。 兩種鎖模式之間真正的區別是它們有着不同的衝突鎖集合。 兩個事務在同一時刻不能在同一個表上持有相互衝突的鎖。 (不過,一個事務決不會和自身衝突。比如,它可以在一個表上請求 ACCESS EXCLUSIVE 然後稍後的時候請求 ACCESS SHARE 。) 非衝突鎖模式可以由許多事務併發地持有。 請特別注意有些鎖模式是自衝突的(比如,在任意時刻 ACCESS EXCLUSIVE 模式就不能夠被多個事務擁有) 而其它地都不是自衝突的(比如,ACCESS SHARE 可以被多個事務持有)。 一旦請求到了某種鎖,那麼該鎖模式將持續到事務結束。
表級鎖模式
- ACCESS SHARE
-
只與 ACCESS EXCLUSIVE 衝突。
SELECT 和 ANALYZE 命令在被引用的表上請求一個這種鎖。 通常,任何只讀取表而不修改它的命令都請求這種鎖模式。
- ROW SHARE
-
與EXCLUSIVE 和ACCESS EXCLUSIVE 模式衝突。
SELECT FOR UPDATE 命令在目標表上需要一個這樣模式的鎖(加上在所有被引用但沒有 FOR UPDATE 的表上的 ACCESS SHARE 鎖)。
- ROW EXCLUSIVE
-
與 SHARE ,SHARE ROW EXCLUSIVE , EXCLUSIVE 和 ACCESS EXCLUSIVE 模式衝突。
命令 UPDATE ,DELETE , 和 INSERT 自動請求這個鎖模式。 (加上所有其它被引用的表上的 ACCESS SHARE 鎖)。 通常,這種鎖將被任何修改表中數據的查詢請求。
- SHARE UPDATE EXCLUSIVE
-
和 SHARE UPDATE EXCLUSIVE ,SHARE , SHARE ROW EXCLUSIVE ,EXCLUSIVE , 和 ACCESS EXCLUSIVE 模式衝突。 這個模式保護一個表不被併發模式改變和 VACUUM 。
VACUUM (不帶 FULL 選項)請求這樣的鎖。
- SHARE
-
與 ROW EXCLUSIVE ,SHARE UPDATE EXCLUSIVE , SHARE ROW EXCLUSIVE ,EXCLUSIVE 和 ACCESS EXCLUSIVE 模式衝突。 這個模式避免表的併發數據修改。
CREATE INDEX 語句要求這樣的鎖模式。
- SHARE ROW EXCLUSIVE
-
與 ROW EXCLUSIVE , SHARE UPDATE EXCLUSIVE ,SHARE , SHARE ROW EXCLUSIVE ,EXCLUSIVE , 和 ACCESS EXCLUSIVE 模式衝突。
任何 PostgreSQL 命令都不會自動請求這樣的鎖模式。
- EXCLUSIVE LOCK
-
與 ROW SHARE ,ROW EXCLUSIVE , SHARE UPDATE EXCLUSIVE , SHARE ,SHARE ROW EXCLUSIVE , EXCLUSIVE 和 ACCESS EXCLUSIVE 模式衝突。 這個模式只允許併發 ACCESS SHARE 鎖,也就是說, 只有對錶的讀動作可以和持有這個鎖模式的事務並行執行。
任何 PostgreSQL 命令都不會自動請求這樣的鎖模式.
- ACCESS EXCLUSIVE
-
與所有模式衝突。 ( ACCESS SHARE , ROW SHARE , ROW EXCLUSIVE , SHARE UPDATE EXCLUSIVE , SHARE , SHARE ROW EXCLUSIVE , EXCLUSIVE , 和 ACCESS EXCLUSIVE ). 這個模式保證其所有者(事務)是可以用任意方式訪問該表的唯一事務。
ALTER TABLE , DROP TABLE ,REINDEX ,CLUSTER 和 VACUUM 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 將偵測這樣的條件並退出其中一個事務。
防止死鎖的最好方法通常是保證所有使用一個數據庫的應用都以一致的順序在多個對象上請求鎖定。 這也是前面的死鎖例子的死鎖原因:如果兩個事務以同樣的順序更新那些行,那麼就不會發生死鎖。 我們也要保證在一個對象上請求的第一個鎖是該對象需要的最高的鎖模式。 如果我們無法提前核實這些問題,那麼我們可以通過在現場重新嘗試因死鎖而退出的事務的方法來處理。
只要沒有檢測到死鎖條件,一個等待表級鎖或者行級鎖的事務將等待衝突鎖的釋放不確定的時間。 這就意味着一個應用持有打開的事務時間太長可不是什麼好事情(比如鎖,等待用戶輸入)。