c3p0连接池maxIdleTime小于mysql数据库interactive_timeout参数导致出现NewPooledConnection close Exception的解决办法

问题描述

年前我所负责的系统新上线了几家客户,这几家客户跟之前上线的客户不同的是,系统所使用的MySQL数据库不是由我们公司自己的运维安装的,而是由兄弟公司来搭建和维护的(由于这个系统会逐渐转给兄弟公司来维护)。系统上线之后,在后台经常会看到如下所示异常信息:

INFO: [c3p0] NewPooledConnection close Exception.
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Communications link failure during rollback(). Transaction resolution unknown.
        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:411)
        at com.mysql.jdbc.Util.getInstance(Util.java:386)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1013)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:987)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:982)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:927)
        at com.mysql.jdbc.ConnectionImpl.rollback(ConnectionImpl.java:4733)
        at com.mysql.jdbc.ConnectionImpl.realClose(ConnectionImpl.java:4328)
        at com.mysql.jdbc.ConnectionImpl.close(ConnectionImpl.java:1556)
        at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:549)
        at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:234)
        at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.destroyResource(C3P0PooledConnectionPool.java:470)
        at com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask.run(BasicResourcePool.java:964)
        at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:547)
Feb 3, 2016 3:48:07 PM com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask run
WARNING: Failed to destroy resource: com.mchange.v2.c3p0.impl.NewPooledConnection@15880543
java.sql.SQLException: Some resources failed to close properly while closing com.mchange.v2.c3p0.impl.NewPooledConnection@15880543
        at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:571)
        at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:234)
        at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.destroyResource(C3P0PooledConnectionPool.java:470)
        at com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask.run(BasicResourcePool.java:964)
        at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:547)

问题分析

大致读下这个异常,基本了解到应该是c3p0连接池的问题,但是系统并没有因为这个异常导致完全不能使用,只是偶尔会出现某些操作异常,但是重复这个有异常的操作,系统就又可以正常工作。

多次尝试后发现,其实系统只在停止操作一段时间后,再次进行操作的时候出现异常,而连续多次操作并没有问题。再联系上面异常中出现的c3p0连接池,估计问题应该是系统隔一段时间不连接数据库,重新连接数据库的时候连接不上导致的。

沿着这个方向到网上搜索,发现数据库系统变量中有一个叫interactive_timeout,官方文档中的解释是:The number of seconds the server waits for activity on an interactive connection before closing it.MySQL文档,翻译过来意思就是:服务器关闭交互式连接前等待活动的秒数。

查看下出问题系统连接的MySQL数据库,发现interactive_timeout的值为120,也就是2分钟。那就是说对于连接这个数据库的客户端连接,活动时间为2分钟,也就是2分钟后会MySQL会强制断开连接。而c3p0连接池的配置maxIdleTime为10000,maxIdleTime的官方文档定义为:Seconds a Connection can remain pooled but unused before being discarded. Zero means idle connections never expire.最大空闲时间,N秒内未使用则连接被丢弃。若为0则永不丢弃。那就是说这个连接池可以保持2个多小时。

那问题基本上可以确定了:c3p0连接池认为自己的每个连接可以保持2个多小时,但是MySQL每2分钟没有客户端连接过来,就会自动关掉连接,而c3p0并不知道连接已经关闭了,所以他再次试图连接的时候就会报上面的异常。


解决方案

根据上面的分析,问题的根本原因是MySQL自动断开了连接,那怎么能让MySQL始终保持连接呢?我们知道MySQL的连接在2分钟内没有连接就会断掉,那最直接的想法就是在这2分钟内一直有客户端发来请求,或者把c3p0连接池的maxIdleTime改为小于interactive_timeout的一个数。

那么怎么才能实现客户端每隔一段时间就发给MySQL一个请求呢?对于4.X版本的MySQL,可以设置autoReconnect=true参数;而对于现在普遍使用的5.X版本的MySQL,可以在MySQL数据库中创建一个表:

CREATE TABLE `c3p0_connection_test` (
                `id` int(11) NOT NULL,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `c3p0_connection_test` VALUES ('1');

然后c3p0属性中添加:

<property name="preferredTestQuery" value="select id from c3p0_connection_test where id=1" />
<property name="idleConnectionTestPeriod" value="60" />

preferredTestQuery属性定义连接测试都执行的测试语句。注意: 测试的表必须在初始数据源的时候就存在。

idleConnectionTestPeriod属性用来配置测试空闲连接的间隔时间(单位:秒)。

也就是每60秒发送一条“select id from c3p0_connection_test where id=1”语句给数据库。这样就保证MySQL不会断开连接。

当然这样配置会消耗一定的性能,但是在现在我目前维护的系统下,影响不大,可以接受。

你可能感兴趣的:(MySQL)