1. 問題
最近由于業(yè)務(wù)的需要冬竟,寫了個基于數(shù)據(jù)庫鎖機(jī)制的分布式調(diào)度簡易框架,用于處理業(yè)務(wù)中的補(bǔ)償任務(wù)和定時輪詢?nèi)蝿?wù)卦溢。由于調(diào)度任務(wù)處理時間長短不一糊余,有些是幾秒處理完,而有些需要好幾分鐘单寂。對于處理時間較長的任務(wù)贬芥,在事務(wù)中,行鎖會一直占據(jù)直到事務(wù)處理結(jié)束宣决。
那么問題就來了蘸劈,對于時間處理較長的任務(wù),再下一輪調(diào)度起來之前事務(wù)還沒處理完疲扎,這個時候昵时,下個事務(wù)會在行鎖上一直等待下去捷雕,直到timeout椒丧。這樣會占據(jù)多個線程、消耗多數(shù)數(shù)據(jù)庫資源救巷。
2. 解決思路壶熏、方案
思路也很簡單,當(dāng)檢測到數(shù)據(jù)庫行鎖被占據(jù)時浦译,當(dāng)前線程立馬結(jié)束棒假,不進(jìn)行鎖釋放等待,這樣既節(jié)約線程資源也節(jié)省數(shù)據(jù)庫資源精盅。
我們知道帽哑,MySQL8是支持 'SELECT ... for update NOWAIT' 這種寫法的,但是對于5.x版本這種寫法并不支持(圖 - 1)叹俏,恰巧我們用的是5.6版本妻枕,我們需要另外想辦法來解決。
思路1: 在MYSQL系統(tǒng)變量里面(global variable, session variable)中有特定的變量用來控制這個行鎖超時等待時間粘驰,我們可以修改這變量值屡谐,來使得我們的等待超時時間縮短
思路2: 比較好的方案,我們只修改當(dāng)前會話的蝌数,而不修改全局變量值(有可能多個程序在連你的數(shù)據(jù)庫)
思路3: 到底哪個變量控制這行鎖的超時等待時間呢愕掏?通過網(wǎng)上查詢,我們可以知道有一個叫 'innodb_lock_wait_timeout' 的變量正是我們要尋找的東東顶伞。 MySQL對于超時相關(guān)的變量有很多饵撑,大家可以查詢information_scheme.global/session_variables表 (5.7版本略微不同剑梳,需要在performance_schema去查詢):
select * from information_schema.global_variables where variable_name like '%timeout%';
你會看到有很多相關(guān)的變量,每個變量的含義肄梨,大家可以網(wǎng)上查找一下阻荒,了解一下很有必要!众羡!這里我們只關(guān)心 「innodb_lock_wait_timeout」這個變量侨赡,默認(rèn)值是 50秒,表示: 行鎖等待超時時間是50秒粱侣。
好了羊壹,問題根源我們也找到,現(xiàn)在的問題是怎么做齐婴?油猫!
思路1:數(shù)據(jù)庫的操作流程其實(shí)也挺直接明了的:
定義DataSource -> 從DS池中獲取Connection -> 通過connection執(zhí)行SQL
涉及到事務(wù)的,其實(shí)也就是在第二步獲取connection后在執(zhí)行第三部前柠偶,執(zhí)行事務(wù)相關(guān)操作(在動態(tài)代理中)情妖,比如:執(zhí)行 begin語句(start transaction語句效果也一樣)
思路2:我們能否在Connection創(chuàng)建的時候,就把 innodb_lock_wait_timeout的值給修改了呢诱担?答案是可以的毡证。
Springboot中默認(rèn)的數(shù)據(jù)庫連接池用的是 Hikari,而它剛好有可配置屬性:connectionInitSql蔫仙,通過代碼跟蹤料睛,可以知道這個SQL在創(chuàng)建connection的時候會被執(zhí)行。這個正好是我們想要的R“睢P羯贰!施籍!好了居扒,其他不多說了,上代碼吧3笊鳌喜喂!
通過 SET語句,我們可以設(shè)置SESSION級別的值(圖 - 3)
3. 測試用例
4. 總結(jié)
這個問題立哑,項(xiàng)目中存在有一段時間了夜惭,一直都沒有時間沉下心來研究,這周趁著項(xiàng)目空閑期終于搞出了這個方案铛绰,下周上線诈茧,線上觀察一段時間。
5. 附圖
有同學(xué)問及到有關(guān)方法的代碼捂掰,現(xiàn)黏貼一下:
lockAgain()方法
lockRowFor1000Seconds()方法