1. 程式人生 > 實用技巧 >postgresql中的諮詢鎖(advisory lock)

postgresql中的諮詢鎖(advisory lock)

諮詢鎖(advisory lock),有的地方翻譯為顧問鎖,作為Postgresql中一種特有的鎖,關於對其介紹,僅從諮詢鎖的描述性定義來看,一開始還真的沒明白這個諮詢鎖是幹什麼的。

暫時拋開諮詢鎖的概念,先說資料庫中傳統的鎖機制。
預設情況下的事務性鎖,讀/寫會自動加鎖,讀/寫完成後會自動解鎖(加解鎖機制在細節上覆雜),這是一種隱式的鎖機制,Postgresql也不例外。
對於加鎖後的併發控制,也就是預設的寫不阻塞讀,是通過MVCC解決的,這種鎖完全不需要認為干預。
相對於隱式鎖機制和MVCC併發控制機制,諮詢鎖可以認為是一種顯式鎖,需要人為地控制,這類鎖需要顯式的申請和釋放,在使用這類鎖的時候,可以自行控制讀寫的排他性。


什麼場景下使用顯式鎖?
比如想實現寫阻塞讀的,或者讀阻塞讀的場景,因為預設的隱式鎖加上MVCC機制,是做不到的。
實際業務型別需求的場景也很多:一個經典的問題,併發情況下,對唯一鍵的存性判斷,然後決定存在則則更新,不存在則插入這種邏輯,就需要諮詢鎖,預設的MVCC下是做不到的,當然也不是說諮詢鎖只能做這個事兒。
再舉個例子:多執行緒程式設計中的執行緒共享變數,在讀寫共享變數時需要執行緒鎖做控制(比如python的lock.acquire()),完成之後釋放鎖,諮詢鎖就有點這個味道(當然不完全相同),這些都是隱式鎖無法完成的。

檢視問官方文件的時候還是嚇了一跳,Postgresql有這麼多型別的諮詢鎖。

Table 9-73. Advisory Lock Functions

NameReturn TypeDescription
pg_advisory_lock(keybigint) void Obtain exclusive session level advisory lock
pg_advisory_lock(key1int,key2int) void Obtain exclusive session level advisory lock
pg_advisory_lock_shared(keybigint) void Obtain shared session level advisory lock
pg_advisory_lock_shared(key1int,key2int) void Obtain shared session level advisory lock
pg_advisory_unlock(keybigint) boolean Release an exclusive session level advisory lock
pg_advisory_unlock(key1int,key2int) 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(keybigint) boolean Release a shared session level advisory lock
pg_advisory_unlock_shared(key1int,key2int) boolean Release a shared session level advisory lock
pg_advisory_xact_lock(keybigint) void Obtain exclusive transaction level advisory lock
pg_advisory_xact_lock(key1int,key2int) void Obtain exclusive transaction level advisory lock
pg_advisory_xact_lock_shared(keybigint) void Obtain shared transaction level advisory lock
pg_advisory_xact_lock_shared(key1int,key2int) void Obtain shared transaction level advisory lock
pg_try_advisory_lock(keybigint) boolean Obtain exclusive session level advisory lock if available
pg_try_advisory_lock(key1int,key2int) boolean Obtain exclusive session level advisory lock if available
pg_try_advisory_lock_shared(keybigint) boolean Obtain shared session level advisory lock if available
pg_try_advisory_lock_shared(key1int,key2int) boolean Obtain shared session level advisory lock if available
pg_try_advisory_xact_lock(keybigint) boolean Obtain exclusive transaction level advisory lock if available
pg_try_advisory_xact_lock(key1int,key2int) boolean Obtain exclusive transaction level advisory lock if available
pg_try_advisory_xact_lock_shared(keybigint) boolean Obtain shared transaction level advisory lock if available
pg_try_advisory_xact_lock_shared(key1int,key2int) 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這個詞,感覺都是大神才能用的)
比如pg_try_advisory_xact_lock(key1int,key2int)這個鎖,就是:非等待模式_申請_一個引數_事務級_排他鎖 這樣一來,需要什麼型別的鎖,或者一個是某個函式實現什麼型別的鎖效果,從這幾個維度區分後,就比較清楚了,而不需要逐個嘗試其效果。 以上簡單總結了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,多次加鎖後需要多次解鎖
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)

if you want do something well,understand it well first.