系統(tǒng)中出現(xiàn)過幾次connection holder is null問題舵稠,有的已解決深员,有的未解決,記錄如下干像。
首先說先druid連接池的實(shí)現(xiàn):
DruidPooledConnection是一個(gè)靜態(tài)代理帅腌,持有ConnectionHolder, connection Holder里持有具體的connection對(duì)象, 在執(zhí)行druidPooledConnection的所有和數(shù)據(jù)庫相關(guān)方法時(shí)麻汰,都會(huì)先調(diào)用checkState()判斷connection holder是否為null速客,如果是null就拋connection holder is null的異常。
那connection holder是什么時(shí)候賦值以及什么時(shí)候置成null的五鲫?
在Datasource.getConnection()獲取連接的時(shí)候溺职,是從池里取出緩存的connection holder對(duì)象,druid是用一個(gè)數(shù)組緩存connection holder對(duì)象,每次都是從最后一個(gè)取浪耘,還的時(shí)候也是放到最后乱灵,這樣保證位于數(shù)組最后的連接會(huì)經(jīng)常處于使用狀態(tài),當(dāng)然這中間會(huì)有鎖的使用以及池里沒線程了通知任務(wù)線程去創(chuàng)建新連接七冲。
Datasource.getConnection()從池里拿出connection holder后痛倚,然后new一個(gè)druidPooledConnection去包裝connection holder,所有每次看到都是不同的druidPooledConnection對(duì)象澜躺。
第一次:系統(tǒng)中事務(wù)執(zhí)行時(shí)間過長蝉稳,超過60秒,后面導(dǎo)致有的請(qǐng)求會(huì)報(bào)connection holder is null掘鄙。
拿出來的connection holder肯定不為null颠区,項(xiàng)目中報(bào)connection holder is null,說明是在使用過程中connection holder被置成null了通铲,很大概率是被別的線程置成null了,因?yàn)楸揪€程只有在事務(wù)提交后還連接的時(shí)候才置null器贩,在github issue上颅夺,作者也反復(fù)強(qiáng)調(diào)連接不要跨線程使用。而druid真的就有跨線程操作連接的地方蛹稍,就是remove abandoned connection功能吧黄,這個(gè)功能是為了回收長時(shí)間還沒還到池里的連接,多長時(shí)間看你設(shè)置唆姐,而我們項(xiàng)目設(shè)置的60秒沒還就強(qiáng)制回收拗慨,這樣就會(huì)報(bào)上面的錯(cuò)誤了。
建議在生產(chǎn)環(huán)境關(guān)閉remove abandoned功能奉芦,如果數(shù)據(jù)庫負(fù)載不重的話赵抢,可以開啟testOnBorrow。 testWhileIde不建議開声功,因?yàn)椴l(fā)請(qǐng)求多的話烦却,數(shù)組后面的連接都不是idle狀態(tài),開沒開testWhileIdle沒啥區(qū)別先巴。
第二次系統(tǒng)中有的事務(wù)長時(shí)間未提交其爵,DBA會(huì)把這個(gè)連接kill掉,后面請(qǐng)求會(huì)報(bào)conneciton holder is null
- 為什么有長時(shí)間未提交的事務(wù)伸蚯,這個(gè)問題還沒找到原因摩渺,從Mysql的innodb_trx和lock表里沒看到有價(jià)值線索,后面想跟蹤事務(wù)和連接來看看有沒有收獲剂邮。
- 連接被kill了摇幻,應(yīng)用端會(huì)報(bào)這樣的異常:
- Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.
- com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure. The last packet successfully received from the server was 20,840 milliseconds ago. The last packet sent successfully to the server was 20,840 milliseconds ago.
- connection holder is null
前面2個(gè)異常很好理解,tcp連接斷了,應(yīng)用端讀不到數(shù)據(jù)報(bào)錯(cuò)囚企,然后druid捕獲到異常丈咐,要去判斷這個(gè)異常是可恢復(fù)異常還是不可恢復(fù)異常。因?yàn)檎驹谶B接池的角度來說龙宏,數(shù)據(jù)庫拋異常太普遍了棵逊,可能是唯一索引重復(fù)也可能是連接斷了,對(duì)于不同的異常處理方式也是不一樣的银酗,唯一索引重復(fù)需要調(diào)用connection.rollback()辆影,然后再把連接還到池里,因?yàn)檫@個(gè)連接還是好的黍特,不影響下次繼續(xù)使用蛙讥。而連接斷了,則要把這個(gè)連接踢出去灭衷,druid用了ExceptionSorter來判斷這個(gè)異常是不是不可恢復(fù)異常次慢,在轉(zhuǎn)換異常的時(shí)候要用當(dāng)前連接獲取數(shù)據(jù)庫的metadata,而當(dāng)前連接已經(jīng)斷了翔曲,所以報(bào)connection holder is null迫像。
但是這個(gè)connection holder is null只會(huì)報(bào)一次,和項(xiàng)目中大量報(bào)connection holder is null不是一個(gè)東西瞳遍,目前還沒找到原因闻妓。而這個(gè)問題在本地卻重現(xiàn)不了。
PS:數(shù)據(jù)庫有一個(gè)設(shè)置 rollback_on_timeout掠械,默認(rèn)是off由缆,這個(gè)值是說當(dāng)事務(wù)超時(shí)(如超過50秒還沒獲取到鎖),默認(rèn)off是回滾最后一條sql語句猾蒂,on是回滾整個(gè)事務(wù)均唉。這個(gè)值一般不需要設(shè)置成on,交由應(yīng)用去處理肚菠,應(yīng)用在獲取不到 can't acquire lock的時(shí)候浸卦,一般會(huì)去調(diào)connection.rollback(),當(dāng)然前提是要你的應(yīng)用開啟事務(wù)案糙。