2008-09-09 130 views
4

我有一個web應用程序,它使用JNDI查找來連接到數據庫。Java ConnectionPool連接沒有關閉,陷入'睡眠'

連接工作正常,並返回查詢沒有問題。問題是我們連接沒有正確關閉,並陷入'睡眠'模式(根據mysql管理員)。這意味着,如果我沒有連接,它們將變得無法使用。

有人可以給我幾個指針,我可以做些什麼來使連接成功返回到池。

public class DatabaseBean { 

private static final Logger logger = Logger.getLogger(DatabaseBean.class); 

private Connection conn; 
private PreparedStatement prepStmt; 

/** 
* Zero argument constructor 
* Setup generic databse connection in here to avoid redundancy 
* The connection details are in /META-INF/context.xml 
*/ 
public DatabaseBean() { 
    try { 
     InitialContext initContext = new InitialContext(); 
     DataSource ds = (DataSource) initContext.lookup("java:/comp/env/jdbc/mysite"); 
     conn = ds.getConnection(); 
    } 
    catch (SQLException SQLEx) { 
     logger.fatal("There was a problem with the database connection."); 
     logger.fatal(SQLEx); 
     logger.fatal(SQLEx.getCause()); 
    } 
    catch (NamingException nameEx) { 
     logger.fatal("There was a naming exception"); 
     logger.fatal(nameEx); 
     logger.fatal(nameEx.getCause()); 
    } 
} 

/** 
* Execute a query. Do not use for statements (update delete insert etc). 
* 
* @return A ResultSet of the execute query. A set of size zero if no results were returned. It is never null. 
* @see #executeUpdate() for running update, insert delete etc. 
*/ 

public ResultSet executeQuery() { 
    ResultSet result = null; 
    try { 
     result = prepStmt.executeQuery(); 
     logger.debug(prepStmt.toString()); 
    } 
    catch (SQLException SQLEx) { 
     logger.fatal("There was an error running a query"); 
     logger.fatal(SQLEx); 
    } 
    return result; 
} 

SNIP

public void close() { 
    try { 
     prepStmt.close(); 
     prepStmt = null; 

     conn.close(); 
     conn = null; 
    } catch (SQLException SQLEx) { 
     logger.warn("There was an error closing the database connection."); 
    } 
} 
} 

這是一個使用數據庫連接一個JavaBean內。

public LinkedList<ImportantNoticeBean> getImportantNotices() { 

    DatabaseBean noticesDBBean = new DatabaseBean(); 
    LinkedList<ImportantNoticeBean> listOfNotices = new LinkedList<ImportantNoticeBean>(); 

    try { 
     PreparedStatement preStmt = noticesDBBean.getConn().prepareStatement("SELECT pseudonym, message, date_to, date_from " + 
       "FROM importantnotices, users " + 
       "WHERE importantnotices.username = users.username " + 
       "AND NOW() >= date_from AND NOW() <= date_to;"); 

     noticesDBBean.setPrepStmt(preStmt); 
     ResultSet result = noticesDBBean.executeQuery(); 

     while (result.next()) { 
      ImportantNoticeBean noticeBean = new ImportantNoticeBean(); 

      noticeBean.setAuthor(result.getString("pseudonym")); 
      noticeBean.setMessage(result.getString("message")); 
      noticeBean.setDateTo(result.getDate("date_to")); 
      noticeBean.setDateFrom(result.getDate("date_from")); 

      listOfNotices.add(noticeBean); 
     } 

     result.close(); 

    } catch (SQLException SQLEx) { 
     logger.error("There was an error in ImportantNoticesBean.getImportantNotices()"); 
     logger.error(SQLEx); 
    } finally { 
     noticesDBBean.close(); 
    } 
    return listOfNotices; 
} 

<Context reloadable="true"> 

    <Resource name="jdbc/mysite" 
       auth="Container" 
       type="javax.sql.DataSource" 
       username="user" 
       password="password" 
       driverClassName="com.mysql.jdbc.Driver" 
       url="jdbc:mysql://localhost:3306/mysite" 
       maxActive="10" 
       maxIdle="5" 
       maxWait="6000" 
       removeAbandoned="true" 
       logAbandoned="false" 
       removeAbandonedTimeout="20" 
      /> 
</Context> 

回答

1

問題我們連接不正常關閉,並停留在「睡眠」模式

這實際上是隻對了一半。

我遇到的問題實際上是每個應用程序都定義了一個到數據庫服務器的新連接。所以每當我關閉所有的連接時,App A會根據它的WEB.xml配置文件創建一堆新的連接並且運行愉快。應用程序B也會這樣做。問題是他們是獨立池,它試圖抓住服務器定義的限制。我猜想這是一種競爭條件。因此,當應用程序A完成了連接後,它等待再次使用它們,直到超時過去,而現在需要連接的應用程序B被拒絕資源,即使應用程序A已經完成並且應該回到池中。一旦超時,連接就被釋放,B(或C等)可以再次獲得它。

例如如果限制爲10(mySQL配置文件限制),並且每個應用程序已配置爲使用最大值10,則會有20次連接嘗試。顯然這是一個糟糕的情況。

解決方案是RTFM並把connection details in the right place。這確實使共享發佈一個痛苦,但有辦法解決(例如從上下文鏈接到其他xml文件)。

只是爲了明確:我把每個應用程序的WEB.xml中的連接細節放在一起,並對此進行了爭論。

2

你似乎正確地關閉連接 - 除了其中prepStmt.close()拋出SQLException的情況下,我無法找到一個連接泄漏。

您使用的是什麼池實現?在關閉連接時,池不需要立即關閉底層的MySQL連接 - 畢竟這是連接池的關鍵點!所以從MySQL的角度來看,連接看起來還活着,儘管你的應用程序並沒有使用任何;他們可能只是被TC連接池佔用。

您可能想要試驗連接池的設置。當系統處於空閒狀態時請求縮小池。或者,要求它定期刷新所有連接。或者,對從MySQL等並行連接的數量有嚴格的上限。

檢查代碼是否存在連接泄漏的一種方法是強制ds.getConnection()始終打開一個新的物理connection和conn.close()釋放連接(如果連接池具有這些設置)。然後,如果您在MySQL端觀看連接,則可能會發現代碼是否確實存在連接泄漏。

0

@ binil錯過了一件事,如果發生異常,您沒有關閉結果集。根據驅動程序的實現情況,這可能會導致連接保持打開狀態。將result.close()調用移動到finally塊。

+0

我以爲RS在創建它的PS關閉時總是會關閉。 – 2008-09-10 17:03:31

+0

來自javadoc: 當生成它的Statement對象關閉,重新執行或用於從多個結果序列中檢索下一個結果時,ResultSet對象會自動關閉。 – Hyposaurus 2008-09-11 06:52:46

2

這是一個類似的問題 - Connection Pool Settings for Tomcat

這是我對這個問題的反應,它解決了這一問題的其他人。它也可以幫助你。

Tomcat Documentation

DBCP使用Jakarta-共享數據庫連接池。它依賴於雅加達共享組件的數量:

* Jakarta-Commons DBCP 
* Jakarta-Commons Collections 
* Jakarta-Commons Pool 

我使用的是相同的連接池的東西,我設置這些屬性,以防止它只是沒有通過Tomcat的配置是一樣的。 但如果第一件事不起作用,請嘗試這些。

testWhileIdle=true 
timeBetweenEvictionRunsMillis=300000 
2

好吧我可能會有這種排序。我已將數據庫配置資源更改爲以下內容:

*SNIP* 
maxActive="10" 
maxIdle="5" 
maxWait="7000" 
removeAbandoned="true" 
logAbandoned="false" 
removeAbandonedTimeout="3" 
*SNIP* 

這對於目前來說已經足夠好了。發生什麼,afaik,是一旦我達到了十個連接,那麼Tomcat正在檢查被放棄的連接(空閒時間> 3)。它在每次達到最大連接時在批處理作業中執行此操作。這個潛在的問題是,如果我需要同時運行超過10個查詢(不是唯一的)。重要的是removeAbandonedTimeout小於maxWait。

這是應該發生什麼?這是泳池應該運作的方式嗎?如果看起來,至少對我來說,你會等到某些事情(連接)在修復之前被破壞,而不是首先讓它「破壞」。也許我還沒有得到它。

0

我使用與您相同的配置。如果mysql管理員(Windows)中的連接顯示它處於睡眠模式,則它僅表示已合併但未使用。我用多線程對Mysql進行隨機查詢來檢查這個運行測試程序的程序。如果它幫助這裏是我的配置:

 defaultAutoCommit="false" 
     defaultTransactionIsolation="REPEATABLE_READ" 
     auth="Container" 
     type="javax.sql.DataSource" 
     logAbandoned="true" 
      removeAbandoned="true" 
     removeAbandonedTimeout="300" 
     maxActive="-1" 
     initialSize="15" 
     maxIdle="10" 
     maxWait="10000" 
     username="youruser" 
     password="youruserpassword" 
     driverClassName="com.mysql.jdbc.Driver" 
     url="jdbc:mysql://yourhost/yourdatabase"/>