1. 程式人生 > >Mysql連結池超時斷開解決方法探討

Mysql連結池超時斷開解決方法探討

引言:昨天晚上做了個啟用服務,然後測試沒問題,今天早上重新測了下,發現報異常,連結不上資料庫.

先說一下發生這個Exception的大致原因:
MySQL的配置中,有一個叫做“wait_timeout"的引數,這個引數大致的意思是這樣:當一個客戶端連線到MySQL資料庫後,如果客戶端不自己斷開,也不做任何操作,MySQL資料庫會將這個連線保留"wait_timeout"這麼長時間(單位是s,預設是28800s,也就是8小時),超過這個時間之後,MySQL資料庫為了節省資源,就會在資料庫端斷開這個連線;當然,在此過程中,如果客戶端在這個連線上有任意的操作,MySQL資料庫都會重新開始計算這個時間。

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

那這個問題怎麼解決呢?

查了下網上的資料,其中詳細點的如下:

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

第二個問題:從上面的Exception中可以得到這麼一個資訊:

  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,配置很簡單,都是最基本的,沒有任何關於連線池和快取的配置,就像下面這樣:

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

程式中更新的過程大致是這樣:
  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的配置檔案,問題得到了解決:

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

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

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

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


最後補充點東西:

1,一些c3p0的屬性方法總結:

datasource.c3p0.acquireIncrement=10當連線池中的連線用完時,C3P0一次性建立新連線的數目;
datasource.c3p0.minPoolSize=50連線池中保留的最小連線數。預設為15
datasource.c3p0.maxPoolSize=400連線池中保留的最大連線數。預設為15;
datasource.c3p0.initialPoolSize=50初始化時建立的連線數,應在minPoolSize與maxPoolSize之間取值。預設為3;
datasource.c3p0.maxIdleTime=1800最大空閒時間,超過空閒時間的連線將被丟棄。為0或負數則永不丟棄。預設為0;
datasource.c3p0.acquireRetryAttempts=100定義在從資料庫獲取新連線失敗後重復嘗試獲取的次數,預設為30;
datasource.c3p0.acquireRetryDelay=20兩次連線中間隔時間,單位毫秒,預設為1000;
datasource.c3p0.debugUnreturnedConnectionStackTraces=true
datasource.c3p0.maxStatements=0JDBC的標準引數,用以控制資料來源內載入的PreparedStatement數量。但由於預快取的Statement屬 於單個Connection而不是整個連線池。所以設定這個引數需要考慮到多方面的因素,如果maxStatements與 maxStatementsPerConnection均為0,則快取被關閉。預設為0;
datasource.c3p0.idleConnectionTestPeriod=1800隔多少秒檢查所有連線池中的空閒連線,預設為0表示不檢查;
datasource.c3p0.breakAfterAcquireFailure=true獲取連線失敗將會引起所有等待獲取連線的執行緒丟擲異常。但是資料來源仍有效保留,並在下次調 用getConnection()的時候繼續嘗試獲取連線。如果設為true,那麼在嘗試獲取連線失敗後該資料來源將申明已斷開並永久關閉。預設為 false;
datasource.c3p0.testConnectionOnCheckout=false因效能消耗大請只在需要的時候使用它。如果設為true那麼在每個connection提交的時候都 將校驗其有效性。建議使用idleConnectionTestPeriod或automaticTestTable
datasource.c3p0.autoCommitOnClose=true連線關閉時預設將所有未提交的操作回滾。預設為false;
datasource.c3p0.maxStatementsPerConnection=100連線池內單個連線所擁有的最大快取Statement數。預設為0;

2,本問題中,再多加入<property name="hibernate.c3p0.maxIdleTime">1800</property>這一行,原因在上面屬性中有.

3,記得新增兩個jar包:我新增的版本是c3p0-0.9.2.1.jar和mchange-commons-java-0.2.3.4.jar

若後面有新的問題 隨時補充.

相關推薦

Mysql連結超時斷開解決方法探討

引言:昨天晚上做了個啟用服務,然後測試沒問題,今天早上重新測了下,發現報異常,連結不上資料庫. 先說一下發生這個Exception的大致原因: MySQL的配置中,有一個叫做“wait_timeout"的引數,這個引數大致的意思是這樣:當一個客戶端連線到MySQL資料庫後,

SFTP使用JSCH庫連結斷開解決方法

在處理銀行業務的時候,資料互動還使用老舊的sftp技術,最近在使用sftp的JSCH庫時發現,採用公私鑰方式連線server,總是無法斷開連線,研究發現這種模式下如果存在併發的連結時,disconnect方法實際不起左右,這樣會產生大量連結,最終造成無法再連結上;解決辦法採

連接Mysql提示Can’t connect to local MySQL server through socket的解決方法

127.0.0.1 方式 mic div conn 三種 問題 ati my.cnf 轉:http://aiezu.com/article/mysql_cant_connect_through_socket.html 有時候,當我們使用“mysql”、“mysqladmin

遠程桌面連接MySQL遇到的問題及解決方法總結

許可授權 fire ble 連接 配置 image iptable 問題 分享 背景提要:想用Delphi做一個可以連接Mysql數據庫的桌面應用程序。其中遇到了一些讓自己很苦惱的問題。因為自己是新手,Delphi用的不熟,FireDAC這個連接數據庫裏控件更是沒有接觸

MySQL中的錯誤及解決方法

chan col pan sql命令 sharp arp 自己的 class word 1. 修改密碼提示 ERROR 1054 (42S22): Unknown column ‘‘password‘‘ in ‘‘field list‘‘ 錯誤原因:mysql數據庫下已

windows下mysql忘記root密碼的解決方法

ogr cmd pwd ati 賬號 lin 新建 use title 方法一: 1、在DOS窗口下輸入net stop mysql5 或 net stop mysql 2、開一個DOS窗口,這個需要切換到mysql的bin目錄。 一般在bin目錄裏面創建一個批處理1

mysql 使用shell時出現 ERROR 2006 (HY000): MySQL server has gone away 解決方法

pos connect 技術分享 mysql table try 發現 com span ERROR 2006 (HY000): MySQL server has gone away No connection. Trying to reconnect... Con

啟動mysql時顯示:/tmp/mysql.sock 不存在的解決方法

blog col 賬號 一個 nbsp 解決 直接 sock div 簡單直接的方法: 自己建立一個mysql.sock文件,放到mysql目錄下,然後使用軟鏈接指向到tmp文件夾下 註意mysql賬號有讀寫/mysql/mysql.sock的權限 ln -s

出現”/var/lib/mysql/mysql.sock“不存在的解決方法

解決辦法如下 文件 socket pan 創建 pass 修改 ip地址 .so 這種情況大多數是因為你的mysql是使用rpm方式安裝的,它會自動尋找 /var/lib/mysql/mysql.sock 這個文件,通過unix socket登錄mysql。常見解決辦法如下

win10+vs2017+asp.net MVC5+EF6+mysql 閃退問題,解決方法

mvc connect mysql data -c 選中 nec 中項 安裝 1.安裝 mysql-for-visualstudio-2.0.5.msi 2.安裝 mysql-connector-net-6.10.7.msi 3.在VS2017 右鍵選中項目,管理NuG

mysql導入太慢解決方法

cts related tween cal mit orm CI sch multi 半調子數據科學家又要折騰數據,拿到數據一看,3.6G的zip文件,解壓看看,臥槽12個G的sql文件。好吧,又要折騰sql數據了。第一件事,肯定是搭一個數據庫,導入數據咯。 折騰過sql導

8.0.11版本Mysql遇到MySQL 服務無法啟動的解決方法

p s 解決方法 details eba 引號 iss 位數 -- 修改密碼 轉:https://blog.csdn.net/iyayaqiqi/article/details/80536110 系統環境:win10(1803),64位 MySQL版本:8.0.11免安

***在Linux環境下mysql的root密碼忘記解決方法(三種)-推薦第三種

href containe 完全 mys init.d 操作 root ubunt upd MySQL密碼的恢復方法之一 1.首先確認服務器出於安全的狀態,也就是沒有人能夠任意地連接MySQL數據庫。 因為在重新設置MySQL的root密碼的期間,MySQL數據庫完全出於沒

Linux環境下mysql的root密碼忘記解決方法

l數據庫 狀態 con ini pda inux star 數據 .cn 1.首先確認服務器出於安全的狀態,也就是沒有人能夠任意地連接MySQL數據庫。 因為在重新設置MySQL的root密碼的期間,MySQL數據庫完全出於沒有密碼保護的 狀態下,其他的用戶也可以任意地登錄

忘記MySQL的root密碼的解決方法

步驟 span ted 通過 run commands cut ive my.cnf 經常會有朋友或者同事問起,MySQL 的 root 密碼忘了,不知道改怎麽辦。 其實解決方法很簡單,下面是詳細的操作步驟。 (1)修改配置文件my.cnf,在配置文件[mysql

使用hibernate自動建立Mysql表失敗原因及解決方法

原因: hibernate裡的dialect和Mysql的版本不匹配,SQL語句裡的type=“****”使用在MySQL5.0之前,5.0之後就要是使用engine=“****”。 解決: 修改hibernate.cfg.xml檔案 MySql5.0之前的配置 <property

ping請求超時解決方法

首先開啟執行視窗,直接按住win+R組合鍵就可以了,開啟之後,我們在視窗中輸入cmd,進入dos命令。 然後我們在這裡ping對方的電腦,直接在裡面輸入ping 192.168.1.1,然後按住回車鍵,這個時候我們發現ping不通,請求超時。 出現這種問題的原因有很

來自轉發的關於修改mysql密碼 忘記MySQL的root密碼的解決方法

忘記MySQL的root密碼的解決方法   經常會有朋友或者同事問起,MySQL 的 root 密碼忘了,不知道改怎麼辦。 其實解決方法很簡單,下面是詳細的操作步驟。   (1)修改配置檔案my

虛擬機中docker安裝mysql遠程無法訪問解決方法

name select src oot -name pull with .com img #docker安裝mysql docker pull mysql docker run --name mysql -p 3307:3306 -e MYSQL_ROOT_PASSWORD

jdbc連線mysql時報時區設定錯誤解決方法

參考https://blog.csdn.net/zqb765720343/article/details/80076020,感謝分享。 jdbc連線mysql時報時區設定錯誤,提示相差8小時。 1、在mysql命令列模式下修改時區,經過嘗試發現,這種方法重啟mysql後就失效了,不是永久的。