dbcp不知MySQL已经'wait_timeout'

由dbcp不知MySQL已经'wait_timeout'而引出的

2011-09-10 22:42:13
    前几天在看一个应用的日志时,发现与MySQL连接时发生如下错误:

The last packet successfully received from the server was XXXXXX seconds ago.
The last packet sent successfully to the server was XXXXXX seconds 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 property 'autoReconnect=true' to avoid this problem

大概意思是距离上一次连接MySQL的间隔时间,已经超出了MySQL设置的'wait_timeout'时长啦。
连上MySQL,

mysql> show global variables like 'wait_timeout';
+----------------------------+--------+
| Variable_name | Value |
+----------------------------+--------+
| wait_timeout | 172800 |
+----------------------------+--------+

可以看到MySQL设置的wait_timeout是24小时,而日志显示上一次访问MySQL的时间已超出24小时。
    确认了问题后,就大概可以知道原因:项目使用dbcp管理jdbc连接。当超过'wait_timeout'时长后,MySQL会自动断开连接;而dbcp这边并不知道,当再次使用该连接时,发现连接不可用,就会报如上错误了。
    解 决的方法主要有两种,一是增加MySQL的'wait_timeout'时长;二是保证连接在MySQL的'wait_timeout'时间内,至少访问 一次数据库。第一种方法,DBA是断然不会同意的(DBA也是有气节的,不会被开发任意鱼肉啊-_-|||...)。而且应用如果不能确定最长 'wait_timeout'时间,则该值无论如何设定,理论上也无法避免如上问题。所以只能保证应用在MySQL的'wait_timeout'时间 内,至少访问一次数据库。
    该方法实现也很简单,只需配置dbcp的三个配置项timeBetweenEvictionRunsMillis、testWhileIdle与validationQuery即可。

timeBetweenEvictionRunsMillis=86400 # 失效检查线程运行时间间隔,要小于MySQL的'wait_timeout'时间(如果小于等于0,不会启动检查线程)
testWhileIdle=true # 检查连接是否有效
validationQuery=SELECT 1 FROM dual # 检查连接有效性的SQL语句

这 样dbcp会在timeBetweenEvictionRunsMillis指定的时间间隔(小于MySQL的'wait_timeout')内,通过 validationQuery指定的SQL语句来检查连接是否有效。避免了连接因长时间未执行SQL语句,而造成MySQL关闭连接。
    但 是具体的实现到底是怎样的呢?dbcp中,org.apache.commons.dbcp.BasicDataSource是使用apache commens pool中的org.apache.commons.pool.impl.GenericObjectPool来管理对象池的。

 


BasicDataSource会将testWhileIdle、timeBetweenEvictionRunsMillis传给GenericObjectPool。

 


GenericObjectPool将testWhileIdle、timeBetweenEvictionRunsMillis分别赋给属性_testWhileIdle与_timeBetweenEvictionRunsMillis。

 


然 后GenericObjectPool执行startEvictor(_timeBetweenEvictionRunsMillis)方法。该方法启动 一个EvictionTimer,根据timeBetweenEvictionRunsMillis指定的时间执行_evictor任务。

 


其中,EvictionTimer使用java.util.Timer来调度任务。而_evictor是Evictor类型对象,该类型继承java.util.TimerTask类。_evictor的run()方法中会执行evict()方法。

 


在evict()方法中,则通过_factory属性的validateObject(pair.value)方法来与MySQL通讯。

 


而这里的_factory属性,是BasicDataSource在构造PoolableConnectionFactory对象时,由PoolableConnectionFactory在自己的构造方法中,将自己指定给GenericObjectPool的。

 


 


由 上图可以看到,validationQuery在构造PoolableConnectionFactory时,一并传入。 PoolableConnectionFactory的validateObject(pair.value)方法,会调用 validateConnection(Connection conn)方法,并在该方法中执行validationQuery。

 


 


    Apache commens pool是一个通用的对象池管理组件。正因为其通用性,池中对象有效性的判断方法,就不能加以限定,而是要根据生成该对象池的工厂来实现。dbcp中的 PoolableConnectionFactory正是根据这一点,将与数据库的连接放在validateConnection方法中实现。
btw:回到最初异常的最后一行

or using the Connector/J connection property 'autoReconnect=true' to avoid this problem

autoReconnect=true在MySQL 5之前的版本可用,但是在MySQL 5之后的版本貌似不可用了。
另外validationQuery=SELECT 1 FROM dual中,dual只是一个虚表,在MySQL中并不存在,查询时也不关联。只是为了保持select … from … 的形式罢了。

猜你喜欢

转载自yunlong167167.iteye.com/blog/1970859
今日推荐