在應用端經過mybatis的interceptor自定義Plugin攔截Executor, 統計輸出sql的執行耗時。html
今天生產發生一個很奇怪的問題: 莫名其妙卡頓15分鐘+,其後正常返回sql正常結果! 使用druid版本是1.0.2。。。。。java
統計發現:mysql
推測是不是在getConnection過程當中出現了等待。但因獲取鏈接時的日誌過少找不到具體的緣由!git
網上的相似問題說的緣由是Druid在從鏈接池中獲取的鏈接(代碼詳見:DruidDataSource的getConnectionDirect),在開啓testWhileIdle時,若是活躍時間距離當前比較久,那麼將驗證該鏈接是否有效,無效會discardConnection並再次去pool內獲取。github
這個過程可能被數據庫層面的防火牆策略已經關閉該鏈接,而客戶端還傻傻的發sql驗證直到超時。。。。spring
在該測試鏈接的方法中有一個查詢超時超時時間:validationQueryTimeout。測試中是-1, 執行的是默認時間。。。。這個默認時間沒有找到!! (有找到MappedStatementConfig由SqlMapConfiguration的defaultStatementTimeout)sql
應用與數據庫間的timeout層級實例:
轉帖: 深刻理解JDBC的超時設置
數據庫
上層的timeout依賴於下層的timeout,只有下層的timeout無誤時,上層的timeout才能確保正常。eg. 當socket timeout出現問題時,上層的statement timeout和transaction timeout都將失效。
statement timeout 沒法處理網絡鏈接失敗時的超時,它能作的僅僅是限制statement的操做時間;網絡鏈接失敗時的timeout必須交由JDBC來處理;JDBC的socket timeout會受到操做系統socket timeout設置的影響。api
Transaction Timeout: 通常存在於框架(Spring, EJB)或應用級。spring在配置事務切面的時候能夠配置timeout。服務器
Statement Timeout: 經過調用JDBC的java.sql.Statement.setQueryTimeout(int timeout) API進行設置。 以myBatis爲例,statement timeout的默認值能夠經過sql-map-config.xml中的defaultStatementTimeout 屬性進行設置。同時,也能夠設置sqlmap中select,insert,update標籤的timeout屬性,從而對不一樣sql語句的超時時間進行獨立的配置。
JDBC的statement timeout: 經過調用Connection的createStatement()方法建立statement來executeQuery時,會註冊一個 TimerTask ->CancelTask 用來給Timer -> ConnectionImpl.getCancelTimer()來處理timeout事項:發送 "KILL QUERY」 。 代碼詳見: StatementImpl 及 ConnectionImpl
JDBC的socket timeout:
因爲TCP/IP的結構緣由,socket沒有辦法探測到網絡錯誤,所以應用也沒法主動發現數據庫鏈接斷開。若是沒有設置socket timeout的話,應用在數據庫返回結果前會無期限地等下去,這種鏈接被稱爲dead connection。 爲了不dead connections,socket必需要有超時配置。socket timeout能夠經過JDBC設置,socket timeout可以避免應用在發生網絡錯誤時產生無休止等待的狀況,縮短服務失效的時間。不推薦使用socket timeout來限制statement的執行時長,所以socket timeout的值必需要高於statement timeout,不然,socket timeout將會先生效,這樣statement timeout就變得毫無心義,也沒法生效。
eg. jdbc:mysql://xxx.xx.xxx.xxx:3306/database?connectTimeout=60000&socketTimeout=60000
操做系統的socket timeout配置
在該文章中說Statement.setQueryTimeout : 若是設置的超時時間過長,在當大批量的SQL同時執行時,每個SQL都會建立一個CancelTask對象,雖然很快執行完,且會調用CancelTask.cancel()方法,可是CancelTask方法的源代碼僅僅是將本身的狀態修改成:CANCELLED,而並不會直接從隊列中移除這個對象, 致使OOM。 建議在cancel()後須要purge一下。
解: MySQL Connector Java 5.1.44 版本 的 StatementImpl 已經有了purge; 且 在1.8版本的JDK對Timer的cancel()也增長了移除queue內容
Timer
public int purge() { int result = 0; synchronized(queue) { for (int i = queue.size(); i > 0; i--) { if (queue.get(i).state == TimerTask.CANCELLED) { queue.quickRemove(i); result++; } } if (result != 0) queue.heapify(); } return result; } } public void cancel() { synchronized(queue) { thread.newTasksMayBeScheduled = false; queue.clear(); queue.notify(); // In case queue was already empty. } }
經過網上blog查找發現有類似的問題存在:
https://www.oschina.net/question/1450045_2157629
在issues中搜索druid的testWhileIdle問題: https://github.com/alibaba/druid/issues/1260