數據庫應用開發過程當中,咱們可能會遇到一個問題:應用使用了數據庫鏈接池,每通過指定時間後,發出到數據庫服務器的任何請求都會失敗,並且有且僅有一次失敗,以後的正常訪問都沒有問題。尤爲是在Web應用中,若是晚上時段沒有訪問,而次日第一個訪客的經歷就是碰到一個數據庫訪問錯誤,若是開發系統的程序員沒有注意這個問題的話,可能終端用戶訪問會看到拋出的一堆數據庫異常信息。java
其實,這個問題的主要緣由是,應用中數據庫鏈接池中會保存指定數量的數據庫鏈接實例,而這些鏈接實例並無定時地檢測其到數據庫服務器鏈接是否正常;數據庫服務器能夠配置一個數據庫鏈接實例的超時時間,超過期間後它會自動斷開鏈接。也就是,被斷開的那個鏈接此時仍然保存在應用的數據庫鏈接池內,下次被使用的時候就會發生數據庫鏈接斷開而致使一次訪問失敗。mysql
解決上述鏈接關閉的方案有兩種值得推薦:程序員
若是可以提供這樣一種檢測機制,在應用的鏈接池管理中定時地檢測鏈接池中鏈接的有效性,就徹底能夠避免上面描述的問題。sql
在應用代碼中經過異常處理機制,來實現該次業務的從新處理,也能夠很好地避免。shell
咱們舉一個例子,使用Java開發的Web系統,Tomcat做爲HTTP服務器,MySQL做爲數據庫,拋出異常的信息以下所示:數據庫
[http-bio-8080-exec-10] 2012-11-28 00:55:43 [org.shirdrn.wm.de.action.StatAction]-[WARN] com.ibatis.dao.client.DaoException: Error ending SQL Map transaction. Cause: java.sql.SQLException: Already closed. at com.ibatis.dao.engine.transaction.sqlmap.SqlMapDaoTransaction.rollback(SqlMapDaoTransaction.java:51) at com.ibatis.dao.engine.transaction.sqlmap.SqlMapDaoTransactionManager.rollbackTransaction(SqlMapDaoTransactionManager.java:85) at com.ibatis.dao.engine.impl.DaoContext.endTransaction(DaoContext.java:112) at com.ibatis.dao.engine.impl.DaoProxy.invoke(DaoProxy.java:77) at $Proxy8.selectByExample(Unknown Source) at org.shirdrn.wm.de.service.impl.StatItemsServiceImpl.countItems(Unknown Source) at org.shirdrn.wm.de.action.StatAction.makeStat(Unknown Source) at org.shirdrn.wm.de.action.StatAction.doGet(Unknown Source) at javax.servlet.http.HttpServlet.service(HttpServlet.java:621) at javax.servlet.http.HttpServlet.service(HttpServlet.java:722) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:987) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:579) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:307) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:619) Caused by: java.sql.SQLException: Already closed. at org.apache.commons.dbcp.PoolableConnection.close(PoolableConnection.java:84) at org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper.close(PoolingDataSource.java:181) at com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransaction.close(JdbcTransaction.java:81) at com.ibatis.sqlmap.engine.transaction.TransactionManager.end(TransactionManager.java:93) at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.endTransaction(SqlMapExecutorDelegate.java:734) at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.endTransaction(SqlMapSessionImpl.java:176) at com.ibatis.sqlmap.engine.impl.SqlMapClientImpl.endTransaction(SqlMapClientImpl.java:153) at com.ibatis.dao.engine.transaction.sqlmap.SqlMapDaoTransaction.rollback(SqlMapDaoTransaction.java:49) ... 25 more
咱們經過上面給出的第二種方案來解決,對應異常中實現的代碼,進行異常處理的邏輯以下所示:apache
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { boolean retry = false; String type = request.getParameter(RequestParams.ITEM_TYPE); String top = request.getParameter(RequestParams.TOP_N); Byte itemType = Byte.parseByte(type); Integer topN = super.topN; if(top!=null) { try { topN = Integer.parseInt(top); } catch (Exception e) {} } Target target = itemNames.get(itemType); try { makeStat(request, response, itemType, topN, target); } catch (Exception e) { LOG.warn("", e); // com.ibatis.dao.client.DaoException: Error ending SQL Map transaction. Cause: java.sql.SQLException: Already closed. if(!retry && e instanceof DaoException) { LOG.warn("Try to obtain database connection again."); retry = true; this.makeStat(request, response, itemType, topN, target); } else { response.sendError(500, e.toString()); return; } } request.getRequestDispatcher(target.url).forward(request, response); } private void makeStat(HttpServletRequest request, HttpServletResponse response, Byte itemType, Integer topN, Target target) throws IOException, ServletException { List<StatItems> items = statItemsService.countItems(itemType, new Date(), topN); for (StatItems statK : items) { if(statK.getItemName()!=null && !"null".equalsIgnoreCase(statK.getItemName())) { pieDataset.setValue(statK.getItemName().trim() + " (" + statK.getPercentage() + ")", statK.getItemValue()); } } String imageUrl = super.generateImage(pieDataset, target.title, request); request.setAttribute("items", items); request.setAttribute("imageUrl", imageUrl); if(items!=null && !items.isEmpty() && items.size()<topN) { topN = items.size(); } request.setAttribute("topN", topN); }
上面代碼,判斷若是是發生鏈接失敗,則保存請求參數,再從新處理該請求。tomcat
另外一種不推薦的方案,就是修改數據庫服務器的鏈接超時配置。由於在實際項目中,一般應用上線的相關人員未必是DBA,對於修改數據庫服務器的配置可能會給其它上線業務帶來風險。解決方法以下:服務器
以MySQL爲例,查看文件/etc/my.cnf,查詢有關超時配置的參數:app
mysql> show variables like '%timeout'; +----------------------------+----------+ | Variable_name | Value | +----------------------------+----------+ | connect_timeout | 10 | | delayed_insert_timeout | 300 | | innodb_lock_wait_timeout | 50 | | innodb_rollback_on_timeout | OFF | | interactive_timeout | 28800 | | lock_wait_timeout | 31536000 | | net_read_timeout | 30 | | net_write_timeout | 60 | | slave_net_timeout | 3600 | | wait_timeout | 28800 | +----------------------------+----------+
咱們能夠在屬性組mysqld下面修改以下兩個參數:
interactive_timeout
wait_timeout
MySQL數據庫服務器配置的鏈接超時時間默認是8小時,若是修改的超時時間足夠長的話,就不會出現前面發生的鏈接斷開的問題。可是,若是有不少應用都在使用數據庫鏈接池,大量的數據庫鏈接資源一直被佔用,嚴重的話可能使數據庫服務器宕機,並且,也會使一些攻擊者僞造大量請求,使數據庫服務器負荷過載而宕機,從而影響應用處理業務。