PostgreSQL出現(xiàn)死鎖怎么辦?

什么是數(shù)據(jù)庫死鎖
在操作系統(tǒng)領(lǐng)域當(dāng)中,死鎖指的是兩個(gè)或者兩個(gè)以上的進(jìn)程在運(yùn)行的過程中,因?yàn)闋帄Z共同的訪問資源而相互等待阻塞,最終導(dǎo)致進(jìn)程繼無法續(xù)執(zhí)行的一種阻塞現(xiàn)象。那么在數(shù)據(jù)庫領(lǐng)域當(dāng)中死鎖又是怎樣的表現(xiàn)形式呢?數(shù)據(jù)庫死鎖又會帶來怎樣的問題呢?
在理解數(shù)據(jù)庫死鎖之前,我們先來明確下數(shù)據(jù)庫的鎖到底是什么?有過Java編程經(jīng)驗(yàn)的同學(xué)都知道,Java中的鎖是為了解決共享數(shù)據(jù)的并發(fā)訪問安全問題,防止并發(fā)訪問導(dǎo)致的共享數(shù)據(jù)出現(xiàn)錯亂。那么在數(shù)據(jù)庫領(lǐng)域,數(shù)據(jù)庫中的鎖又是來干什么的呢?實(shí)際上在數(shù)據(jù)庫中所也是解決并發(fā)問題。假如在同一時(shí)刻,可能存在多個(gè)事務(wù)對同一張表的同一個(gè)字段進(jìn)行數(shù)字的加減操作,如果沒有任何的控制措施也同樣會導(dǎo)致各種各樣的數(shù)據(jù)一致性問題。因此數(shù)據(jù)庫的鎖實(shí)際上也是為了保證數(shù)據(jù)一致性的一種手段,對可能存在的并發(fā)操作進(jìn)行控制。
下面以一個(gè)例子來進(jìn)行說明,假設(shè)有這樣兩個(gè)事務(wù),事務(wù)A中包含如下語句:
UPDATE user SET name = '小慕' id = 1
UPDATE product SET price = price * 10 WHERE id = 2
事務(wù)B中包含如下語句:
UPDATE product SET price = price * 100 WHERE id = 2
UPDATE user SET name = '小楓' WHERE id = 1
如果這兩個(gè)事務(wù)并發(fā)執(zhí)行,那么他們可能存在如下的執(zhí)行情況,當(dāng)事務(wù)A執(zhí)行的時(shí)候,首先運(yùn)行了查詢語句:
UPDATE user SET name = '小慕' id = 1
相當(dāng)于事務(wù)A給id為1的數(shù)據(jù)行加上了排他鎖,但是事務(wù)并沒有執(zhí)行完也就是說此時(shí)事務(wù)A持有user表的id為1的排他鎖,排他鎖的特性就是此時(shí)其他事務(wù)不能對數(shù)據(jù)進(jìn)行刪除和修改,因此只有等待事務(wù)結(jié)束釋放鎖之后才能重新獲取。

此時(shí)事務(wù)B執(zhí)行更新語句獲取了product表id為2的排他鎖,接著事務(wù)B開始執(zhí)行user表的update語句,需要獲取user表的id為1的排他鎖。但是此時(shí)事務(wù)A并未提交,因此事務(wù)A持有表user的id為1的排他鎖,事務(wù)B只有乖乖阻塞等待事務(wù)A釋放鎖。而此時(shí)事務(wù)A執(zhí)行update語句,需要獲取product的id為2的排他鎖,但是此時(shí)事務(wù)B持有該排他鎖,因此也需要等待事務(wù)B鎖釋放。
UPDATE product SET price = price * 10 WHERE id = 2

事務(wù)A在等待事務(wù)B結(jié)束釋放鎖,而事務(wù)B又在等待事務(wù)A釋放鎖,最終陷入了互相等待的情況也就是所謂的死鎖。


那么數(shù)據(jù)庫出現(xiàn)死鎖又會導(dǎo)致什么問題呢?數(shù)據(jù)庫死鎖會導(dǎo)致嚴(yán)重的性能問題,可能平臺因?yàn)閿?shù)據(jù)庫死鎖而導(dǎo)致運(yùn)行緩慢,嚴(yán)重影響用戶正常使用業(yè)務(wù),因此如果出現(xiàn)數(shù)據(jù)庫死鎖情況需要及時(shí)發(fā)現(xiàn)以及解決。
定位死鎖
//先確定數(shù)據(jù)庫有沒有死鎖情況發(fā)生
select * from pg_stat_activity where datname = 'product_db';
//查詢可能鎖了的表的oid
select oid from pg_class where relname='product';
//查詢對應(yīng)的pid
select pid from pg_locks where relation='oid' //上面查詢出來的oid
//取消或者終止對應(yīng)的進(jìn)程破壞死鎖條件
select pg_cancel_backend(pid);
select pg_terminate_backend(pid);
死鎖可能原因及解決辦法
以上分析了PostgreSQL出現(xiàn)死鎖后如何定位分析,那么接下來就需要總結(jié)分析分析下PostgreSQL出現(xiàn)死鎖情況的原因以及一般的應(yīng)對解決辦法。
1、索引使用不當(dāng)導(dǎo)致的死鎖問題
索引使用存在問題的話會導(dǎo)致死鎖問題,假設(shè)在一個(gè)數(shù)據(jù)查詢的事務(wù)當(dāng)中,進(jìn)行數(shù)據(jù)檢索的時(shí)候沒辦法按照SQL中的where條件進(jìn)行查詢,因此導(dǎo)致了全表掃描,那么此時(shí)數(shù)據(jù)庫表的行級鎖會上升為表級鎖。如果此時(shí)有多個(gè)未能按照where條件進(jìn)行數(shù)據(jù)查詢的事務(wù)存在,那么就容易導(dǎo)致數(shù)據(jù)庫死鎖問題。也就是說在數(shù)據(jù)庫表數(shù)據(jù)量比較大的時(shí)候,對應(yīng)進(jìn)行數(shù)據(jù)查詢的表沒有建立索引或者說索引創(chuàng)建的不合理導(dǎo)致無法通過索引進(jìn)行數(shù)據(jù)查詢,只能通過全表索引,這樣的場景下就容易產(chǎn)生死鎖。
如何避免:
在進(jìn)行數(shù)據(jù)查詢的時(shí)候,對應(yīng)的SQL語句不宜太過復(fù)雜,也就是說盡量避免多張表的關(guān)聯(lián)查詢。
2、不同事務(wù)之間的訪問順序問題
當(dāng)用戶A 訪問數(shù)據(jù)庫表A時(shí),此時(shí)對表A加了共享鎖,然后又訪問數(shù)據(jù)庫表B。而此時(shí)另一個(gè)用戶B 訪問表B,對表B加了共享鎖,然后試圖訪問表A。但是用戶A由于用戶B已經(jīng)鎖住表B,它必須等待用戶B釋放表B才能繼續(xù),同樣用戶B要等用戶A釋放表A才能繼續(xù),也就是說互相等待對方釋放資源,從而導(dǎo)致了死鎖的發(fā)生。
如何避免:
這種情況在實(shí)際項(xiàng)目中遇到的可能比較多,主要還是需要通過控制代碼的執(zhí)行邏輯,避免多表操作時(shí)同時(shí)鎖住多個(gè)資源。
避免死鎖的建議
(1)如果平臺中存在大事務(wù),盡量將其拆分為小事務(wù)。因?yàn)榇笫聞?wù)一般操作的數(shù)據(jù)庫表或者數(shù)據(jù)都比較多,因此造成死鎖或者阻塞的概率就會相對較大。
(2)為數(shù)據(jù)庫表設(shè)計(jì)合理的索引,盡量避免數(shù)據(jù)查詢時(shí)索引未覆蓋或者索引失效的情況,因?yàn)槿頀呙钑?dǎo)致給表中的數(shù)據(jù)行上鎖,大大增加了數(shù)據(jù)庫產(chǎn)生死鎖的概率。
(3)如果業(yè)務(wù)允許,我們可以嘗試將隔離級別調(diào)低,比如將隔離級別從RR調(diào)整為RC,可以避免掉很多因?yàn)間ap鎖造成的死鎖。
(4)在我們自己的代碼中,盡量以一致的順序獲取對象上的鎖,避免事務(wù)中SQL交互執(zhí)行,從而降低死鎖發(fā)生的概率。



























