PostgreSQL出現死鎖怎么辦?
什么是數據庫死鎖
在操作系統領域當中,死鎖指的是兩個或者兩個以上的進程在運行的過程中,因為爭奪共同的訪問資源而相互等待阻塞,最終導致進程繼無法續執行的一種阻塞現象。那么在數據庫領域當中死鎖又是怎樣的表現形式呢?數據庫死鎖又會帶來怎樣的問題呢?
在理解數據庫死鎖之前,我們先來明確下數據庫的鎖到底是什么?有過Java編程經驗的同學都知道,Java中的鎖是為了解決共享數據的并發訪問安全問題,防止并發訪問導致的共享數據出現錯亂。那么在數據庫領域,數據庫中的鎖又是來干什么的呢?實際上在數據庫中所也是解決并發問題。假如在同一時刻,可能存在多個事務對同一張表的同一個字段進行數字的加減操作,如果沒有任何的控制措施也同樣會導致各種各樣的數據一致性問題。因此數據庫的鎖實際上也是為了保證數據一致性的一種手段,對可能存在的并發操作進行控制。
下面以一個例子來進行說明,假設有這樣兩個事務,事務A中包含如下語句:
UPDATE user SET name = '小慕' id = 1
UPDATE product SET price = price * 10 WHERE id = 2
事務B中包含如下語句:
UPDATE product SET price = price * 100 WHERE id = 2
UPDATE user SET name = '小楓' WHERE id = 1
如果這兩個事務并發執行,那么他們可能存在如下的執行情況,當事務A執行的時候,首先運行了查詢語句:
UPDATE user SET name = '小慕' id = 1
相當于事務A給id為1的數據行加上了排他鎖,但是事務并沒有執行完也就是說此時事務A持有user表的id為1的排他鎖,排他鎖的特性就是此時其他事務不能對數據進行刪除和修改,因此只有等待事務結束釋放鎖之后才能重新獲取。
此時事務B執行更新語句獲取了product表id為2的排他鎖,接著事務B開始執行user表的update語句,需要獲取user表的id為1的排他鎖。但是此時事務A并未提交,因此事務A持有表user的id為1的排他鎖,事務B只有乖乖阻塞等待事務A釋放鎖。而此時事務A執行update語句,需要獲取product的id為2的排他鎖,但是此時事務B持有該排他鎖,因此也需要等待事務B鎖釋放。
UPDATE product SET price = price * 10 WHERE id = 2
事務A在等待事務B結束釋放鎖,而事務B又在等待事務A釋放鎖,最終陷入了互相等待的情況也就是所謂的死鎖。
那么數據庫出現死鎖又會導致什么問題呢?數據庫死鎖會導致嚴重的性能問題,可能平臺因為數據庫死鎖而導致運行緩慢,嚴重影響用戶正常使用業務,因此如果出現數據庫死鎖情況需要及時發現以及解決。
定位死鎖
//先確定數據庫有沒有死鎖情況發生
select * from pg_stat_activity where datname = 'product_db';
//查詢可能鎖了的表的oid
select oid from pg_class where relname='product';
//查詢對應的pid
select pid from pg_locks where relation='oid' //上面查詢出來的oid
//取消或者終止對應的進程破壞死鎖條件
select pg_cancel_backend(pid);
select pg_terminate_backend(pid);
死鎖可能原因及解決辦法
以上分析了PostgreSQL出現死鎖后如何定位分析,那么接下來就需要總結分析分析下PostgreSQL出現死鎖情況的原因以及一般的應對解決辦法。
1、索引使用不當導致的死鎖問題
索引使用存在問題的話會導致死鎖問題,假設在一個數據查詢的事務當中,進行數據檢索的時候沒辦法按照SQL中的where條件進行查詢,因此導致了全表掃描,那么此時數據庫表的行級鎖會上升為表級鎖。如果此時有多個未能按照where條件進行數據查詢的事務存在,那么就容易導致數據庫死鎖問題。也就是說在數據庫表數據量比較大的時候,對應進行數據查詢的表沒有建立索引或者說索引創建的不合理導致無法通過索引進行數據查詢,只能通過全表索引,這樣的場景下就容易產生死鎖。
如何避免:
在進行數據查詢的時候,對應的SQL語句不宜太過復雜,也就是說盡量避免多張表的關聯查詢。
2、不同事務之間的訪問順序問題
當用戶A 訪問數據庫表A時,此時對表A加了共享鎖,然后又訪問數據庫表B。而此時另一個用戶B 訪問表B,對表B加了共享鎖,然后試圖訪問表A。但是用戶A由于用戶B已經鎖住表B,它必須等待用戶B釋放表B才能繼續,同樣用戶B要等用戶A釋放表A才能繼續,也就是說互相等待對方釋放資源,從而導致了死鎖的發生。
如何避免:
這種情況在實際項目中遇到的可能比較多,主要還是需要通過控制代碼的執行邏輯,避免多表操作時同時鎖住多個資源。
避免死鎖的建議
(1)如果平臺中存在大事務,盡量將其拆分為小事務。因為大事務一般操作的數據庫表或者數據都比較多,因此造成死鎖或者阻塞的概率就會相對較大。
(2)為數據庫表設計合理的索引,盡量避免數據查詢時索引未覆蓋或者索引失效的情況,因為全表掃描會會導致給表中的數據行上鎖,大大增加了數據庫產生死鎖的概率。
(3)如果業務允許,我們可以嘗試將隔離級別調低,比如將隔離級別從RR調整為RC,可以避免掉很多因為gap鎖造成的死鎖。
(4)在我們自己的代碼中,盡量以一致的順序獲取對象上的鎖,避免事務中SQL交互執行,從而降低死鎖發生的概率。