【轉】MySQL連接超時斷開的問題


 

這遍文章轉過來做個筆記,時不時看看。

轉:http://blog.csdn.net/nethibernate/article/details/6658855

Exception如下:

 

[html]  view plain copy
 
 
  1. org.hibernate.util.JDBCExceptionReporter - SQL Error:0, SQLState: 08S01  
  2. org.hibernate.util.JDBCExceptionReporter - The last packet successfully received from the server was43200 milliseconds ago.The last packet sent successfully to the server was 43200 milliseconds ago, which is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection 'autoReconnect=true' to avoid this problem.  
  3. org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session  
  4. org.hibernate.exception.JDBCConnectionException: Could not execute JDBC batch update  
  5. com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Connection.close() has already been called. Invalid operation in this state.  
  6. org.hibernate.util.JDBCExceptionReporter - SQL Error:0, SQLState: 08003  
  7. org.hibernate.util.JDBCExceptionReporter - No operations allowed after connection closed. Connection was implicitly closed due to underlying exception/error:  
  8.   
  9. ** BEGIN NESTED EXCEPTION **  
  10. com.mysql.jdbc.exceptions.jdbc4.CommunicationsException  

先說一下發生這個Exception的大致原因:

 

MySQL的配置中,有一個叫做“wait_timeout"的參數,這個參數大致的意思是這樣:當一個客戶端連接到MySQL數據庫后,如果客戶端不自己斷開,也不做任何操作,MySQL數據庫會將這個連接保留"wait_timeout"這么長時間(單位是s,默認是28800s,也就是8小時),超過這個時間之后,MySQL數據庫為了節省資源,就會在數據庫端斷開這個連接;當然,在此過程中,如果客戶端在這個連接上有任意的操作,MySQL數據庫都會重新開始計算這個時間。

這么看來,發生上面Exception的原因就是因為我的服務器和MySQL數據庫的連接超過了”wait_timeout"時間,MySQL服務器端將其斷開了,但是我的程序再次使用這個連接時沒有做任何判斷,所以就掛了。

那這個問題怎么解決呢?

在想解決方案的過程中,我發現了幾個讓我不着頭緒的問題:

第一個問題:我們的服務器曾經在設計的過程中考慮過這個事情,所以服務器的主線程有一個定時的check機制,每隔半小時會發送一個"select 1"到數據庫來保證連接是活動的,為什么這個check機制不起作用了呢?

第二個問題:從上面的Exception中可以得到這么一個信息:

[java]  view plain copy
 
 
  1. The last packet sent successfully to the server was 43200 milliseconds ago, which is longer than the server configured value of 'wait_timeout'.  
這個信息說的很明白,最后一個成功發到Server的包是43200毫秒之前。但是43200毫秒才43.2秒,也就是說我們的服務器43.2秒之前才和MySQL服務器通過信,怎么會發生超過”wait_timeout“的問題呢?而且MySQL數據庫的配置也確實是28800秒(8小時),這又是神馬情況呢?

 

在網上google了n長時間,倒是有不少關於這個問題的討論,但是一直沒有找到讓我覺得有效的方法,只能自己結合google到結果來慢慢琢磨了。

首先,MySQL數據庫那邊的解決方案很單一,就是延長”wait_timeout“的數值。我看有的人直接就延長到一年了,也有人說這個值最大也就是21天,即使值設的再大,MySQL也就只識別21天(這個我沒有具體去MySQL的文檔中去查)。但是這是一個治標不治本的方法,即使可以一年,也還是會有斷的時候,服務器可是要7x24小時在線的呀。

既然MySQL數據庫那邊沒什么好方法,接下來就只能從程序這邊來搞了。

先說說程序這邊的大致結構吧:兩個線程,一個線程負責查詢和上面說到的check機制,另一個線程負責定時更新數據庫,使用hibernate,配置很簡單,都是最基本的,沒有任何關於連接池和緩存的配置,就像下面這樣:

[html]  view plain copy
 
 
  1. <session-factory>  
  2.     <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>  
  3.     <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>  
  4.     <property name="hibernate.connection.useUnicode">true</property>  
  5.     <property name="hibernate.connection.characterEncoding">UTF-8</property>  
  6.     <property name="hibernate.show_sql">true</property>  
  7.     <!-- 以下就全是mapping了,省略 -->  
  8. </session-factory>  

程序中更新的過程大致是這樣:
[java]  view plain copy
 
 
  1. session = org.hibernate.SessionFactory.openSession();  
  2. transaction = session.beginTransaction();  
  3. session.update(something);  
  4. transaction.commit();  
  5. session.close();  
在這里,所有關於數據庫Connection的連接和關閉都在Hibernate中,因此,不去挖掘Hibernate的源碼是不可能了。

 

在挖掘Hibernate源碼之前,必須明確目標:挖掘什么?

其實我的目標很明確,既然斷開連接是MySQL數據庫做的,那么相對於我們程序這邊的問題就是我們在使用完連接之后沒有調用Connection.close(),才會保留一個長連接在那里。那么,Hibernate是什么時候開啟這個連接,又什么時候調用Connection.close()的呢?

接下來就是Hibernate的源碼挖掘中。。。

枯燥的過程就不說了,說說挖掘出的東西:

Hibernate(忘了說了,我們用的Hibernate版本是3.3.2)在上面的那種配置之下,會有一個默認的連接池,名字叫:DriverManagerConnectionProvider;這是一個極其簡單的連接池,默認會在池中保留20個連接,這些連接不是一開始Hibernate初始化時就創建好的,而是在你需要使用連接時創建出來,使用完之后才加入到池中的。這里有一個叫closeConnection(Connection conn)的方法,這個方法很NB,它直接將傳入的連接不做任何處理,放到池中。而這個類內部的連接池實際是一個ArrayList,每次取得時候remove掉ArrayList的第一個連接,用完后直接用add方法加入到ArrayList的最后。

我們的程序更新時,Hibernate會通過DriverManagerConnectionProvider得到一個連接Connection,在使用完之后,調用session.close()時,Hibernate會調用DriverManagerConnectionProvider的closeConnection方法(就是上面說的那個NB方法),這個時候,該連接會直接放到DriverManagerConnectionProvider的ArrayList中,從始至終也沒有地方去調用Connection的close方法。

說到這里,問題就很明顯了。

第一,我們的那個”select 1“的check機制和我們服務器程序中更新的邏輯是兩個線程,check機制工作時,它會向DriverManagerConnectionProvider獲取一個連接,而此時更新邏輯工作時,它會向DriverManagerConnectionProvider獲取另外一個連接,兩個邏輯工作完之后都會將自己獲得的連接放回DriverManagerConnectionProvider的池中,而且是放到那個池的末尾。這樣,check機制再想check這兩個連接就需要運氣了,因為更新邏輯更新完之后就把連接放回池中了,而更新邏輯是定時的,check機制也是定時的,兩個定時機制如果總是能錯開,那么check機制check的永遠都是兩個中的一個連接,另外一個就麻煩了。這也就是為什么check機制不好使的原因。

第二,關於Exception信息中那個43200毫秒的問題也就能說明白了,check機制check的總是一個連接,而另外一個過期的連接被更新線程拿跑了,並且在check機制之后沒多久就有更新發生,43200毫秒恐怕就是它們之間的間隔吧。

到這里問題分析清楚了,怎么解決呢?

最容易想到的方案,也是網上說的最多的方案,就是延長MySQL端”wait_timeout“的時間。我說了,治標不治本,我覺得不爽,不用。

第二個看到最多的就是用”autoReconnect = true"這個方案,郁悶的是MySQL 5之后的數據庫把這個功能給去了,說會有副作用(也沒具體說有啥副作用,我也懶得查),我們用的Hibernate 3.3.2這個版本也沒有autoReconnect這個功能了。

第三個說的最多的就是使用c3p0池了,況且Hibernate官網的文檔中也提到,默認的那個連接池非常的屎,僅供測試使用,推薦使用c3p0(讓我郁悶的是我連c3p0的官網都沒找到,只在sourceForge上有個項目主頁)。好吧,我就決定用c3p0來搞定這個問題。

用c3p0解決這個Exception問題:(點擊這里進入我參考的博客,要翻牆哦,親!)

首先很明了,只要是池它就肯定有這個問題,除非在放入池之前就把連接關閉,那池還頂個屁用。所以我參考的博客里說到,最好的方式就是在獲取連接時check一下,看看該連接是否還有效,即該Connection是否已經被MySQL數據庫那邊給關了,如果關了就重連一個。因此,按照這個思路,我修正了Hibernate的配置文件,問題得到了解決:

 

[html]  view plain copy
 
 
  1. <session-factory>  
  2.     <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>  
  3.     <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>  
  4.     <property name="hibernate.connection.useUnicode">true</property>  
  5.     <property name="hibernate.connection.characterEncoding">UTF-8</property>  
  6.     <property name="hibernate.show_sql">true</property>  
  7.     <!-- c3p0在我們使用的Hibernate版本中自帶,不用下載,直接使用 -->  
  8.     <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>  
  9.     <property name="hibernate.c3p0.min_size">5</property>  
  10.     <property name="hibernate.c3p0.max_size">20</property>  
  11.     <property name="hibernate.c3p0.timeout">1800</property>  
  12.     <property name="hibernate.c3p0.max_statements">50</property>  
  13.     <property name="hibernate.c3p0.maxIdelTime">1800</property> 
  14.     <!-- 下面這句很重要,后面有解釋 -->  
  15.     <property name="hibernate.c3p0.testConnectionOnCheckout">true</property>  
  16.     <!-- 以下就全是mapping了,省略 -->  
  17. </session-factory>  

 

上面配置中最重要的就是hibernate.c3p0.testConnectionOnCheckout這個屬性,它保證了我們前面說的每次取出連接時會檢查該連接是否被關閉了。不過這個屬性會對性能有一些損耗,引用我參考的博客上得話:程序能用是第一,之后才是它的性能(又不是不能容忍)。

當然,c3p0自帶類似於select 1這樣的check機制,但是就像我說的,除非你將check機制的間隔時間把握的非常好,否則,問題是沒有解決的。

好了,至此,困擾我的問題解決完了。希望上面的這些整理可以為我以后碰到類似的問題留個思路,也可以為正在被此問題困擾的人提供一絲幫助。

 

2、使用dbcp數據源

 

由於mysql的默認最大空閑時間8小時,所以只要把minEvictableIdleTimeMillis設置小於此值即可。例如配置每十分鍾檢查超過空閑一個小時的連接

	<property name="minEvictableIdleTimeMillis">
		<value>3600000</value>
	</property>
	<property name="timeBetweenEvictionRunsMillis">
		<value>600000</value>
	</property>

 

3、proxool 數據源

數據庫連接池proxool,它有兩個屬性:一個是test-before-use,還有一個是test-after-use,這兩個屬性就是在使用前和使用后都要進行對連接的檢查,如果連接無效就扔掉再創建一個新的連接


4、

testConnectionOnCheckout: 因性能消耗大請只在需要的時候使用它。如果設為true那么在每個connection提交的時候都 將校驗其有效性。建議使用 idleConnectionTestPeriod或automaticTestTable 
等方法來提升連接測試的性能。默認為false;

 

(1)idleConnectionTestPeriod

當數據庫重啟后或者由於某種原因進程被殺掉后,C3P0不會自動重新初始化數據庫連接池,當新的請求需要訪問數據庫的時候,此時會報錯誤(因為連接失效),同時刷新數據庫連接池,丟棄掉已經失效的連接,當第二個請求到來時恢復正常。

C3P0目前沒有提供當獲取已建立連接失敗后重試次數的參數,只有獲取新連接失敗后重試次數的參數(acquireRetryAttempts【默認為30】 )。

要解決此問題,可以通過設置idleConnectionTestPeriod【默認為0,表示不檢查 】參數折中解決,該參數的作用是設置系統自動檢查連接池中連接是否正常的一個頻率參數,時間單位是秒 。

 

(2)acquireIncrement

當連接池中的的連接耗盡的時候c3p0一次同時獲取的連接數,也就是說,如果使用的連接數已經達到了maxPoolSize,c3p0會立即建立新的連接。

 

(3)maxIdleTime

另外,C3P0默認不會close掉不用的連接池,而是將其回收到可用連接池中,這樣會導致,連接數越來越大,所以需要設置maxIdleTime【默認0,表示永遠不過期】,單位是秒,maxIdleTime表示idle狀態的connection能存活的最大時間。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM