咨詢鎖(advisory lock),有的地方翻譯為顧問鎖,作為Postgresql中一種特有的鎖,關於對其介紹,僅從咨詢鎖的描述性定義來看,一開始還真的沒明白這個咨詢鎖是干什么的。
暫時拋開咨詢鎖的概念,先說數據庫中傳統的鎖機制。
默認情況下的事務性鎖,讀/寫會自動加鎖,讀/寫完成后會自動解鎖(加解鎖機制在細節上復雜),這是一種隱式的鎖機制,Postgresql也不例外。
對於加鎖后的並發控制,也就是默認的寫不阻塞讀,是通過MVCC解決的,這種鎖完全不需要人為干預。
相對於隱式鎖機制和MVCC並發控制機制,咨詢鎖可以認為是一種顯式鎖,需要人為地控制,這類鎖需要顯式的申請和釋放,在使用這類鎖的時候,可以自行控制讀寫的排他性。
什么場景下使用顯式鎖?
比如想實現寫阻塞讀,或者讀阻塞讀的場景,因為默認的隱式鎖加上MVCC機制,是做不到的。
實際業務類型需求的場景也很多:一個經典的問題,並發情況下,對唯一鍵的存性判斷,然后決定存在則更新,不存在則插入這種邏輯,就需要咨詢鎖,默認的MVCC下是做不到的,當然也不是說咨詢鎖只能做這個事兒。
再舉個例子:多線程編程中的線程共享變量,在讀寫共享變量時需要線程鎖做控制(比如python的lock.acquire()),完成之后釋放鎖,咨詢鎖就有點這個味道(當然不完全相同),這些都是隱式鎖無法完成的。
查看問官方文檔的時候還是嚇了一跳,Postgresql有這么多類型的咨詢鎖。
Table 9-73. Advisory Lock Functions
Name | Return Type | Description |
---|---|---|
pg_advisory_lock(key bigint) |
void | Obtain exclusive session level advisory lock |
pg_advisory_lock(key1 int, key2 int) |
void | Obtain exclusive session level advisory lock |
pg_advisory_lock_shared(key bigint) |
void | Obtain shared session level advisory lock |
pg_advisory_lock_shared(key1 int, key2 int) |
void | Obtain shared session level advisory lock |
pg_advisory_unlock(key bigint) |
boolean | Release an exclusive session level advisory lock |
pg_advisory_unlock(key1 int, key2 int) |
boolean | Release an exclusive session level advisory lock |
pg_advisory_unlock_all() |
void | Release all session level advisory locks held by the current session |
pg_advisory_unlock_shared(key bigint) |
boolean | Release a shared session level advisory lock |
pg_advisory_unlock_shared(key1 int, key2 int) |
boolean | Release a shared session level advisory lock |
pg_advisory_xact_lock(key bigint) |
void | Obtain exclusive transaction level advisory lock |
pg_advisory_xact_lock(key1 int, key2 int) |
void | Obtain exclusive transaction level advisory lock |
pg_advisory_xact_lock_shared(key bigint) |
void | Obtain shared transaction level advisory lock |
pg_advisory_xact_lock_shared(key1 int, key2 int) |
void | Obtain shared transaction level advisory lock |
pg_try_advisory_lock(key bigint) |
boolean | Obtain exclusive session level advisory lock if available |
pg_try_advisory_lock(key1 int, key2 int) |
boolean | Obtain exclusive session level advisory lock if available |
pg_try_advisory_lock_shared(key bigint) |
boolean | Obtain shared session level advisory lock if available |
pg_try_advisory_lock_shared(key1 int, key2 int) |
boolean | Obtain shared session level advisory lock if available |
pg_try_advisory_xact_lock(key bigint) |
boolean | Obtain exclusive transaction level advisory lock if available |
pg_try_advisory_xact_lock(key1 int, key2 int) |
boolean | Obtain exclusive transaction level advisory lock if available |
pg_try_advisory_xact_lock_shared(key bigint) |
boolean | Obtain shared transaction level advisory lock if available |
pg_try_advisory_xact_lock_shared(key1 int, key2 int) |
boolean | Obtain shared transaction level advisory lock if available |
其實細看下去,並不復雜,按照“申請/釋放,生效范圍”,鎖類型,參數個數,等待行為,這個咨詢鎖從幾個維度分類之后,還是比較清晰的。
所有的咨詢鎖函數都是這幾個維度的不同組合,只要弄清楚這些鎖的不同維度,上面表格中洋洋灑灑的數十個鎖函數,加上備注,理解起來還是比較容易的。
所有的咨詢鎖函數都是這幾個維度的不同組合,只要弄清楚這些鎖的不同維度,上面表格中洋洋灑灑的數十個鎖函數,加上備注,理解起來還是比較容易的。

如下對生效范圍,鎖類型,申請/釋放,參數個數,等待行為逐一解釋:
- 1,申請/釋放:有申請就有釋放,Session級別的鎖需要顯式釋放,隨着連接的關閉自動釋放;事務級別的鎖也需要顯式釋放,或者會隨着事務的結束(提交或者回滾)一並釋放
- 2,鎖類型:共享鎖和排它鎖,比如pg_advisory_lock是排它鎖,pg_advisory_lock_shared是共享鎖
- 3,生效范圍:Session級的或者事務級的,很好理解,比如pg_advisory_lock是添加Session級的排它鎖,pg_advisory_xact_lock是申請事務級排它鎖
- 4,參數個數,這個看概念是有點蒙的,有的鎖函數是1個參數,有的是2個參數,一個參數的情況下,鎖是庫級別的,舉個例子就很容易理解了
SessionA dbtest=> select pg_advisory_lock(id),* from t_advisory1 where id = 1; pg_advisory_lock | id ------------------+---- | 1 (1 row) SessionB dbtest=>select pg_advisory_lock(id),* from t_advisory2 where id = 1; --當前Session一直被掛起,或者說阻塞,直到SessionA解鎖。
如果想要設置一個同一個表的同一個Id的鎖,相信聰明的少俠一定知道該怎么辦了,pg_advisory_lock這個函數重載的兩個參數的方法,就是在另外一個維度定義鎖定信息的。
這里說的兩個參數,可以從不同維度定義鎖定目標,而不是單單為了表級別的鎖定。
- 5,等待行為,對於鎖的申請,其結果有兩種可能性,1是申請到了,2是沒有申請到,對於沒有申請到的情況,有兩中可選行為,要么一直等下去,要么不等了直接返回表面沒申請到
對於上面所說的,SessionB因為無法獲取Id上的排它鎖,導致掛起的行為,對應用程序表現的不太友好,也容易造成長時間持有連接造成數據庫連接的暴增,如何破解?
如果注意上述列表中鎖函數的返回值,就會返現,有一部分返回值是void,一部分返回值是boolean,返回boolean的方法就是可以根據鎖定目標時,根據返回值來判斷是否成功鎖定。
對於范圍值為boolean的函數,請求發起后都會立即返回,只不過是如果成功申請到了鎖,返回T(true),如果沒有成功申請到鎖,返回F(False)
這樣的話,處理起來就比較靈活一點,而不是在申請不到鎖的時候,Session處於一直掛起的狀態,用流行專業的術語說就是Session一直hang起(一直不怎么敢用hang這個詞,感覺都是大神才能用的)
比如pg_try_advisory_xact_lock(key1 int, key2 int)這個鎖,就是:非等待模式_申請_一個參數_事務級_排他鎖
這樣一來,需要什么類型的鎖,或者一個是某個函數實現什么類型的鎖效果,從這幾個維度區分后,就比較清楚了,而不需要逐個嘗試其效果。
以上簡單總結了Postgresql中咨詢鎖的概念和用法,咨詢鎖作為一種顯式定義的鎖,青脆爽口,輕便靈活,為處理不同邏輯提供了一定的方便性,但也需要使用咨詢鎖時的潛在的問題。
簡單測試一把
簡單測試一把
--鎖定某個表的某一行 db01=# select pg_try_advisory_lock(cast('t1'::regclass::oid as int),id),id from t1 where id = 1; pg_try_advisory_lock | id ----------------------+---- t | 1 (1 row) --解鎖鎖定某個表的某一行 db01=# select pg_advisory_unlock(cast('t1'::regclass::oid as int),id),id from t1 where id = 1; pg_advisory_unlock | id --------------------+---- t | 1 (1 row) --直接基於變量的鎖定 db01=# select pg_try_advisory_lock(100,1); pg_try_advisory_lock ---------------------- t (1 row) --同一個Session內可以重復鎖定 db01=# select pg_try_advisory_lock(100,1); pg_try_advisory_lock ---------------------- t (1 row) --解鎖,解鎖成功返回t db01=# select pg_advisory_unlock(100,1); pg_advisory_unlock -------------------- t (1 row) --解鎖,解鎖成功返回t,多次加鎖后需要多次解鎖,如果腦袋沒問題的話,相信沒人會在一個Session或者事務里連續對一個Id加鎖,雖然postgre支持這么干 db01=# select pg_advisory_unlock(100,1); pg_advisory_unlock -------------------- t (1 row) --如果解鎖的時候鎖不存在,解鎖失敗 db01=# select pg_advisory_unlock(100,1); WARNING: you don''t own a lock of type ExclusiveLock pg_advisory_unlock -------------------- f (1 row) db01=# --如果上一個Session的排它鎖解鎖之前,其他Session嘗試加鎖,直接返回失敗 db01=# select pg_try_advisory_lock(100,1); pg_try_advisory_lock ---------------------- f (1 row) --活動鎖的查看 select locktype as lc,relation::regclass as relname,page||','||tuple as ctid,virtualxid ,transactionid as txid,virtualtransaction,pid,mode,granted from pg_locks ;
if you want do something well, understand it well first.