PostgreSQL併發控制
基於PostgreSQL 9.4
9.3中文文件:
9.4英文文件:
MVCC、事務、事務隔離級別:
四、顯式鎖定
PostgreSQL提供了多種鎖模式用於控制對錶中資料的併發訪問。這些模式可以用於在MVCC無法給出期望行為的場合。同樣,大多數PostgreSQL命令自動施加恰當的鎖以保證被引用的表在命令的執行過程中不會以一種不相容的方式被刪除或者修改。 比如,在存在其它併發操作的時候,TRUNCATE是不能在同一個表上面執行的。
要檢查資料庫伺服器裡所有當前正在被持有的鎖,可以使用系統檢視。有關監控鎖管理器子系統狀態的更多資訊,請參考章。
3.1表級鎖:
下面的列表顯示了可用的鎖模式和它們被PostgreSQL自動使用的場合。你也可以用命令明確獲取這些鎖。兩個事務在同一時刻不能在同一個表上持有相互衝突的鎖。不過,一個事務決不會和自身衝突。比如,它可以在一個表上請求ACCESS EXCLUSIVE然後接著請求 ACCESS SHARE。 非衝突鎖模式可以被許多事務同時持有。請特別注意有些鎖模式是自衝突的(比如,在任意時刻ACCESS EXCLUSIVE模式就不能夠被多個事務擁有), 但其它鎖模式都不是自衝突的(比如,ACCESS SHARE可以被多個事務持有)。
表級鎖LOCK TABLE命令語法:
(中文文件:)
LOCK [ TABLE ] [ ONLY ] name [ * ] [, ...] [ IN lockmode MODE ] [ NOWAIT ] 這裡的lockmode可以是下列之一:
ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE
| SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE
下面是可用的鎖模式和它們被PostgreSQL自動使用的場合。你也可以用命令明確獲取這些鎖。請注意所有這些鎖模式都是表級鎖,即使它們的名字包含"row"單詞(這些名稱是歷史遺產)。從某種角度而言,這些名字反應了每種鎖模式的典型用法—但是語意卻都是一樣的。兩種鎖模式之間真正的區別是它們有著不同的衝突鎖集合(參見)。 兩個事務在同一時刻不能在同一個表上持有相互衝突的鎖。不過,一個事務決不會和自身衝突。比如,它可以在一個表上請求ACCESS EXCLUSIVE然後接著請求 ACCESS SHARE。非衝突鎖模式可以被許多事務同時持有。請特別注意有些鎖模式是自衝突的(比如,在任意時刻ACCESS EXCLUSIVE模式就不能夠被多個事務擁有),但其它鎖模式都不是自衝突的(比如,ACCESS SHARE可以被多個事務持有)。
8大表級鎖模式:
ACCESS SHARE:只與ACCESS EXCLUSIVE衝突。 SELECT命令在引用的表上請求這個鎖。通常,任何只讀取表而不修改它的命令都請求這種鎖模式。
--事務1
BEGIN;
--給表lyy上access share鎖
LOCK TABLE lyy IN ACCESS SHAER MODE;
--與SELECT * FROM lyy;產生相同型別的鎖
--事務2:
BGEIN;
--給表lyy上access exclusive鎖.
LOCK TABLE lyy IN ACCESS EXCLUSIVE MODE;
--與DROP TABLE lyy或者其他DDL操作產生相同型別的鎖。
--以上操作被阻塞,因為產生了鎖衝突
ROW SHARE:與EXCLUSIVE和ACCESS EXCLUSIVE鎖模式衝突。SELECT FOR UPDATE和SELECT FOR SHARE命令在目標表上需要一個這樣模式的鎖 (加上在所有被引用但沒有ACCESS SHARE的表上的FOR UPDATE/FOR 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選項),ANALYZE,CREATE INDEX CONCURRENTLY和ALTER TABLE請求這樣的鎖。
SHARE:與ROW EXCLUSIVE,SHARE UPDATE EXCLUSIVE,SHARE ROW EXCLUSIVE,EXCLUSIVE和ACCESS EXCLUSIVE鎖模式衝突。這個模式避免表的併發資料修改。CREATE INDEX(不帶CONCURRENTLY選項)語句要求這樣的鎖模式。
SHARE ROW EXCLUSIVE:與ROW EXCLUSIVE,SHARE UPDATE EXCLUSIVE,SHARE,SHARE ROW EXCLUSIVE,EXCLUSIVE和ACCESS EXCLUSIVE鎖模式衝突。 這個模式避免表的併發資料修改。 並且是自我排斥的,因此每次只有一個會話可以擁有它。 任何PostgreSQL命令都不會自動請求這個鎖模式。
EXCLUSIVE:與ROW SHARE,ROW EXCLUSIVE,SHARE UPDATE EXCLUSIVE,SHARE,SHARE ROW EXCLUSIVE,EXCLUSIVE和ACCESS EXCLUSIVE鎖模式衝突。這個模式只允許併發ACCESS SHARE鎖,也就是說,只有對錶的讀動作可以和持有這個鎖模式的事務併發執行。 任何PostgreSQL命令都不會在使用者表上自動請求這個鎖模式。
ACCESS EXCLUSIVE:與所有模式衝突(8大模式,也包括自身型別)。這個模式保證其所有者(事務)是可以訪問該表的唯一事務。 ALTER TABLE,DROP TABLE,TRUNCATE,REINDEX,CLUSTER和VACUUM FULL命令要求這樣的鎖。多種形式的ALTER TABLE也要求一個該級別的鎖 (參見 ). 在LOCK TABLE命令沒有明確宣告需要的鎖模式時,ACCESS EXCLUSIVE是預設表鎖模式。
注意: 只有ACCESS EXCLUSIVE阻塞SELECT(不包含FOR UPDATE/SHARE語句)。
--事務1
BEGIN;
LOCK TABLE lyy in access exclusive mode;
--事務2
BEGIN;
SELECT * FROM lyy;
--與LOCK TABLE lyy IN ACCESS SHAER MODE;都產生ACCESS SHARE鎖
--SELECT操作阻塞,因為與access exclusive產生了鎖衝突
一旦請求已獲得某種鎖,那麼該鎖模式將持續到事務結束。但是如果在建立儲存點之後才獲得鎖,那麼在回滾到這個儲存點的時候將立即釋放所有該儲存點之後獲得的鎖。這與ROLLBACK取消所有儲存點之後對錶的影響的原則一致。同樣的原則也適用於PL/pgSQL異常塊中獲得的鎖:一個跳出塊的錯誤將釋放在塊中獲得的鎖。
--事務1
postgres=# begin;
BEGIN
postgres=# LOCK TABLE lyy IN ACCESS SHARE MODE;
LOCK TABLE
postgres=# savepoint svp1;
SAVEPOINT
postgres=# LOCK TABLE lyy IN ACCESS exclusive MODE;
LOCK TABLE
--事務2
postgres=# begin;
BEGIN
postgres=# select * from lyy;--此時操作被阻塞,因為此時是ACCESS exclusive鎖
--事務1
postgres=# rollback to savepoint svp1;//此時是access share鎖
ROLLBACK
--事務2
此時select * from lyy返回查詢結果.//access share鎖不阻塞SELECT操作。
衝突鎖模式對比表
Requested Lock Mode | Current Lock Mode | |||||||
---|---|---|---|---|---|---|---|---|
ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE | SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE | |
ACCESS SHARE | X | |||||||
ROW SHARE | X | X | ||||||
ROW EXCLUSIVE | X | X | X | X | ||||
SHARE UPDATE EXCLUSIVE | X | X | X | X | X | |||
SHARE | X | X | X | X | X | |||
SHARE ROW EXCLUSIVE | X | X | X | X | X | X | ||
EXCLUSIVE | X | X | X | X | X | X | X | |
ACCESS EXCLUSIVE | X | X | X | X | X | X | X | X |
備註: 上圖是Postgresql 表級鎖的各種衝突模式對照表,‘X’表示衝突項。
3.2行級鎖:
行級鎖可以是排他的或者是共享的。特定行上的排他行級鎖是在行被更新的時候自動請求的。 該鎖一直保持到事務提交或者回滾。行級鎖不影響對資料的查詢,它們只阻塞對同一行的寫入。
行級鎖(SELECT .. FOR ..)命令語法
(中文文件:)
FOR UPDATE,FOR NO KEY UPDATE,FOR SHARE和FOR KEY SHARE是鎖定子句;他們影響SELECT如何從表中鎖定行作為獲得的行。鎖定子句的一般形式:
FOR lock_strength [ OF table_name [, ...] ] [ NOWAIT ]
這裡的lock_strength可以是下列之一:
UPDATE
NO KEY UPDATE
SHARE
KEY SHARE
4個行級鎖強度
FOR UPDATE:令那些被SELECT檢索出來的行被鎖住,就像在更新一樣。這樣就避免它們在當前事務結束前被其它事務修改或者刪除;也就是說, 其它企圖UPDATE,DELETE,SELECT FOR UPDATE,SELECT FOR NO KEY UPDATE,SELECT FOR SHARE或SELECT FOR KEY SHARE這些行的事務將被阻塞,直到當前事務結束。同樣, 如果一個來自其它事務的UPDATE,DELETE,SELECT FOR UPDATE已經鎖住了某個或某些選定的行,SELECT FOR UPDATE將等到那些事務結束,並且將隨後鎖住並返回更新的行(或者不返回行,如果行已經被刪除)。但是,在REPEATABLE READ或SERIALIZABLE事務內部,如果在事務開始時要被鎖定的行已經改變了,那麼將丟擲一個錯誤。更多的討論參閱。
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。
為了避免操作等待其它事務提交,使用NOWAIT選項。如果被選擇的行不能立即被鎖住, 那麼語句將會立即彙報一個錯誤,而不是等待。請注意,NOWAIT只適用於行級別的鎖, 要求的表級鎖ROW SHARE仍然以通常的方法進行(參閱)。 如果需要申請表級別的鎖同時又不等待,那麼你可以使用的 NOWAIT選項。
舉例說明:
準備表和資料:
CREATE TABLE lyy(id int primary key, name varchar);
INSERT INTO lyy values(1,'a1'),(2,'a2'),(3,'a3'),(4,'a4');
--for update
事務1:
BEGIN;
SELECT * FROM lyy WHERE id<3 FOR UPDATE;
事務2:
BEGIN;
UPDATE lyy SET name='aa' WHERE id=2;
--此時id=2的行已被FOR UPDATE鎖定,所以update操作阻塞,直到事務1,提交之後,才能執行。
--for no key update
事務1:
BEGIN;
SELECT * FROM lyy WHERE id=1 FOR NO KEY UPDATE;
--在對行1的鎖定效果上,等價於
update lyy set id=2 where id=2;
事務1:
BEGIN;
SELECT * FROM lyy WHERE id=1 FOR KEY SHARE;
--該行可以執行,因為雖然id=1的行已被鎖定,但是FOR NO KEY UPDATE不阻塞SELECT ... FOR KEY SHARE;
--for share
事務1:
BGEIN;
SELECT * FROM lyy WHERE id=1 FOR SHARE;
事務2:
BEGIN;
SELECT * FROM lyy WHERE id=1 FOR [KEY] SHARE;--可以執行,因為該行的FOR SHARE鎖,不阻塞FOR [KEY] SHARE;
UPDATE lyy set name='sss' WHERE id=1; --阻塞,
--for key share
事務1:
BGEIN;
SELECT * FROM lyy WHERE id=1 FOR KEY SHARE;
事務2:
BEGIN;
UPDATE lyy SET id=222 WHERE id=2; --阻塞,for key share 阻塞鍵值欄位的update操作;
UPDATE lyy SET name='coco' WHERE id=2; --正常執行,for update share不阻塞非鍵值欄位的update操作。
3.3頁級鎖
除了表級鎖和行級鎖,頁級別的共享/排他鎖用來控制中對錶頁的讀/寫訪問(在共享快取池)。一個行被抓去或者更新後,這些鎖會迅速被釋放。應用開發者通常不需要關心頁級鎖,在此處提及頁級鎖是為了鎖機制介紹的完整性。
3.4死鎖:
顯式鎖定的使用可能會增加死鎖的可能性。
死鎖是指兩個(或兩個以上)事務相互持有對方期待的鎖,如果沒有其他機制,這些事務都將無法進行下去。
避免死鎖的方法
防止死鎖的最好方法通常是保持所有使用同一個資料庫的應用都能與相同的順序在多個物件上請求排它鎖。
由於資料庫可以自動檢測出死鎖,所以應用也可以通過補貨死鎖異常來處理思索。但這不是一個很好的方法,因為資料庫檢測死鎖需要一些代價,可能會導致應用程式過久持有排它鎖,從而導致系統的併發處理能力下降。
排它鎖持有的時間越長,就越容易導致死鎖,所以在程式設計時,要儘量短的持有排它鎖。
阻塞與死鎖的區別
參考:
資料庫阻塞的現象: 第一個連線佔有資源沒有釋放,而第二個連線需要獲取這個資源。如果第一個連線沒有提交或者回滾,第二個連線會一直等待下去,直到第一個連線釋放該資源為止。對於阻塞,資料庫無法處理,所以對資料庫操作要及時地提交或者回滾。
資料庫死鎖的現象:第一個連線佔有資源沒有釋放,準備獲取第二個連線所佔用的資源,而第二個連線佔有資源沒有釋放,準備獲取第一個連線所佔用的資源。這種互相佔有對方需要獲取的資源的現象叫做死鎖。對於死鎖,資料庫處理方法:犧牲一個連線,保證另外一個連線成功執行。
3.5諮詢鎖:
PostgreSQL允許建立由應用定義其含義的鎖。這種鎖被稱為諮詢鎖, 因為系統並不強迫其使用— 而是由應用來保證其被恰當的使用。 諮詢鎖可用於 MVCC 難以實現的鎖定策略。 比如,諮詢鎖一般用於模擬常見於"平面檔案"資料管理系統的悲觀鎖策略。 雖然可以用儲存在表中的一個特定標誌達到同樣的目的,但是使用諮詢鎖更快,還可以避免表臃腫, 更可以在會話結束的時候由系統自動執行清理工作。