1、
mysql5数据库连接超时问题的解决办法 最近碰到一个mysql5数据库的问题。就是一个标准的servlet/tomcat网络应用,后台使用mysql数据库。问题是待机一晚上后,第二天早上第一次登录总是失败。察看日志发现如下错误:
“com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:
Communications link failure
Last packet sent to the server was 0 ms ago.”
经过一番调研,发现很多人都碰到过类似问题,但网上令人满意的回答并不多。mysql网站上的提问也很多,但并没有正确答案;百度知道上倒是有一个近似正确的回答。现将本人的解决办法总结一下:
上述问题是由mysql5数据库的配置引起的。mysql5将其连接的等待时间(wait_timeout)缺省为8小时。在其客户程序中可以这样来查看其值:
mysql﹥
mysql﹥ show global variables like 'wait_timeout';
+---------------+---------+
| Variable_name | Value |
+---------------+---------+
| wait_timeout | 28800 |
+---------------+---------+
1 row in set (0.00 sec)
28800 seconds,也就是8小时。
如果在wait_timeout秒期间内,数据库连接(java.sql.Connection)一直处于等待状态,mysql5就将该连接关闭。这时,你的Java应用的连接池仍然合法地持有该连接的引用。当用该连接来进行数据库操作时,就碰到上述错误。这解释了为什么我的程序第二天不能登录的问题。
你可能会想到在tomcat的数据源配置中有没有办法解决?的确,在jdbc连接url的配置中,你可以附上“autoReconnect=true”,但这仅对mysql5以前的版本起作用。增加“validation query”似乎也无济于事。
本人觉得最简单的办法,就是对症下药:既然问题是由mysql5的全局变量wait_timeout的缺省值太小引起的,我们将其改大就好了。
查看mysql5的手册,发现对wait_timeout的最大值分别是24天/365天(windows/linux)。以windows为例,假设我们要将其设为21天,我们只要修改mysql5的配置文件“my.ini”(mysql5 installation dir),增加一行:wait_timeout=1814400
需要重新启动mysql5。
linux系统配置文件:/etc/my.cnf
测试显示问题解决了。
2、
mysql 长时间没连接了 就会自动断开服务
Communications link failure due to underlying exception
这是因为mysql 长时间没连接了 就会自动断开服务。
解决办法
1、首先,下载必须的jar包
dbcp 包,目前版本是1.2.1:http://jakarta.apache.org/commons/ dbcp /
pool包,目前版本是1.3:http://jakarta.apache.org/commons/pool/,
如果下载的pool包是1.2的版本,还要下载common-collections包:http://jakarta.apache.org/commons/collections/
在建立我们自己的数据库连接池时,可以使用xml文件来传入需要的参数,这里只使用hard code的方式来简单介绍,所有需要我们自己写的代码很少,只要建立一个文件如下:
import org.apache.commons. dbcp .BasicDataSource;
import org.apache.commons. dbcp .BasicDataSourceFactory;
import java.sql.SQLException;
import java.sql.Connection;
import java.util.Properties;
public class ConnectionSource {
private static BasicDataSource dataSource = null;
public ConnectionSource() {
}
public static void init() {
if (dataSource != null) {
try {
dataSource.close();
} catch (Exception e) {
//
}
dataSource = null;
}
try {
Properties p = new Properties();
p.setProperty("driverClassName", "oracle.jdbc.driver.OracleDriver");
p.setProperty("url", "jdbc:oracle:thin:@192.168.0.1:1521:testDB");
p.setProperty("password", "scott");
p.setProperty("username", "tiger");
p.setProperty("maxActive", "30");
p.setProperty("maxIdle", "10");
p.setProperty("maxWait", "1000");
p.setProperty(" removeAbandoned ", "false");
p.setProperty("removeAbandonedTimeout", "120");
p.setProperty("testOnBorrow", "true");
p.setProperty("logAbandoned", "true");
dataSource = (BasicDataSource) BasicDataSourceFactory.createDataSource(p);
} catch (Exception e) {
//
}
}
public static synchronized Connection getConnection() throws SQLException {
if (dataSource == null) {
init();
}
Connection conn = null;
if (dataSource != null) {
conn = dataSource.getConnection();
}
return conn;
}
}
接下来,在我们的应用中,只要简单地使用ConnectionSource.getConnection()就可以取得连接池中的数据库连接,享受数 据库连接带给我们的好处了。当我们使用完取得的数据库连接后,只要简单地使用connection.close()就可把此连接返回到连接池中,至于为什 么不是直接关闭此连接,而是返回给连接池,这是因为 dbcp 使用委派模型来实现Connection接口了。
在使用Properties来创建BasicDataSource时,有很多参数可以设置,比较重要的还有:
testOnBorrow、 testOnReturn、testWhileIdle,他们的意思是当是取得连接、返回连接或连接空闲时是否进行有效性 验证(即是否还和数据库连通的),默认都为false。所以当数据库连接因为某种原因断掉后,再从连接池中取得的连接,实际上可能是无效的连接了,所以, 为了确保取得的连接是有效的,可以把把这些属性设为true。当进行校验时,需要另一个参数:validationQuery,对oracle来说,可以 是:SELECT COUNT(*) FROM DUAL,实际上就是个简单的SQL语句,验证时,就是把这个SQL语句在数据库上跑一下而已,如果连接正常的,当然就有结果返回了。
还有2个参数:timeBetweenEvictionRunsMillis 和 minEvictableIdleTimeMillis,他们两个配合,可以持续更新连接池中的连接对象,当 timeBetweenEvictionRunsMillis 大于0时,每过timeBetweenEvictionRunsMillis 时间,就会启动一个线程,校验连接池中闲置时间超过minEvictableIdleTimeMillis的连接对象。
还有其他的一些参数,可以参考源代码。
部分参数简要说明:
removeAbandoned :是否自动回收超时连接
removeAbandonedTimeout:超时时间(以秒数为单位)
rmoveAbandoned=true 那么在getNumActive()快要到getMaxActive()的时候,系统会进行无效的 Connection的回收,回收的Connection为removeAbandonedTimeout(默认300秒)中设置的秒数后没有使用的 Connection
logAbandoned:logAbandoned=true的话,将会在回收事件后,在log中打印出回收Connection的错误信息,包括在哪个地方用了Connection却忘记关闭了,在调试的时候很有用。
maxWait:超时等待时间以毫秒为单位
maxIdle:最大空闲连接
minIdle:最小空闲连接
maxActive:最大连接数
testOnBorrow、testOnReturn、testWhileIdle、validationQuery:上面有介绍
另外很重要的一点是每次连接使用完了不要忘了调用connection.close()使连接返回到连接池
3、
Throwable occurred: org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 50,123,505 milliseconds ago. The last packet sent successfully to the server was 50,123,505 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.
这主要是由两个原因引起来的:
1.mysql 会自动关闭长时间不用的connection,一个连接如果处于sleep状态达到mysql的参数wait_timeout指定的时间(默认为8小时),就是自动关闭这个连接
2.common pool中没有指定相应的连接检查参数
解决办法:从common pool的配置参数来解决:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>${db.driver}</value>
</property>
<property name="url">
<value>${db.url}</value>
</property>
<property name="username">
<value>${db.user}</value>
</property>
<property name="password">
<value>${db.password}</value>
</property>
<property name="maxActive">
<value>100</value>
</property>
<property name="maxIdle">
<value>50</value>
</property>
<property name="maxWait">
<value>10000</value>
</property>
<property name="timeBetweenEvictionRunsMillis">
<value>3600000</value><!--1 hours-->
</property>
<!--
<property name="minEvictableIdleTimeMillis">
<value>20000</value>
</property>
-->
<property name="testWhileIdle">
<value>true</value>
</property>
<property name="validationQuery">
<value>select 1 from dual</value>
</property>
</bean>
使用上述的三个红色的参数,就可以避免这个问题.这三个参数的意义:
timeBetweenEvictionRunsMillis:启动connection校验定时器,定时器运行时间间隔就是timeBetweenEvictionRunsMillis的值.默认为-1,表示不启动定时器,这里设定为1小时,只要小于mysql的wait_timeout就可以了
testWhileIdle: true,表示检查idle的connection,false为不检查
validationQuery:用于检查connection的sql语句.
这只是一种方法,另外的几种方法:
timeBetweenEvictionRunsMillis+minEvictableIdleTimeMillis:这种方式不检查Connection的有效性,而是检查连接的空闲时间,大于minEvictableIdleTimeMillis就清除.
<property name="timeBetweenEvictionRunsMillis">
<value>3600000</value><!--1 hours-->
</property>
<property name="minEvictableIdleTimeMillis">
<value>120000</value><!--connection的空闲时间大于这个值,就直接被关闭,并从连接池中删除-->
</property>
如果不喜欢用定时器,也可以配置testOnBorrow+validationQuery参数:每次从连接池取参数都会校验连接的有效性.实际上这种方式性能会比定时器差些.
<property name="testOnBorrow">
<value>true</value>
</property>
<property name="validationQuery">
<value>select 1 from dual</value>
</property>
另外,也可以用testOnReturn+validationQuery,不过未必会解决问题:这表示每次使用完连接,归还连接池的时候检查连接的有效性,这有可能导致使用一次无效的连接,最好不要用.
上面的几种方法可以合并使用,只是检查的点多了,未必是好事.
4、
最近做网站有一个站要用到WEB网页采集器功能,当一个PHP脚本在请求URL的时候,可能这个被请求的网页非常慢慢,超过了mysql的 wait-timeout时间,然后当网页内容被抓回来后,准备插入到MySQL的时候,发现MySQL的连接超时关闭了,于是就出现了"MySQL server has gone away"这样的错误提示,解决这个问题,我的经验有以下两点,或许对大家有用处:
第一种方法:
当然是增加你的 wait-timeout值,这个参数是在my.cnf(在Windows下台下面是my.ini)中设置,我的数据库负荷稍微大一点,所以,我设置的值 为10,(这个值的单位是秒,意思是当一个数据库连接在10秒钟内没有任何操作的话,就会强行关闭,我使用的不是永久链接 (mysql_pconnect),用的是mysql_connect,关于这个wait-timeout的效果你可以在MySQL的进程列表中看到 (show processlist) ),你可以把这个wait-timeout设置成更大,比如300秒,呵呵,一般来讲300秒足够用了,其实你也可以不用设置,MySQL默认是8个小 时。情况由你的服务器和站点来定。
第二种方法:
这也是我个人认为最好的方法,即检查 MySQL的链接状态,使其重新链接。
可能大家都知道有mysql_ping这么一个函数,在很多资料中都说这个mysql_ping的 API会检查数据库是否链接,如果是断开的话会尝试重新连接,但在我的测试过程中发现事实并不是这样子的,是有条件的,必须要通过 mysql_options这个C API传递相关参数,让MYSQL有断开自动链接的选项(MySQL默认为不自动连接),但我测试中发现PHP的MySQL的API中并不带这个函数,你重新编辑MySQL吧,呵呵。但mysql_ping这个函数还是终于能用得上的,只是要在其中有一个小小的操作技巧:
这是我的的数据库操作类中间的一个函数
?function ping(){
?? if(!mysql_ping($this->link)){
?? mysql_close($this->link); //注意:一定要先执行数据库关闭,这是关键
?? $this->connect();
?? }
??}
我需要调用这个函数的代码可能是这样子的
?$str = file_get_contents
??$db->ping();//经过前面的网页抓取后,或者会导致数据库连接关闭,检查并重新连接
??$db->query('select * from table');
ping()这个函数先检测数据连接是否正常,如果被关闭,整个把当前脚本的MYSQL实例关闭,再重新连接。
经 过这样处理后,可以非常有效的解决MySQL server has gone away这样的问题,而且不会对系统造成额外的开销。
今天遇到类似的情景,MySQL只是冷冷的说:MySQL server has gone away.
大概浏览了一下,主要可能是因为以下几种原因:
一种可能是发送的SQL语句太长,以致超过了max_allowed_packet的大小,如果是这种原因,你只要修改my.cnf,加大max_allowed_packet的值即可。
还有一种可能是因为某些原因导致超时,比如说程序中获取数据库连接时采用了Singleton的做法,虽然多次连接数据库,但其实使用的都是同一个连接,而且程序中某两次操作数据库的间隔时间超过了wait_timeout(SHOW STATUS能看到此设置),那么就可能出现问题。最简单的处理方式就是把wait_timeout改大,当然你也可以在程序里时不时顺手mysql_ping()一下,这样MySQL就知道它不是一个人在战斗。
解决MySQL server has gone away
1、应用程序(比如PHP)长时间的执行批量的MYSQL语句。最常见的就是采集或者新旧数据转化。
解决方案:
在my.cnf文件中添加或者修改以下两个变量:
wait_timeout=2880000
interactive_timeout = 2880000
关于两个变量的具体说明可以google或者看官方手册。如果不能修改my.cnf,则可以在连接数据库的时候设置CLIENT_INTERACTIVE,比如:
sql = "set interactive_timeout=24*3600";
mysql_real_query(…)
2、执行一个SQL,但SQL语句过大或者语句中含有BLOB或者longblob字段。比如,图片数据的处理
解决方案:
在my.cnf文件中添加或者修改以下变量:
max_allowed_packet = 10M(也可以设置自己需要的大小)
max_allowed_packet参数的作用是,用来控制其通信缓冲区的最大长度
5、
首先,如果使用了长连接而长期没有对数据库进行任何操作,那么在timeout值后,mysql server就会关闭此连接,而客户端在执行查询的时候就会得到一个类似于“MySQL server has gone away“这样的错误。
一个好的解决方法是使用mysql_ping。在使用mysql_real_connect连接数据库之后,再使用mysql_options( &mysql, MYSQL_OPT_RECONNECT, … ) 来设置为自动重连。这样当mysql连接丢失的时候,使用mysql_ping能够自动重连数据库。如果是在mysql 5.1.6之前,那么则应在每次执行完real_connect 之后执行mysql_options( &mysql, MYSQL_OPT_RECONNECT, … ) ,如果是mysql 5.1.6+,则在connect之前执行一次就够了。
有一种很自然的想法就是,新开一个线程,让它隔一定时间(如20秒)就执行一次mysql_ping.除此而外它什么都不用做。但是显然,这个线程必须与其它线程共享一个mysql连接,共享该mysql句柄。否则这样做一点意义都没有。
但是,mysql_ping会改变mysql_affected_rows的返回值。所以最好是给该MYSQL句柄再加一个mutex(最好是读写锁)。当其它线程准备执行query的时候,就获取锁,执行完就释放。而这个执行mysql_ping的线程在执行ping之间先尝试获取锁,如果获取失败,则继续sleep,放弃这一轮的ping.