C3p0连接配置问题
在日常开发中,经常会涉及到数据库读写访问,对数据库访问通常会通过数据库连接池来获取数据库连接,再进行SQL,最近涉及到迁移数据,在日志中偶尔出现一些MySQL数据库连接错误,通过DB配置和连接池配置最终解决。
服务器日志中的错误信息大致如:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failureThe last packet successfully received from the server was 20,024 milliseconds ago. The last packet sent successfully to the server was 14 milliseconds ago. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:526) at com.mysql.jdbc.Util.handleNewInstance(Util.java:409) at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1122) at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3056) at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2942) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3485) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1960) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2114) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2690) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2619) at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1465) at com.mchange.v2.c3p0.impl.NewProxyStatement.executeQuery(NewProxyStatement.java:35) at org.haolin.kb.C3p0Test$1.run(C3p0Test.java:40) at java.lang.Thread.run(Thread.java:745)Caused by: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost. at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2503) at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2953) ... 10 morecom.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure 这样的错误重启应用后,便能暂时解决,但过不了多久又会出现,而且这个错误有可能一直持续下去。最终发现与MySQL超时连接配置有关。
[mysqld]# 单位为秒,默认为28800,即8小时wait_timeout=100interactive_timeout=100 wait_timeout和interactive_timeout需要同时设置才会生效。当数据库连接超过wait_timeout这段时间内,没有任何数据库操作,MySQL将主动关闭服务端连接,因此客户端再次进行数据库操作时,会报该错误,所以将这两个值设置得比较大,但其实设置过大,对安全和资源也有一定的损失。
ComboPooledDataSource pool = new ComboPooledDataSource();pool.setDriverClass("com.mysql.jdbc.Driver" );pool.setJdbcUrl("jdbc:mysql://localhost:3306/user_db");pool.setUser("root");pool.setPassword("");// 检测连接配置pool.setPreferredTestQuery("SELECT 1");pool.setIdleConnectionTestPeriod(5);// 获取到连接时就同步检测// pool.setTestConnectionOnCheckout(true); 所以最好开启c3p0连接池的自动检测功能,这样即便之前的连接被MySQL关闭了,也不容易出现上述的错误,除非连接池还没来得及检测连接是否可用,就被应用拿来使用了,但c3p0提供了同步检测连接的方式pool.setTestConnectionOnCheckout(true);,但这对应用性能是有所损失的。
上面的错误在数据库重启的时候也有可能报错,并且错误日志中会多出一部分MySQL关闭的信息:
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Server shutdown in progress at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:526) at com.mysql.jdbc.Util.handleNewInstance(Util.java:409) at com.mysql.jdbc.Util.getInstance(Util.java:384) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1015) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3562) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3494) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1960) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2114) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2690) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2619) at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1465) at com.mchange.v2.c3p0.impl.NewProxyStatement.executeQuery(NewProxyStatement.java:35) at org.haolin.kb.C3p0Test$1.run(C3p0Test.java:42) at java.lang.Thread.run(Thread.java:745) 但这种情况下,即便不启用c3p0的连接检测机制,只要应用的数据库操作间隔时间未超过MySQL配置的wait_timeout,下一次数据库操作仍能连接上。
c3p0从0.9.5以上开始支持JDBC 4的Connection.isValid(),所以可以不用设置检测语句pool.setPreferredTestQuery("SELECT 1")。