PostgreSQL 提供各種鎖定模式來控制對錶中資料的併發訪問。在以下情況下,這些模式可用於應用程式控制的鎖定:MVCC不產生期望的行為。此外,大多數 PostgreSQL 命令會自動獲取適當模式的鎖,以確保在命令執行期間,被引用的表不會被刪除或以不相容的方式修改。(例如,TRUNCATE
不能安全地與其他操作在同一表上併發執行,因此它會獲取表上的 ACCESS EXCLUSIVE
鎖來強制執行這一點。)
要檢查資料庫伺服器中當前未完成鎖的列表,請使用 pg_locks
系統檢視。有關監控鎖管理器子系統狀態的更多資訊,請參閱 第 27 章。
PostgreSQL 自動使用的可用鎖定模式和上下文列表如下。您也可以使用 LOCK 命令顯式獲取這些鎖中的任何一個。請記住,所有這些鎖定模式都是表級鎖,即使名稱中包含“row”一詞;鎖定模式的名稱是歷史遺留的。在某種程度上,這些名稱反映了每種鎖定模式的典型用法 — 但語義都是相同的。唯一真正的區別在於每種鎖定模式與其他鎖定模式的衝突集(參見 表 13.2)。兩個事務不能在同一表上同時持有衝突模式的鎖。(但是,一個事務永遠不會與自身衝突。例如,它可能獲取 ACCESS EXCLUSIVE
鎖,然後又在同一個表上獲取 ACCESS SHARE
鎖。)非衝突鎖定模式可以被多個事務併發持有。請特別注意,某些鎖定模式是自衝突的(例如,一個 ACCESS EXCLUSIVE
鎖一次只能由一個事務持有),而其他模式則不是自衝突的(例如,一個 ACCESS SHARE
鎖可以被多個事務持有)。
表級鎖定模式
ACCESS SHARE
(AccessShareLock
)僅與 ACCESS EXCLUSIVE
鎖定模式衝突。
SELECT
命令在引用的表上獲取此模式的鎖。通常,任何僅 讀取 表而不修改它的查詢都會獲取此鎖定模式。
ROW SHARE
(RowShareLock
)與 EXCLUSIVE
和 ACCESS EXCLUSIVE
鎖定模式衝突。
SELECT
命令在所有指定了 FOR UPDATE
、FOR NO KEY UPDATE
、FOR SHARE
或 FOR KEY SHARE
選項的表上獲取此模式的鎖(此外,在任何沒有顯式 FOR ...
鎖定選項的其他引用表上也會獲取 ACCESS SHARE
鎖)。
ROW EXCLUSIVE
(RowExclusiveLock
)與 SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
鎖定模式衝突。
UPDATE
、DELETE
、INSERT
和 MERGE
命令在目標表上獲取此鎖定模式(此外,在任何其他引用的表上也會獲取 ACCESS SHARE
鎖)。通常,任何 修改表資料 的命令都會獲取此鎖定模式。
SHARE UPDATE EXCLUSIVE
(ShareUpdateExclusiveLock
)與 SHARE UPDATE EXCLUSIVE
、SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
鎖定模式衝突。此模式保護表免受併發模式更改和 VACUUM
執行的影響。
由 VACUUM
(不帶 FULL
)、ANALYZE
、CREATE INDEX CONCURRENTLY
、CREATE STATISTICS
、COMMENT ON
、REINDEX CONCURRENTLY
以及某些 ALTER INDEX
和 ALTER TABLE
變體獲取(有關詳細資訊,請參閱這些命令的文件)。
SHARE
(ShareLock
)與 ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
鎖定模式衝突。此模式保護表免受併發資料更改的影響。
由 CREATE INDEX
(不帶 CONCURRENTLY
)獲取。
SHARE ROW EXCLUSIVE
(ShareRowExclusiveLock
)與 ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
鎖定模式衝突。此模式保護表免受併發資料更改的影響,並且是自排他的,一次只有一個會話可以持有它。
由 CREATE TRIGGER
和某些形式的 ALTER TABLE
獲取。
EXCLUSIVE
(ExclusiveLock
)與 ROW SHARE
、ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
鎖定模式衝突。此模式僅允許併發 ACCESS SHARE
鎖,即,只有對錶的讀取操作可以與持有此鎖定模式的事務並行執行。
由 REFRESH MATERIALIZED VIEW CONCURRENTLY
獲取。
ACCESS EXCLUSIVE
(AccessExclusiveLock
)與所有模式的鎖(ACCESS SHARE
、ROW SHARE
、ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
)衝突。此模式保證持有者是唯一以任何方式訪問表的事務。
由 DROP TABLE
、TRUNCATE
、REINDEX
、CLUSTER
、VACUUM FULL
和 REFRESH MATERIALIZED VIEW
(不帶 CONCURRENTLY
)命令獲取。許多形式的 ALTER INDEX
和 ALTER 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 |
除了表級鎖之外,還有行級鎖,它們在下面列出了 PostgreSQL 自動使用的上下文。有關行級鎖衝突的完整表格,請參閱 表 13.3。請注意,一個事務可以在同一行上持有衝突的鎖,即使是在不同的子事務中;但除此之外,兩個事務永遠不能在同一行上持有衝突的鎖。行級鎖不會影響資料查詢;它們只阻止對同一行的寫入者和鎖定者。行級鎖在事務結束時或在儲存點回滾期間釋放,與表級鎖一樣。
行級鎖定模式
FOR UPDATE
FOR UPDATE
會鎖定 SELECT
語句檢索到的行,就像更新一樣。這可以防止它們被其他事務鎖定、修改或刪除,直到當前事務結束。也就是說,其他嘗試對這些行執行 UPDATE
、DELETE
、SELECT FOR UPDATE
、SELECT FOR NO KEY UPDATE
、SELECT FOR SHARE
或 SELECT FOR KEY SHARE
的事務將被阻塞,直到當前事務結束;反之,SELECT FOR UPDATE
將等待併發事務在同一行上執行了任何這些命令,然後鎖定並返回更新後的行(如果行已被刪除,則不返回行)。但是,在 REPEATABLE READ
或 SERIALIZABLE
事務中,如果要鎖定的行自事務開始以來已更改,則會丟擲錯誤。有關更多討論,請參閱 第 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
,但它在每行檢索上獲取共享鎖而不是排他鎖。共享鎖會阻止其他事務在這些行上執行 UPDATE
、DELETE
、SELECT FOR UPDATE
或 SELECT FOR NO KEY UPDATE
,但不會阻止它們執行 SELECT FOR SHARE
或 SELECT FOR KEY SHARE
。
FOR KEY SHARE
行為類似於 FOR SHARE
,但鎖較弱:SELECT FOR UPDATE
會被阻止,但 SELECT FOR NO KEY UPDATE
不會。鍵共享鎖會阻止其他事務執行 DELETE
或任何更改鍵值的 UPDATE
,但不會阻止其他 UPDATE
,也不會阻止 SELECT FOR NO KEY UPDATE
、SELECT FOR SHARE
或 SELECT 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 |
除了表鎖和行鎖之外,還使用頁級共享/排他鎖來控制共享緩衝區池中表頁的讀/寫訪問。這些鎖在獲取或更新行後立即釋放。應用程式開發人員通常不需要關心頁級鎖,但在此提及是為了完整性。
使用顯式鎖定會增加死鎖的可能性,即兩個(或多個)事務各自持有對方想要的鎖。例如,如果事務 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 將檢測到這種情況並中止其中一個事務。
防止死鎖的最佳方法通常是避免它們,確保所有使用資料庫的應用程式以一致的順序獲取多個物件的鎖。在上面的示例中,如果兩個事務都以相同的順序更新了行,就不會發生死鎖。還應該確保在事務中獲取的第一個物件鎖是該物件將需要的最嚴格的模式。如果無法提前驗證這一點,那麼可以透過重試因死鎖而中止的事務來即時處理死鎖。
只要沒有檢測到死鎖情況,請求表級或行級鎖的事務將無限期地等待衝突鎖被釋放。這意味著應用程式長時間保持事務開啟(例如,在等待使用者輸入時)是一個壞主意。
PostgreSQL 提供了一種建立具有應用程式定義含義的鎖的方法。這些被稱為推薦鎖,因為系統不強制使用它們 — 正確使用它們取決於應用程式。推薦鎖對於不適合 MVCC 模型(多版本併發控制)的鎖定策略可能很有用。例如,推薦鎖的常見用途是模擬所謂的““扁平檔案””資料管理系統典型的悲觀鎖定策略。雖然表中的標誌也可以用於此目的,但推薦鎖速度更快,避免了表膨脹,並且伺服器會在會話結束時自動清理。
在 PostgreSQL 中,有兩種獲取推薦鎖的方法:會話級別或事務級別。一旦在會話級別獲取,推薦鎖將一直持有,直到顯式釋放或會話結束。與標準鎖請求不同,會話級別推薦鎖請求不遵循事務語義:在後來被回滾的事務中獲取的鎖在回滾後仍將持有,同樣,即使呼叫事務稍後失敗,解鎖也會生效。一個程序可以多次獲取同一個推薦鎖;對於每個已完成的鎖請求,在鎖實際釋放之前,必須有一個對應的解鎖請求。另一方面,事務級別的鎖請求比常規鎖請求更相似:它們會在事務結束時自動釋放,並且沒有顯式的解鎖操作。對於推薦鎖的短期使用,這種行為通常比會話級別的行為更方便。同一推薦鎖識別符號的會話級別和事務級別鎖請求將以預期的方式相互阻塞。如果一個會話已經持有給定的推薦鎖,它進行的附加請求總是會成功,即使其他會話正在等待該鎖;此陳述適用於現有鎖持有和新請求是在會話級別還是事務級別。
與 PostgreSQL 中的所有鎖一樣,可以在 pg_locks
系統檢視中找到當前由任何會話持有的推薦鎖的完整列表。
推薦鎖和常規鎖都儲存在一個共享記憶體池中,其大小由配置變數 max_locks_per_transaction 和 max_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 節 中描述。
如果您在文件中看到任何不正確、與您在使用該功能時的實際體驗不符或需要進一步澄清的內容,請使用 此表單 來報告文件問題。