`

Mysql的一种CommunicationsException异常

    博客分类:
  • SQL
阅读更多

问题背景:

一个项目需要启动一个定时器任务,隔断时间访问一下DB,后端DB使用的是Mysql。之前测试的时候,启动程序,自动运行没有问题,逻辑正常。周五晚上走后,启动程序,按照之前配置的Cron,周末程序会自动执行。周一来了之后查看DB的数据发现有问题,于是查看服务器的程序日志,发现爆出了如下错误(非关键错误省略。。。):

 

Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 82,661,051 milliseconds ago.  The last packet sent successfully to the server was 82,661,051 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.
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:409)
        at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1122)
        at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3317)
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1941)
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2114)
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2696)
        at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2105)
        at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:2264)
        at org.apache.commons.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:93)
        at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:208)
        at org.hibernate.loader.Loader.getResultSet(Loader.java:1812)
        at org.hibernate.loader.Loader.doQuery(Loader.java:697)
        at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
        at org.hibernate.loader.Loader.doList(Loader.java:2232)
        ... 30 more
Caused by: java.net.SocketException: Broken pipe
        at java.net.SocketOutputStream.socketWrite0(Native Method)
        at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
        at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
        at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
        at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3298)
        ... 41 more

问题原因:

其实上面的提示中已经给出了一部分的简要说明,简单来说就是: 程序启动时,在跟DB首次交互时,获得了相应的DB Connection资源,从而进行正常的DB读写操作。但是在下次进行DB读写时(我的定时任务本身设置的时间间隔是24小时),应用程序认为这个连接是可以正常使用的(程序执行过一次之后没有退出,这个连接从来并没有被释放掉),但实际上,这个连接已经坏掉了,因为Mysql本身已经把这个连接标记为timeout了。于是,应用程序“傻乎乎”的在这个已经坏掉的数据通道上发起对DB的读写请求,但是Mysql已经对这些请求不买账了。。。

 

为啥呀,mysql到底认为这个连接空闲多长时间算过期啊?

这个可以通过查看mysql的配置文件,看看是否有对这个时间做过特殊的配置,我的场景下在64位linux服务器上部署的mysql服务器,这个配置文件在:在/etc/my.cnf

如果你打开这个文件,发现并没有如下这行配置:

 

wait_timeout=xxx (这里xxx是数据,单位为秒)

说明你并没有对这个timeout做过特殊配置,通常Mysql默认的配置是8小时。你也可以在登陆进入mysql之后,通过如下命令确认一下:

 

show global variables like 'wait_timeout';

结合上面我的程序的配置(24小时执行一次),上面的问题就好解释了:24小时之后程序再次对DB进行读写操作时,Mysql单方已经认为之前connection已经timeout了(停止活动了8小时以上就认为过期了)。

如何解决:

Google了一下,发下下面两篇文章还比较靠谱:

http://www.databasesandlife.com/automatic-reconnect-from-hibernate-to-mysql/

http://javacrazyer.iteye.com/blog/721393/

基本思路也比较清晰,从上面我们分析问题的过程中,我们也可以想到如下两点:

1、既然Application持有了无效的DB connection自己不知道,那就让App变聪明点,自己在真正使用之前都去先确认一下自己的持有的connection是否有效再进行处理呗。这个方向的解决方案就是上面的第一个链接提到的。当然里面借助了C3P0。

2、既然App需要长时间的持有DB connection以备后面继续使用(24小时之后再次使用),那就让mysql的默认连接空闲的timeout加长到大于这个时间呗。理论上,保证过期时间大于这个connection的再次使用时间间隔就哦了。这个就是上面第二个连接的具体解决方案了。

另外,关于这里的第二点,需要注意的一点是,网上的多数配置说明都是将这个值调整的比默认8小时更短,比如这里http://www.serveridol.com/2012/04/13/mysql-interactive_timeout-vs-wait_timeout/ 有比较详细的说明。主要原因也是因为,长时间让DB保持连接不释放,对DB本身的资源也是一种严重的消耗。我的使用场景是定时器,没有前台用户访问,所以这种问题不大。但是如果你的使用场景是前端供用户使用的App,那这个恐怕就需要小心测试了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics