読者です 読者をやめる 読者になる 読者になる

Spring+MySQLでMySQLの接続切れコネクション使用によるエラーの対策

こんなエラーが出てたので調べてみる。

### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: 
The last packet successfully received from the server was 229,658,792 milliseconds ago.
The last packet sent successfully to the server was 229,658,820 milliseconds ago.
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 property 'autoReconnect=true' to avoid this problem.

これは接続が切れたコネクションを使用しようとすると発生する例外らしい。
MySQLの場合、デフォルトで8時間以上アイドル状態の接続がある場合に、接続を切断する設定になっているらしく、
ここの設定もまさにデフォルト8時間となっていた。
エラーメッセージを見ると回避方法として以下を挙げている。

  1. アプリケーションでコネクションを使用する前に有効性をテストする
  2. タイムアウトの設定値を増やす
  3. autoReconnect=trueの設定を追加する

しかし、2.に関しては暫定措置に過ぎず根本原因解決にはなっていないので却下。
3.に関してはMySQLのサイトに利用はお勧めできません。と書いてあるのでこれも却下。
なので今回は1.の方法で対応する。

Commons DBCPのプロパティ

Commons DBCPのサイトにあるバリデーションのプロパティ

パラメータ デフォルト値 概要
validationQuery コネクションの有効性検証用のクエリ。このクエリは少なくとも1行を返すSQL SELECT文でなければなりません。
testOnBorrow true trueに設定すると、プールからコネクションを取得する際に検証を行います。
testWhileIdle false trueに設定すると、監視スレッドがアイドル状態のコネクションの生存確認を行う際に、有効性の検証も行います。検証に失敗した場合は、プールから削除されます。
timeBetweenEvictionRunsMillis -1 指定した間隔で監視スレッドを起動し、監視スレッドはコネクションの生存確認を行い、30分(デフォルト)以上アイドル状態のコネクションをプールから削除していく。

testOnBorrowの指定だけで良いのかなぁと思ったのだが、このエントリーによるとtestOnBorrowで有効性の検証に失敗した場合、プール内の他のコネクションを取得しさらに検証を行うらしく冗長な気がするので、有効性の検証を行い、かつ、監視スレッドによる監視を追加する。
なので、springのコンテキストファイルに上記4つの定義を追加する。

<bean
 id="datasource"
 class="org.apache.commons.dbcp.BasicDataSource"
 destroy-method="close">
 <property name="driverClassName" value="${database.driverClassName}"/>
 <property name="username" value="${database.username}"/>
 <property name="password" value="${database.password}"/>
 <property name="validationQuery" value="SELECT 1 FROM DUAL" />
 <property name="testOnBorrow" value="true" />
 <property name="testWhileIdle" value="true" />
 <property name="timeBetweenEvictionRunsMillis" value="60000" />
</bean>

これで、今回のエラーは解消されたが、我ながら勉強不足だったなぁと反省...