咨詢鎖(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解鎖,
這里的兩個Session是在兩個不同的表上申請的相同的Id的鎖,但是SessionB一樣會被阻塞,這個就是解釋了pg_advisory_lock在一個引數的時候,是一個庫級別的鎖,
如果想要設定一個同一個表的同一個Id的鎖,相信聰明的少俠一定知道該怎么辦了,pg_advisory_lock這個函式多載的兩個引數的方法,就是在另外一個維度定義鎖定資訊的,
這里說的兩個引數,可以從不同維度定義鎖定目標,而不是單單為了表級別的鎖定,
- 5,等待行為,對于鎖的申請,其結果有兩種可能性,1是申請到了,2是沒有申請到,對于沒有申請到的情況,有兩中可選行為,要么一直等下去,要么不等了直接回傳表面沒申請到
對于上面所說的,SessionB因為無法獲取Id上的排它鎖,導致掛起的行為,對應用程式表現的不太友好,也容易造成長時間持有連接造成資料庫連接的暴增,如何破解?
如果注意上述串列中鎖函式的回傳值,就會返現,有一部分回傳值是void,一部分回傳值是boolean,回傳boolean的方法就是可以根據鎖定目標時,根據回傳值來判斷是否成功鎖定,
對于范圍值為boolean的函式,請求發起后都會立即回傳,只不過是如果成功申請到了鎖,回傳T(true),如果沒有成功申請到鎖,回傳F(False)
這樣的話,處理起來就比較靈活一點,而不是在申請不到鎖的時候,Session處于一直掛起的狀態,用流行專業的術語說就是Session一直hang起(一直不怎么敢用hang這個詞,感覺都是大神才能用的)
簡單測驗一把
--鎖定某個表的某一行 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.
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/1171.html
標籤:PostgreSQL
