PostgreSQL中有兩類鎖:表級鎖和行級鎖。當要查詢、插入、更新、刪除表中數據時,首先要獲得表級鎖,然後獲得行級鎖。
下面對PostgreSQL數據庫鎖機制的理解,大部分來自與《PostgreSQL修煉之道 從小工到專家》-唐成書中,以及網絡上的博客的總結。通過實際測試發現,還是存在一些不合理的點,後面實際的案列中,會有一些說明。
1.表級鎖模式
鎖模式 |
解釋 |
Access Share |
只與Access Exclusive鎖模式衝突。 |
查詢命令(Select command)將會在它查詢的表上獲取Access Shared鎖,一般地,任何一個對錶上的只讀查詢操作都將獲取這種類型鎖。 |
|
Row Share |
與Exclusive和Access Exclusive鎖模式衝突。 |
Select for update和Select for share命令將獲得這種類型鎖,並且所有被引用但沒有for update 的表上會加上Access Shared鎖。 |
|
Row Exclusive |
與Share,Shared Row Exclusive,Exclusive,Access Exclusive模式衝突。 |
Update/Delete/Insert命令會在目標表上獲得這種類型的鎖,並且在其它被引用的表上加上Access Share鎖,一般地,更改表數據的命令都將在這張表上獲得Row Exclusive鎖。 |
|
Share Update Exclusive |
Share Update Exclusive,Share,Share Row Exclusive,Exclusive,Access exclusive模式衝突,這種模式保護一張表不被併發的模式更改和Vacuum。 |
Vacuum(without full),Analyze 和 Create index concur-ently命令會獲得這種類型鎖。 |
|
Share |
與Row Exclusive,Shared Update Exclusive,Share Row Exclusive,Exclusive,Access exclusive鎖模式衝突,這種模式保護一張表數據不被併發的更改。 |
Create index命令會獲得這種鎖模式。 |
|
Share Row Exclusive |
與Row Exclusive,Share Update Exclusive,Shared,Shared Row Exclusive,Exclusive,Access Exclusive鎖模式衝突。 |
任何PostgreSQL命令不會自動獲得這種類型的鎖。 |
|
Exclusive |
與ROW Share , Row Exclusive, Share Update Exclusive, Share , Share Row Exclusive, Exclusive, Access Exclusive模式衝突,這種鎖模式僅能與Access Share 模式併發,換句話說,只有讀操作可以和持有Exclusive鎖的事務並行。 |
任何PostgreSQL命令不會自動獲得這種類型的鎖。 |
|
Access Exclusive |
與所有模式鎖衝突(Access Share,Row Share,Row Exclusive,Share Update Exclusive,Share , Share Row Exclusive,Exclusive,Access Exclusive),這種模式保證了當前只有一個人訪問這張表;ALTER TABLE,DROP TABLE,TRUNCATE,REINDEX,CLUSTER,VACUUM FULL 命令會獲得這種類型鎖,在Lock table 命令中,如果沒有申明其它模式,它也是默認模式。 |
2.表級鎖的衝突矩陣
請求的鎖模式 |
已申請到的鎖模式 |
|||||||
Access Share |
Row Share |
Row Exclusive |
Share Update Exclusive |
Share |
Share Row Exclusive |
Exclusive |
Access Exclusive |
|
Access Share |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
N |
Row Share |
Y |
Y |
Y |
Y |
Y |
Y |
N |
N |
Row Exclusive |
Y |
Y |
Y |
Y |
N |
N |
N |
N |
Share Update Exclusive |
Y |
Y |
Y |
N |
N |
N |
N |
N |
Share |
Y |
Y |
N |
N |
N |
N |
N |
N |
Share Row Exclusive |
Y |
Y |
N |
N |
N |
N |
N |
N |
Exclusive |
Y |
N |
N |
N |
N |
N |
N |
N |
Access Exclusive |
N |
N |
N |
N |
N |
N |
N |
N |
表中“N”表示這兩種表衝突,也就是不同的進程不能同時持有這兩種鎖。
最普通的是Share和Exclusive這兩種鎖,它們分別是讀、寫鎖的意思。加了Share鎖,即讀鎖,表的內容就不被修改了;可以爲多個事務加上此鎖,只要任意一個事務不釋放這個讀鎖,則其他事務就不能修改這個表。加上了Exclusive,相當於加了寫鎖,這時別的進程不能寫也不能讀這條數據。但後來數據庫又加上了多版本的功能。修改一條語句的同時,允許了讀數據,爲了處理這種情況,又增加了兩種鎖Access Share和Access Excusive,鎖中的關鍵字 Access 是與多版本相關的有了該功能。其實,有了該功能後,如果修改一行數據,實際並沒有改原先那行數據,而是複製了一個新行,修改都在新行上,事務不提交,其他人是看不到修改的這條數據的。由於舊行數據沒有變化,在修改過程中,讀數據的人仍然可以讀到舊的數據。
表級鎖加鎖對象是表,這使得加鎖範圍太大,導致併發並不高,於是人們提出了行級鎖的概念,但行級鎖與表級鎖之間會產生衝突,這時需要一種機制來描述行級鎖與表級鎖之間的關係,有了意向鎖的概念,這時又加了兩種鎖,即意向共享鎖(Row Share) 和意向排他鎖(Row Exclusive),由於意向鎖之間不會產生衝突,因爲他們是“有意”做,還沒真做;而且意向排它鎖相互之間也不會產生衝突,於是又需要更嚴格一些的鎖,這樣就產生了Share Update Exclusive,Share Row Exclusive可以看成Share與Row Exclusive,PostgreSQL不會自動請求這個鎖模式,也就是PostgreSQL內部目前沒有使用這種鎖。
這裏稍微補充一下多版本併發控制原理。
多版本併發控制原理:
大家熟知的讀與寫鎖是不能併發的,所以有人想到一種新的能夠讓讀寫併發的方法,稱這種方法爲MVCC。MVCC的方法是寫數據時,舊版本的數據並不刪除,併發的讀操作還能讀到舊版本的數據。
實現MVCC的兩種方法:
- 寫新數據時,把舊數據移到一個單獨的地方,如回滾段中,其他讀數據時,從回滾段中把舊版本數據讀出來。
- 寫新數據時,舊版本的數據不刪除,而是把新數據插入。
PostgreSQL數據庫使用的正是第二種方法,而oracle與MySQL中的innodb引擎用的是第一種方法。
PostgreSQL實現該功能,需要在每張表上添加四個系統字段tmin、tmax、cmin、cmax,通過這四個字段可以區分併發時,記錄不同數據的版本,和事務標識,當刪除時,只會標記記錄,而不會從數據塊中刪除,空間也沒有立即釋放。
PostgreSQL通過運行vaccum進程來進行回收之前的存儲空間,默認PostgreSQL中的autovacuum是打開的,當一表更新達到一定數量時,autovacuum會自動回收空間。
3.表級鎖類型對應的數據庫操作
鎖類型 |
對應的數據庫操作 |
Access Share |
select |
Row Share |
select for update, select for share |
Row Exclusive |
update,delete,insert |
Share Update Exclusive |
vacuum(without full),analyze,create index concurrently |
Share |
create index |
Share Row Exclusive |
任何Postgresql命令不會自動獲得這種類型的鎖 |
Exclusive |
任何Postgresql命令不會自動獲得這種類型的鎖 |
Access Exclusive |
alter table,drop table,truncate,reindex,cluster,vacuum full |
4.行級鎖模式
行級鎖模式只有兩種,分別是共享鎖和排他鎖,或者說是讀鎖或寫鎖。由於多版本的實現,實際讀取行數據時,並不會在行上執行任何鎖。
特定行上的行級鎖是在行被更新的時候自動請求的(或者被刪除時或標記爲更新)。 鎖一直保持到事務提交或者回滾。行級鎖不影響對數據的查詢;它們只阻塞對同一行的寫入。 要在不修改某行的前提下請求在該行的行級鎖,用 SELECT FOR UPDATE 選取該行。請注意一旦我們請求了特定的行級鎖,那麼該事務就可以多次對該行進行更新而不用擔心衝突。
《PostgreSQL修煉之道 從小工到專家》-唐成書中強調的是,行級鎖只有共享鎖與排他鎖,也就是讀鎖、寫鎖,但是實際測試中有發現一些tuple類型的多版本控制的寫鎖。
5.頁級鎖
除了表級別的和行級別的鎖以外, 頁面級別的共享/排他銷也用於控制對共享緩衝池中表頁面的讀/寫訪問。這些鎖在抓取或者更新一行後馬上被釋放。應用程序員通常不需要關心頁級鎖,在這裏提到它們只是爲了完整。
6.鎖命令
1.表級鎖命令
在PsotgreSQL中,顯示的在表上加鎖命令爲LOCK TABLE命令:
LOCK [ TABLE ] [ ONLY ] name [ * ] [, ...] [ IN lockmode MODE ] [ NOWAIT ]
說明如下:
- name:要鎖定的現有表的鎖名稱(可選模式限定)。 如果在表名之前指定ONLY,則僅該表被鎖定,如果未指定ONLY,則表及其所有後代表(如果有)被鎖定。
- lock_mode:鎖模式指定此鎖與之衝突的鎖。 如果未指定鎖定模式,則使用最嚴格的訪問模式ACCESS EXCLUSIVE。
- NOWAIT:如果沒有NOWAIT關鍵字時,當無法獲得鎖時,會一直等待,而如果加了NOWAIT關鍵字,在無法立即獲取該鎖時,此命令會立即退出併發出一個錯誤信息
2.行級鎖命令
在PsotgreSQL中,顯示的行級鎖命令是由select命令發出的:
SELECT …… FOR { UPDATE | SHARE } [OF table_name[,……]] [ NOWAIT]
說明如下:
- 指定 OF table_name,則只有被指定的表會被鎖定。
- 例外情況,主查詢中引用了WITH查詢時,WITH查詢中的表不被鎖定。
- 如果需要鎖定WITH查詢中的表,需在WITH查詢內指定FOR UPDATA或FOR SHARE。
- NOWAIT關鍵字與顯示加表級鎖命令作用一樣。
7.死鎖
死鎖是指兩個或者兩個以上的事務在執行過程中相互持有對方期待的資源,若沒有其他機制,它們將無法進行下去。
1.形成死鎖的4個必要條件
- 請求與保持條件:獲取資源的進程可以同時申請新的資源。
- 非剝奪條件:已經分配的資源不能從該進程中剝奪。
- 循環等待條件:多個進程構成環路,並且每個進程都在等待相鄰進程正佔用的資源。
- 互斥條件:資源只能被一個進程使用。
2.減少死鎖的方法
- 在所有事務中都以相同的次序使用資源。
- 使事務儘可能簡單並在一個批處理中。
- 爲死鎖超時參數設置一個合理範圍,如10s;超時則自動放棄本操作,避免進程掛起。可以在postgresql.conf文件中設置:log_lock_waits = on deadlock_timeout = 10s
- 避免在事務內核用戶進行交互,減少資源的鎖定時間。
- 使用較低的隔離級別,相比較高的隔離級別能夠有效減少持有共享鎖的時間,減少鎖之間的競爭。