如何避免熱度數(shù)據(jù)頻繁更新造成數(shù)據(jù)庫死鎖?
數(shù)據(jù)庫死鎖對業(yè)務(wù)來說是一個非常嚴重的問題,它一定一定一定是代碼的執(zhí)行流程處理不當(dāng)造成的。
但是重構(gòu)龐大的業(yè)務(wù)代碼不是說了就能輕易做到的事情,下面給出了一些方案,由淺入深,告訴大家解決死鎖問題的正確之道。
死鎖問題產(chǎn)生的原因和條件
死鎖問題一般發(fā)生在短時間內(nèi)多個并發(fā)任務(wù)對同一組表進行修改的場景。
數(shù)據(jù)庫在更新數(shù)據(jù)時會加鎖,以確保不會發(fā)生并發(fā)寫數(shù)據(jù)。
MySQL加鎖的機制是根據(jù)索引情況加鎖,可能是行鎖,也有可能是間隔鎖(詳情可查看文檔:https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html)
并發(fā)修改表A時,只有一個請求能夠獲取到鎖,其余的請求需要等待它處理完成釋放鎖,才能進行下一輪競爭。
如果此時獲取到表A鎖的線程T1同時需要修改表B,但表B的鎖被線程T2獲取,且T2正在等待表A的鎖,這就造成了兩個線程相互等待,進而死鎖。
解決辦法
1、給表增加索引
MySQL給表加鎖時,鎖的范圍受索引影響。如果更新表時,where條件恰好有索引覆蓋,那么MySQL可以精確地對滿足條件的行記錄加鎖,而不會對一組范圍內(nèi)的數(shù)據(jù)(甚至是全表)加鎖。
給需要更新的表添加索引,覆蓋where條件里的字段,能夠一定程度上緩解鎖的碰撞概率,降低死鎖發(fā)生次數(shù)。
2、使用主鍵更新表
這個辦法與辦法1的原理是相同的,也是利用索引讓數(shù)據(jù)庫精確地加鎖。因為主鍵天然就是一個索引,所以不需要給表添加額外的索引。
3、縮短事務(wù)時長或取消事務(wù)
許多數(shù)據(jù)庫框架或庫都會提供事務(wù)管理機制,在默認情況下,它們采用AOP的方式來切入事務(wù)管理行為。
但是,這種事務(wù)管理行為是很粗糙的,并且會帶來一個問題:如果方法執(zhí)行的時間相當(dāng)長,那么這個事務(wù)也會持續(xù)相當(dāng)長的時間。事務(wù)越長也就意味著鎖的碰撞概率越大。
如果開發(fā)人員對框架和庫足夠熟悉,并且對數(shù)據(jù)庫事務(wù)有良好的理解,可以考慮手動控制事務(wù),并采用編程式的事務(wù)管理方法,讓應(yīng)用程序在需要訪問數(shù)據(jù)庫的時候才開啟事務(wù)。
或者,如果系統(tǒng)不強調(diào)瞬態(tài)一致性,也沒有并發(fā)訪問數(shù)據(jù)沖突,那么可以完全取消事務(wù)。
4、使用合理的編程范式
123方法只能緩解數(shù)據(jù)庫表鎖的碰撞概率,但不是解決死鎖問題的根本之道。
死鎖問題更深層次的原因是:應(yīng)用程序無序地訪問數(shù)據(jù)庫,流程管控不合理。
一個清晰又整潔的編程范式,即可以有效提高代碼的可讀性,又可以降低數(shù)據(jù)庫(以及外部服務(wù))的訪問次數(shù),達到較高的程序效能。
//1.收集數(shù)據(jù)
var requiredData = queryFromDatabase()
//2.處理數(shù)據(jù)
process(requiredData)
//3.持久化處理后的數(shù)據(jù)
saveToDatabase(requiredData)
另外,盡量不要在循環(huán)里訪問內(nèi)數(shù)據(jù)庫,在循環(huán)開始前收集所有必要的信息,在循環(huán)結(jié)束后統(tǒng)一持久化到數(shù)據(jù)庫。
5、使用緩存處理熱度數(shù)據(jù)
RMDB處理密集小事務(wù)的能力并不弱,但是如果并發(fā)處理的數(shù)據(jù)有重合,就會存在死鎖的隱患。如果要解決RMDB在這方面的問題,就要投入不少精力,優(yōu)化代碼流程,精細化控制事務(wù)。
如果使用緩存來處理熱度數(shù)據(jù),可以巧妙地避開RMDB在這方面的缺陷。
緩存可以使用本地緩存,也可以使用分布式緩存,由系統(tǒng)的設(shè)計和架構(gòu)決定。
6、其他方法
除了上面的5種方法,解決事務(wù)死鎖問題的辦法還有很多,例如:其他高級編程模型、特定的中間件支持,可以開放性思考。但是再多的優(yōu)化技巧,也不如設(shè)計一個高效的程序結(jié)構(gòu)。