POSTGRESQL 並發控制


http://meidayhxp.blog.163.com/blog/static/117608156201210243837491/

這個內容是官方Doc中的一章,具體是那一版的,還未確認。

 

第九章 並發控制

 

 

本章介紹PostgreSQL的並發控制機制。當兩個或多個用戶同時訪問同一個數據行時,需要使用並發控制機制來維護數據的完整性和一致性。

 

9.1 概述

PostgreSQL使用的是多版本並發控制機制(Multiversion Concurrency Control, MVCC)。多版本並發控制機制的意思是數據庫中的每個事務在查詢數據時,看到的是數據的快照(一個歷史版本),而不是數據的當前狀態。在多版本並發控制機制中,讀操作不用等待寫操作,寫操作不用等待讀操作,只有在兩個事務試圖同時更新同一個數據行時,才會有等待出現。多版本並發控制機制可以減少數據庫中的鎖爭用,減少用戶的等待時間,提高數據庫的性能。

 

PostgreSQL同時也提供了接口讓應用程序顯式對數據進行加鎖操作。這些接口支持表級鎖和行級鎖。此外,還提供了建議鎖(advisory lock)這樣的鎖機制,使得應用程序能夠完全自主地控制得到和釋放鎖的時間。

 

9.2事務的隔離級別

SQL標准定義了四個事務隔離級別,表9-1列出了所有的隔離級別,事務在不同的隔離級別可以中看到的數據是不相同的。

 

9-1. SQL 事務隔離級別

隔離級別

臟讀(dirty read

不可重復讀(nonrepeatable read

影子讀(Phantom Read

Read uncommitted

可能

可能

可能

Read committed

不可能

可能

可能

Repeatable read

不可能

不可能

可能

Serializable

不可能

不可能

不可能

 

臟讀(dirty read

一個事務可以讀取其它還未提交的事務修改的數據。

 

不可重復讀(nonrepeatable read

一個事務重新讀取以前讀過的數據時,發現該數據被修改過。

 

影子讀(phantom read

一個事務兩次執行同一個查詢,發現第二次查詢得到的數據行比第一次查詢得到的數據行多。

 

PostgreSQL只提供了兩種事務隔離級別,分別是Read Committed Serializable。使用命令SET TRANSACTION設置事務的隔離級別。應用程序可以將事務的隔離級別設為Read Uncommitted,但系統會自動將隔離級別設為Read Committed。應用程序也可以將事務的隔離級別設為Repeatable Read,但系統會自動將隔離級別設為Serializable。這樣的規則是符合SQL標准的。

 

9.2.1 Read Committed隔離級別

Read CommittedPostgreSQL的默認隔離級別。如果一個事務運行在這個隔離級別,事務發出的SELECT命令只能看見在SELECT命令開始執行以前提交的數據。同一個事務的兩個先后執行的 SELECT命令可能會看到不同的數據,因為在其它並發執行的事務可能在第一個SELECT命令執行的過程中被提交。另外,事務發出的SELECT命令可以看到該事務以前發出的更新命令(包括INSERTDELETEUPDATE)修改過的數據。

 

命令UPDATE、 DELETESELECT FOR UPDATESELECT FOR SHARE可以看到的數據和SELECT命令是一樣的。如果一個事務執行命令UPDATEDELETESELECT FOR UPDATESELECT FOR SHARE),該命令發現一個數據行R1滿足自己的搜索條件,同時有另外一個事務T2已經鎖住了數據行T1T2可能正在刪除或更新R1),那么T1將進入等待狀態,直到T2執行結束(回滾或提交),T1才能繼續運行,根據T2的執行結果,T1有兩種執行方式:

 

1)如果T2被回滾,T2R1做的更新將被取消,T1會使用R1原來的值繼續運行命令。

2)如果T2被提交,那要分兩種情況:

aT2刪除了數據行R1,那么T1繼續運行時,將忽略R1, R1T1正在執行的命令是不可見的。

bT2修改了數據行R1, 那么T1繼續運行時,T1正在執行的命令將看見R1的新的值,同時會重新檢查R1的新值是否符合該命令的WHERE子句的搜素條件如果符合,將使用R1的新值作為搜索結果,如果不符合,將忽略R1的新值。

 

Read Committed隔離級別值保證一個事務的單個命令看到的數據是一致的,不能保證一個事務發出的所有命令看到的數據都是一致的。

 

Read Committed隔離級別可以滿足大部分應用程序的需要,它使用簡單,比serializable隔離級別要快速。但那些使用復雜的查詢和更新操作的應用可能需要數據庫提供更加嚴格數據的一致性,這種情況下應該使用Serializable隔離級別。

 

9.2.2 Serializable隔離級別

Serializable是最嚴格的隔離級別。在這種隔離級別下,所有並發執行的事務的執行結果和單個事務一個一個地執行的結果是一樣的。在這種隔離級別下運行的應用程序的邏輯要復雜一些,在事務不符合可串行化要求的而被終止的情況下,應用程序應該能夠重新創建被終止的事務並再次請求數據庫執行該事務。

 

Serializable隔離級別下運行的應用程序,一個事務發出的SELECT命令只能看見事務開始運行以前已經提交的數據,看不見在事務開始運行以前沒有提交的數據和在事務執行過程中其它並發執行的事務提交的數據。同一個事務的兩個先后執行的 SELECT命令看到的數據是一樣的。但事務的SELECT命令可以看到該事務以前發出的更新命令(包括INSERTDELETEUPDATE)修改過的數據。

 

命令UPDATE、 DELETESELECT FOR UPDATESELECT FOR SHARE可以看到的數據和SELECT命令是一樣的。這些命令只能看到事務開始運行以前已經提交的數據。但是如果一個事務執行命令UPDATEDELETESELECT FOR UPDATESELECT FOR SHARE),該命令發現一個數據行R1滿足自己的搜索條件,同時有另外一個事務T2已經鎖住了數據行T1T2可能正在刪除或更新R1),那么T1將進入等待狀態,直到T2執行結束(回滾或提交),T1才能繼續運行,根據T2的執行結果,T1有兩種執行方式:

 

1)如果T2被回滾,T2R1做的更新將被取消,T1會使用R1原來的值繼續運行命令。

2)如果T2被提交,而且T2刪除或者更新了R1T1將被回滾,數據庫會發出下面的提示信息:

 

錯誤當前事務運行在可串行化模式下,與其它並發執行的事務沖突,將被回滾。

 

如果應用程序收到上面的錯誤信息,應該終止執行當前事務,並重新從頭開始執行這個事務。注意,只有含有更新操作的事務可能遇到上面的錯誤而被終止,只讀事務永遠都不會被被終止。

 

Serializable隔離級別保證每個事務在執行過程中看到完全一致的數據。但應用程序的邏輯會變得很復雜,多個事務若同時更新同一個數據行,其中有一個事務就可能失敗,應用程序必須重新執行失敗的事務,對數據庫的壓力就會變大。如果事務的更新邏輯非常復雜,導致在Read Committed隔離級別下可能出現不正確的結果,應該使用Serializable隔離級別。通常如果一個事務的幾個連續執行的命令需要看到一樣的數據,就應該使用Serializable隔離級別。

 

9.2.3 Serializable隔離級別和真正的可串行化

 

Serializable隔離級別並沒有保證真正的數學上的可串行化,它只是保證事務在執行時不會出現臟讀、不可重復讀和Phantom Read

 

例如,假設有一個叫mytab的表,它的內容如下:

 class | value 
-------+-------
     1 |    10
     1 |    20
     2 |   100
     2 |   200

 

有一個事務T1:

 

BEGIN

 
        
SELECT SUM(value) FROM mytab WHERE class = 1;
 
        
INSERT INTO  mytab VALUES(2, SUM(value)); 
 --注意SUM(value)表示將第一查詢的結果插入到表中,這不是正確的INSERT命令語法。
 
        
COMMIT

 

 

有另一個事務T2:

 

BEGIN

 

SELECT SUM(value) FROM mytab WHERE class = 2;
INSERT INTO  mytab VALUES(1, SUM(value));  ));  --注意SUM(value)表示將第一查詢的結果插入到表中,這不是正確的INSERT命令語法

 

COMMIT

 

假設T1T2Serializable隔離級別下被執行。

 

如果T1T2並發執行,T1得到的結果是30,同時mytab中會多一個數據行(2,30)T2得到的結果是300,同時mytab中會多一個數據行(1,30)

 

如果T1先執行,T1結束后,T2再執行,T1得到的結果是30,同時mytab中會多一個數據行(2,30)T2得到的結果是330,同時mytab中會多一個數據行(1,330)

 

如果T2先執行,T2結束后,T1再執行,T1得到的結果是330,同時mytab中會多一個數據行(2,330)T2得到的結果是300,同時mytab中會多一個數據行(1,300)

 

從上面的例子可以看出在Serializable隔離級別下,並發執行的事務的執行結果與事務串行執行的結果並不相同。

 

如果想保證真正的數學上的可串行化,數據庫必須使用謂詞鎖(predicate lockSQL Serverrange lock),意思是如果一個事務T1正在執行一個查詢,該查詢的的WHERE子句存在一個條件表達式E1,那么另外一個事務T2 就不能插入或刪除任何滿足E1的數據行。例如,一個事務A正在執行一個查詢SELECT ... WHERE class = 1,另外一個事務就不能插入、更新或刪除任何滿足“class=1”的數據行。只有在A提交以后,B才能進行這樣的操作。

 

實現謂詞鎖的代價非常高,而且事務會經常處理等待的狀態,增加了查詢的響應時間。而且絕大大不部分的應用都不需要數據庫保證真正的數學上的可串行化。所以PostgreSQL並沒有實現謂詞鎖,如果應用程序需要數據庫保證真正的數學上的可串行化,可以使用PostgreSQL提供的命令對數據顯示地進行加鎖操作,后面的小節將會討論如何對數據進行加鎖操作。

 

9.3 顯式加鎖

PostgreSQL提供了多種鎖模式來控制對表中的數據的並發訪問。如果MVCC 不能滿足應用的需求,可以使用這些鎖來實現需要的功能。大部分的數據庫命令在執行時,數據庫會自動鎖住相關的資源,不需要用戶干預。可以查看視圖pg_locks來得到系統中當前的鎖信息。

 

9.3.1 表級鎖

下面列出了數據庫使用的表級鎖的模式和數據庫在什么時候會使用這些鎖。通常數據庫在執行命令時會自動對表加上每個命令需要使用的鎖。用戶也可以使用命令LOCK對表進行加鎖操作。

 

ACCESS SHARE

只與 ACCESS EXCLUSIVE模式沖突。

SELECT 命令會在引用的表上加上這種模式的鎖。 一般來說任何只是讀取一個表中的數據而不修改表中的數據的查詢都會表上加上這種模式的鎖。

 

ROW SHARE

EXCLUSIVE ACCESS EXCLUSIVE 模式沖突。

SELECT FOR UPDATE SELECT FOR SHARE 命令會在引用的表上加上這種模式的鎖(同時會對查詢引用的其它的表加ACCESS SHARE)

 

ROW EXCLUSIVE

SHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 模式沖突。

UPDATEDELETEINSERT命令會在引用的表上加上這種模式的鎖((同時會對查詢引用的其它的表加ACCESS SHARE)。 一般來說任何修改表中的數據的查詢都會表上加上這種模式的鎖。

 

SHARE UPDATE EXCLUSIVE

SHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 模式沖突。這個模式保證其它事務不能更新表的定義信息。VACUUM (不帶FULL), ANALYZECREATE INDEX CONCURRENTLY命令會在引用的表上加上這種模式的鎖。

 

SHARE

與 ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 模式沖突。 這個模式保證其它事務不能同時更新表中的數據。

CREATE INDEX (不帶CONCURRENTLY命令會在引用的表上加上這種模式的鎖。

 

SHARE ROW EXCLUSIVE

ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE模式沖突。

任何PostgreSQL命令都不會自動得到這種模式的鎖。

 

EXCLUSIVE

與 ROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE模式沖突 。這種模式只允許其它的事務讀表中的數據,不允許其它事務寫表中的數據。PostgreSQL命令不會自動在任何用戶創建的表上加這種模式的鎖。只有在訪問系統表時才會用到這種模式的鎖。

 

ACCESS EXCLUSIVE

與所有的鎖模式都沖突 (ACCESS SHAREROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARE,SHARE ROW EXCLUSIVEEXCLUSIVE, and ACCESS EXCLUSIVE)。這個鎖模式保證只有擁有該鎖的事務才能訪問表,其它事務不能讀也不能寫這個表。ALTER TABLEDROP TABLETRUNCATEREINDEXCLUSTERVACUUM FULL 命令會得到這種模式的鎖。LOCK TABLE命令如果沒有指定鎖的模式,默認使用這種模式來鎖住表。

提示只有ACCESS EXCLUSIVE 模式的鎖才能阻止SELECT (不帶FOR UPDATE/SHARE)命令被執行。

 

事務一旦得到某個模式的鎖,該鎖只有在事務結束以后才會被釋放。但事務在創建了一個savepoint以后得到的鎖會在事務被回滾該savepoint以后立即被釋放。

 

如果兩個鎖模式互相沖突,那么兩個不同的事務不能在同一個表上同時擁有這兩種模式的鎖。如果兩個鎖模式不是互相沖突的,那么兩個不同的事務可以在同一個表上同時擁有這兩種模式的鎖。但同一個事務可以同時在同一個表上擁有兩個互相沖突的鎖模式。例如,ACCESS EXCLUSIVEACCESS SHARE是互相沖突的鎖模式,但一個事務可以先在一個表上得到ACCESS EXCLUSIVE模式的鎖,然后又可以在該表上得到ACCESS SHARE模式的鎖。9-2列出了鎖沖突矩陣,其中的X表示沖突,空白表示不沖突。

 

注意,有些鎖模式與自身是沖突的,例如,兩個事務不能在同一表上同時擁有ACCESS EXCLUSIVE模式的鎖。注意,有些鎖模式與自身是不沖突的,例如,兩個事務可以在同一表上同時擁有ACCESS SHARE模式的鎖。

 

9-2. 鎖沖突矩陣

請求的鎖模式

當前鎖模式

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

 

 

9.3.2 行級鎖

除了表級鎖以外,還有行級鎖。行級鎖的模式有互斥和共享兩種。當一個數據行被刪除或更新時,事務會自動得到這個數據行上的互斥行級鎖。一個事務獲得的行級鎖只有在事務提交或回滾以后才會被釋放。行級鎖不會影響查詢操作,如果有其它事務企圖更新同一個數據行,該事務將會進入等待狀態,直到持有行級鎖的事務被回滾或提交。如果一個命令只是想鎖住數據行,而不是想更新數據行,可以使用SELECT FOR UPDATE,同一個事務以后發出的命令就可以隨意地修改被鎖住的數據行。

 

如果想得到一個數據行上的共享鎖,使用命令SELECT FOR SHARE。多個不同的事務可以在同一個數據行上同時持有共享鎖。如果一個事務已經在一個數據行上持有共享鎖,那么其它的事務就不能更新和刪除這個數據行,也不能在這個數據行上得到互斥鎖。

 

PostgreSQL不會限制一個事務一次能同時鎖住的數據行的最大數目,但每鎖住一個數據行就會有一次寫磁盤的操作。

 

除了表級鎖和行級鎖,數據庫中還存在數據頁級別的共享和互斥鎖,數據頁級別的鎖用來協調對表的的某個數據頁並發訪問操作,它是由數據庫自動得到和釋放的。開發應用程序時不必關心數據頁級別的鎖,只要知道它存在就可以了。

 

9.3.3 死鎖

使用鎖來協調多個事務對表中的數據的並發訪問,很容易產生死鎖,即兩個或多個事務中的每個事務都擁有其它的某個或多個事務需要的鎖資源,所有的事務都會無限期地等待下去。

 

系統會自動檢測數據庫中是否有死鎖發生,如果有,會回滾一個造成死鎖的事務,讓其它的因死鎖而等待的事務繼續執行。

 

9.3.4 建議鎖(Advisory Locks

PostgreSQL提供了一種被稱為建議鎖的鎖機制。建議鎖的含義是有應用程序自己特定的,何時得到和釋放建議鎖也是由應用程序自己控制的,數據庫只是提供得到和釋放建議鎖的接口供用戶使用。建議鎖同事務沒有任何關系,在一個事務中得到的建議鎖,即使在事務被回滾以后仍然存在。建議鎖的所有者是會話而非事務,一個會話可以多次得到同一個建議鎖(如果一個會話執行了n次得到同一個建議鎖的操作,那么它必須執行n次釋放這個建議鎖的操作,否則該建議鎖不會被真正地釋放,仍然被這個會話所有)。在一個會話結束后,該會話持有的所有建議鎖都會被自動釋放。

 

可以查看視圖pg_locks得到當前會話持有的所有建議鎖的列表。建議鎖存放在數據庫的共享內存中,數據庫中的建議鎖的最大數目受參數max_locks_per_transaction max_connections控制。

 

數據庫提供的用於處理建議鎖的函數參見第7.22節表7-50

下面是一些使用建議鎖的實例:

 

SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok

 

SELECT pg_advisory_lock(q.id) FROM

(

SELECT id FROM foo WHERE id > 12345 LIMIT 100;

) q;

 

9.4 在應用程序級別檢查數據的一致性

因為PostgreSQL中的只讀查詢無論在任何隔離事務級別下都不會在讀取的數據上加鎖,一個事務讀取的數據可能已被其它並發執行的事務修改。每個事務都會看到一個數據庫內容的快照,並發執行的事務可能看到不同的數據庫快照。如果事務想保證自己讀取的數據在自己提交或回滾以前不被其它的事務修改,應該使用SELECT FOR UPDATESELECT FOR SHARE來讀取數據(而不是只使用SELECT),也可以使用LOCK TABLE來鎖住表,不讓其它事務更新表中的數據。

 

如果應用程序希望一個事務在執行的過程中所引用的表的數據不能有任何變化,可以先用LOCK TABLE命令鎖住所有的表,然后再執行其它的命令。

 

一般使用顯示的加鎖命令時,事務應該運行在Read Committed隔離級別下,也可以使用Serializable隔離級別。在Serializable隔離級別下使用LOCK TABLE命令時,注意LOCK TABLE命令應該作為事務發出的第一條命令。

 

9.5 鎖與索引

PostgresSQL在訪問索引時也會使用鎖來控制對索引的並發訪問,不同類型的索引的加鎖模式如下:

 

B-tree GiST 索引

使用短期的共享或互斥數據頁級別的鎖來控制對索引的並發訪問。在每個索引數據行被讀取或刪除以后,鎖會被立即釋放。不會出現死鎖的情況。

 

哈希索引

使用共享或互斥數據頁級別的鎖來控制對索引的並發訪問。在每個哈希桶被處理以后,鎖會被立即釋放。可能會出現死鎖。

 

GIN 索引

使用短期的共享和互斥數據頁級別的鎖來控制對索引的並發訪問。在每個索引數據行被讀取或刪除以后,鎖會被立即釋放 。GIN類型索引的插入操作可能素要比較大的工作量。

 

對標量數據類型建立索引時,應該使用B-tree索引。對非標量類型的數據建立索引,應該使用GistGIN索引。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM