記一次Mysql連接未滿但程序卻報錯連接已滿獲取連接超時GetConnectionTimeoutException的問題

1.問題描述

今天同事找到我,讓我幫忙查一個問題,據說已經持續一個月:

  • 之前服務正常,問題在上個月開始出現。
  • 服務運行大概1天左右,後臺開始報錯:獲取數據庫連接失敗GetConnectionTimeoutException
  • 服務配置的最大連接數maxActive=600
  • 但是,此時通過show processlist卻顯示此服務實際只佔用了300~400左右的連接。
  • 服務重啓,問題暫時消失。

2.報錯信息

下面是服務報錯信息:

  • 替換掉了部分公司敏感信息。
  • 出錯的sql隨機,可能是UserDao.xml相關的,也可能是OrderDao.xml相關的。
2019-08-27 13:31:10,200 276111 ERROR pers.hanchao.server.UserServices:125 -  
Exception: ### Error querying database.  Cause: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10000, active 600, maxActive 600, creating 0 
Exception: ### The error may exist in mybatis/UserDao.xml 
Exception: ### The error may involve pers.hanchao.dao.PostExtraTitleDao.getByPostID 
Exception: ### The error occurred while executing a query 
Exception: ### Cause: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10000, active 600, maxActive 600, creating 0 
Exception: org.apache.ibatis.exceptions.PersistenceException:  
### Error querying database.  Cause: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10000, active 600, maxActive 600, creating 0 
### The error may exist in mybatis/PostExtraTitleDao.xml 
### The error may involve pers.hanchao.dao.PostExtraTitleDao.getByPostID 
### The error occurred while executing a query 
### Cause: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10000, active 600, maxActive 600, creating 0 
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:26) 
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:111) 
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:102) 
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:119) 
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:63) 
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:52) 
at com.sun.proxy.$Proxy54.getByID(Unknown Source) 
...

3.問題分析

  • 出錯的sql是隨機的,所以應該與業務邏輯無關。
  • druid既然已經報錯active 600, maxActive 600,則說明:當前druid確實認爲還未釋放的連接有600個。
  • 通過show processlist顯示實際連接300~400個,則說明:當前服務實際存活的連接有300~400個。
  • 發送上述情況,應該是有代碼:
    • 通過druid獲取了連接,但是未釋放連接,所以druid認爲有600個鏈接正在使用。
    • 部分未釋放的連接在超過一定時間後,被mysql主動釋放,所以服務實際存活的連接有300~400個。
    • mysql釋放連接並不會告訴druid,所以druid仍然認爲有600個活連接。
  • 出問題的服務,有部分老代碼使用的是手動獲取和釋放SqlSession的邏輯,所以存在連接泄漏的可能。

4.問題定位

查詢全部代碼中獲取連接的地方,確定是否正確是否了連接。

正確寫法(僞代碼):

////方式一:try-catch主動close
public void func(){
	SqlSession sqlSession = null;
	try{
	  //從工具類獲取連接
	  sqlSession = getSqlSession();
	  //業務代碼...
	}finally{
	  if(Objects.nonNull(sqlSession)){
	    sqlSession.close();
	  }
	}
}

////方式二:try-resource自動close
public void func(){
	try(SqlSession sqlSession = getSqlSession()){
	  //業務代碼...
	}
}

錯誤寫法(僞代碼):

////錯誤一:只獲取不釋放
public void func(){
  SqlSession sqlSession = getSqlSession();
	//業務代碼...
}

////錯誤二:錯誤的try-catch釋放方式
//從工具類獲取連接
public void func(){
	SqlSession  sqlSession = getSqlSession();
	try{
	  //業務代碼...
	}finally{
	  if(Objects.nonNull(sqlSession)){
	    sqlSession.close();
	  }
	}
}

5.問題解決

最終卻是發現一個緩存刷新服務,存在錯誤寫法,類似第4章的錯誤一:只獲取不釋放

這個服務,每5分鐘獲取1個連接,1小時獲取12個,1天獲取240個鏈接,再加上真實存在的300~400個連接,確實湊夠了600個連接。

基本可以確認問題由此出代碼產生。

修改代碼,上線,問題解決。

6.問題總結

  • 這個問題產生的原因有兩個:1.項目框架太老,需要手動釋放。2.開發人員不瞭解老框架。
  • 本次問題解決比較順利,有時候好運氣也是解決問題的重要助力。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章