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í)行。這個正好是我們想要的?。。?!好了,其他不多說了,上代碼吧?。?/p>
通過 SET語句,我們可以設(shè)置SESSION級別的值(圖 - 3)

3. 測試用例

4. 總結(jié)
這個問題,項(xiàng)目中存在有一段時間了,一直都沒有時間沉下心來研究,這周趁著項(xiàng)目空閑期終于搞出了這個方案,下周上線,線上觀察一段時間。
5. 附圖
有同學(xué)問及到有關(guān)方法的代碼,現(xiàn)黏貼一下:
lockAgain()方法

lockRowFor1000Seconds()方法
