PostgreSQL死鎖相關

作者:閻書利

記得原來一老大哥說他運維ORACLE,發現死鎖的時候,總想著緩一緩,再拖一拖,沒準再檢視的時候。死鎖就已經自己釋放掉,不需要處理了(運維的無奈)。而在PostgreSQL中,事務可以按照任意順序加鎖。且PostgreSQL也有著其死鎖處理機制。

當程序請求加鎖的時候,如果失敗,會進入等待佇列。如果在佇列中已經存在一些程序要求本程序中已經持有的鎖,那麼為了儘量避免死鎖,可以把本程序插入到它們的前面。當一個鎖被釋放時,將會試圖喚醒等待佇列裡的程序。這個行為預防了死鎖的產生。

但是這種方法不能完全避免死鎖的產生,PostgreSQL提供如下圖的死鎖檢驗機制。

PostgreSQL死鎖相關

且PostgreSQL使用等待圖(WFG)來檢驗死鎖,WFG為一個有向圖,頂點ABC表示申請加鎖的程序,XY有向邊表示依賴關係。

PostgreSQL死鎖相關

圖中的虛線為soft edge,實線為hard edge

當程序A和程序B都在某個鎖的等待佇列,且程序A在程序B的後邊,兩個程序的加鎖要求衝突,程序A在等待程序B,則存在從A到B的有向邊,名為soft edge;如果程序A的加鎖要求和程序B已經持有的鎖衝突,這時候從A指向B的為hard edge。

系統出現死鎖當且僅當WFG出現環,如果WFG中有soft edge環,則可以透過拓撲排序對佇列進行重排,嘗試消除死鎖。從頂點開始,沿著WFG有向邊走,如果能回到頂點,說明出現死鎖。如果路徑沒有出現soft edge,則直接終止此事務。如果存在soft edge,則記錄所有的soft edge,並嘗試對這個集合進行調整。

透過拓撲排序找到可行的方案,則採用此方案,消除死鎖,(不一定是最優的),否則死鎖清除失敗,終止該事務。

以下為pg12。1有向邊的資料結構

/* * One edge in the waits-for graph。 * * waiter and blocker may or may not be members of a lock group, but if either * is, it will be the leader rather than any other member of the lock group。 * The group leaders act as representatives of the whole group even though * those particular processes need not be waiting at all。 There will be at * least one member of the waiter‘s lock group on the wait queue for the given * lock, maybe more。 */typedef struct{ PGPROC *waiter; /* the leader of the waiting lock group */ PGPROC *blocker; /* the leader of the group it is waiting for */ LOCK *lock; /* the lock being waited for */ int pred; /* workspace for TopoSort */ int link; /* workspace for TopoSort */} EDGE;

PostgreSQL對程序的檢驗過程為:

遞迴試圖檢驗和消除死鎖。

測試當前佇列狀態是否會發生死鎖,如果不滿足約束性檢查,則死鎖。對soft edge調整,並檢驗是否合法。

判斷是否出現環,如果存在且不能調整,則死鎖。

相關引數:

1。Postgresql中,有一個死鎖等待事件的引數,預設是1s,也就是是說Postgresql後臺程序會以1s的頻率來檢測是否存在死鎖。

PostgreSQL死鎖相關

鎖等待超時

2。Postgresql中同樣可以設定鎖等待的超時時間,意味著當前事務在請求一個鎖的時候,一旦等待時長超出指定的時間,當前語句被中止。該引數的預設值為0,意味著發生鎖等待的時候永遠不超時,一直等待下去。預設情況下,鎖超時之後,當前Session的任何語句都會被回滾,即便是執行一個commit。

PostgreSQL死鎖相關

以下為標準鎖的鎖模式以及對應的操作

PostgreSQL死鎖相關

鎖手動處理:

1。查詢阻塞:

postgres=# SELECT w。query as waiting_query,postgres-# w。pid as w_pid,postgres-# w。usename as w_user,postgres-# l。query as locking_query,postgres-# l。pid as l_pid,postgres-# l。usename as l_user,postgres-# t。schemaname || ’。‘ || t。relname as tablenamepostgres-# from pg_stat_activity w join pg_locks l1 on w。pid = l1。pidpostgres-# and not l1。granted join pg_locks l2 on l1。relation = l2。relationpostgres-# and l2。granted join pg_stat_activity l on l2。pid = l。pid join pg_stat_user_tables t on l1。relation = t。relid; waiting_query | w_pid | w_user | locking_query | l_pid | l_user | tablename——————————+————-+——————+————————————————————+————-+——————+————————truncate tab_ysl ; | 5391 | postgres | update tab_ysl set id =9 where id=1; | 5524 | postgres | public。tab_ysl(1 row)//其中l_pid為阻塞者的pid,w_pid為被阻塞者的pid。

2。查詢表持有的鎖:

postgres=# select oid from pg_class where relname= ’tab_ysl‘; oid————24580 (1 row)使pg_class。oid=pg_locks。relation,則表tab_ysl上持有的鎖為,RowExclusiveLock和AccessExclusiveLock ,對應了update和truncate的操作。postgres=# select * from pg_locks where pid in (’5524‘,’5391‘); locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | mode | granted | fastpath————————-+——————+——————+————+————-+——————+————————-+————-+————-+——————+——————————+————+——————————-+————-+——————relation | 13593 | 2659 | | | | | | | | 7/11 | 5524 | AccessShareLock | t | trelation | 13593 | 2658 | | | | | | | | 7/11 | 5524 | AccessShareLock | t | trelation | 13593 | 1249 | | | | | | | | 7/11 | 5524 | AccessShareLock | t | trelation | 13593 | 3455 | | | | | | | | 7/11 | 5524 | AccessShareLock | t | trelation | 13593 | 2663 | | | | | | | | 7/11 | 5524 | AccessShareLock | t | trelation | 13593 | 2662 | | | | | | | | 7/11 | 5524 | AccessShareLock | t | trelation | 13593 | 2685 | | | | | | | | 7/11 | 5524 | AccessShareLock | t | trelation | 13593 | 2684 | | | | | | | | 7/11 | 5524 | AccessShareLock | t | trelation | 13593 | 2615 | | | | | | | | 7/11 | 5524 | AccessShareLock | t | trelation | 13593 | 1259 | | | | | | | | 7/11 | 5524 | AccessShareLock | t | tvirtualxid | | | | | 7/11 | | | | | 7/11 | 5524 | ExclusiveLock | t | tvirtualxid | | | | | 6/181 | | | | | 6/181 | 5391 | ExclusiveLock | t | ttransactionid | | | | | | 512 | | | | 7/11 | 5524 | ExclusiveLock | t | frelation | 13593 | 24580 | | | | | | | | 7/11 | 5524 | RowExclusiveLock | t | ftransactionid | | | | | | 513 | | | | 6/181 | 5391 | ExclusiveLock | t | frelation | 13593 | 24580 | | | | | | | | 6/181 | 5391 | AccessExclusiveLock | f | f(16 rows)

殺掉阻塞者的程序,釋放鎖:

select pg_cancel_backend(’上面查詢到的阻塞著的pid‘);

##注意##pg_cancel_backend(‘阻塞者的pid值’);只能殺死select語句,對其他語句不生效,殺了之後查詢發現還存在,考慮使用pg_terminate_backend(‘程序ID’);