在數據庫中,通過鎖機制以及多版本并發控制(MVCC)可以保護數據的一致性。例如,會話A正在查詢數據,會話B就無法對會話A訪問的對象執行DDL。會話A正在更新某條記錄,會話B就不能刪除或更新這條記錄。

鎖是由數據庫自動控制的,如果應用程序或者SQL腳本設計不當,就可能導致長時間的鎖等待或者死鎖。AnalyticDB for PostgreSQL提供了兩種統計視圖,用戶可通過這兩個視圖查詢鎖等待或者死鎖的情況。

  • pg_locks:用于展示鎖信息,每個被鎖的對象或等待鎖的對象為一條記錄。
  • pg_stat_activity:顯示所有會話的信息,每個會話為一條記錄。

創建鎖監控視圖

具體如何查詢當前的鎖等待和持鎖信息,您可以通過如下SQL語句創建鎖監控視圖。

說明 本文的所有SQL命令均在psql客戶端中執行,請使用psql連接數據庫。

create view v_locks_monitor as
with
t_wait as
(
select a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,a.classid,a.granted,
a.objid,a.objsubid,a.pid,a.transactionid,
b.xact_start,b.query_start,b.usename,b.datname,b.client_addr,b.client_port,b.application_name
from pg_locks a,pg_stat_activity b where a.pid=b.pid and not a.granted
),
t_run as
(
select a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,a.classid,a.granted,
a.objid,a.objsubid,a.pid,a.transactionid,
b.xact_start,b.query_start,b.usename,b.datname,b.client_addr,b.client_port,b.application_name
from pg_locks a,pg_stat_activity b where a.pid=b.pid and a.granted
),
t_overlap as
(
select r.* from t_wait w join t_run r on
(
r.locktype is not distinct from w.locktype and
r.database is not distinct from w.database and
r.relation is not distinct from w.relation and
r.page is not distinct from w.page and
r.tuple is not distinct from w.tuple and

r.transactionid is not distinct from w.transactionid and
r.classid is not distinct from w.classid and
r.objid is not distinct from w.objid and
r.objsubid is not distinct from w.objsubid and
r.pid <> w.pid
)
),
t_unionall as
(
select r.* from t_overlap r
union all
select w.* from t_wait w
)
select locktype,datname,relation::regclass,page,tuple,transactionid::text,classid::regclass,objid,objsubid,
string_agg(
'Pid: '||case when pid is null then 'NULL' else pid::text end||chr(10)||
'Lock_Granted: '||case when granted is null then 'NULL' else granted::text end||
' , Mode: '||case when mode is null then 'NULL' else mode::text end||
' , Username: '||case when usename is null then 'NULL' else usename::text end||
' , Database: '||case when datname is null then 'NULL' else datname::text end||
' , Client_Addr: '||case when client_addr is null then 'NULL' else client_addr::text end||
' , Client_Port: '||case when client_port is null then 'NULL' else client_port::text end||
' , Application_Name: '||case when application_name is null then 'NULL' else application_name::text end||chr(10)||
' , Xact_Start: '||case when xact_start is null then 'NULL' else xact_start::text end||
' , Query_Start: '||case when query_start is null then 'NULL' else query_start::text end||
' , Xact_Elapse: '||case when (now()-xact_start) is null then 'NULL' else (now()-xact_start)::text end||
chr(10)||'--------'||chr(10), (case when granted then '0' else '1' end)
order by
( case mode
when 'INVALID' then 0
when 'AccessShareLock' then 1
when 'RowShareLock' then 2
when 'RowExclusiveLock' then 3
when 'ShareUpdateExclusiveLock' then 4
when 'ShareLock' then 5
when 'ShareRowExclusiveLock' then 6
when 'ExclusiveLock' then 7
when 'AccessExclusiveLock' then 8
else 0
end ) desc )
as lock_conflict
from t_unionall
group by
locktype,datname,relation,page,tuple,transactionid::text,classid,objid,objsubid;

            

查詢鎖信息

當發生鎖等待或者死鎖時,使用如下SQL語句查詢v_locks_monitor的信息。


postgres=# \x

postgres=# select * from v_locks_monitor;

            

處理方法

前面SQL查詢語句可以清晰地顯示目前數據庫系統發生的鎖的情況,使用如下語句終止對應的進程。

postgres=# select pg_terminate_backend(PID);
其中PID為v_locks_monitor查詢結果中Lock_Granted值為t的記錄的Pid,如下圖所示。

執行如下語句,如果返回結果為0 rows表示鎖已經清除,如果查詢結果還有記錄,請再次執行select pg_terminate_backend(PID);終止對應的PID。

postgres=# select * from v_locks_monitor; 
            

參考文獻

PostgreSQL 鎖等待監控珍藏級SQL - 誰堵塞了誰