MySQL数据库连接超时自动断开的解决方案

最近在将本地的JavaWeb项目部署到服务器上之后遇到的一个奇怪问题

  • 在部署完成后,网站当时可以正常工作,但是第二天访问网站的时候却会遇到一个500 Server Error。

Tomcat 的日志中关键信息如下:

05-May-2017 05:15:27.682 SEVERE [http-nio-8080-exec-5] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [Login] in context with path [] threw exception
org.hibernate.exception.JDBCConnectionException: could not execute query

Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 83,827,560 milliseconds ago. The last packet sent successfully to the server was 83,827,560 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.

Caused by: java.net.SocketException: Broken pipe (Write failed)
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
at java.net.SocketOutputStream.write(SocketOutputStream.java:155)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3721)
... 41 more

从日志中可以看出是MySQL数据库出现了异常

The last packet successfully received from the server was 83,827,560 milliseconds ago. The last packet sent successfully to the server was 83,827,560 milliseconds ago. is longer than the server configured value of 'wait_timeout'
最后一个数据包在 83827560 ms 之前被成功接收,最后一个数据包在83827560 ms 之前被成功发送。比服务的配置参数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.
你应考虑在程序中进行数据库操作之前检验数据库连接的有效性或者将数据库的autoConnect属性设置为true来避免这个问题。

在google上搜索之后,发现问题出在如果超过这个wait_timeout时间(默认是8小时)对数据库没有任何操作,那么MySQL会自动关闭数据库连接以节省资源。而我的网站对数据库进行操作的时候却并未判断数据库连接是否断开,因此在管道(pipe)的这一端请求数据而另一端却被关闭的情况下,就会抛出这个Broken pipe异常。

这里就有一个疑问: 日志上说最后一个数据包是在 83827560ms 之前被成功接收和发送的,这个时间换算过来大概就是139分钟,也就是2小时多一点,明显没有超过wait_timeout的值。但是数据库连接自动断开的问题确实是在第二天发生了,也就是在一个晚上没有对数据库进行操作(显然超过了8小时)的情况下发生的这个问题。
  这个疑问我暂时还没有找到答案,希望有知道的大神赐教。

不过既然找到了这个问题,那么我们就很容易考虑到如下的几个解决方案:

  1. 在主线程中定时发送一个 SELECT操作来确保数据库一直保持连接状态。
  2. 延长数据库的wait_timeout时间。
  3. 按照日志中给出的解决方案在程序中进行数据库操作之前检验数据库连接的有效性。
  4. 按照日志中给出的解决方案将MySQL数据库的autoConnect属性设置为true

接下来一一分析这几个解决方案是否可行

方法一

  • 通过定时的 SELECT操作来保持数据库连接的有效性

首先看一下我的Hibernate配置文件

<hibernate-configuration>

    <session-factory>
        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/DatabaseName</property>
        <property name="connection.characterEncoding">utf-8</property>
        <property name="connection.username">root</property>
        <property name="connection.password">password</property>
        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
        <!--mapping files-->
              ...... 
    </session-factory>

</hibernate-configuration>

接下来看一下Hibernate进行数据库连接的流程到底是什么样的

  • Hibernate(我导入的jar包是hibernate3.jar)在上面的那种配置之下,会有一个默认的连接池,名字叫:DriverManagerConnectionProvider;这是一个极其简单的连接池,默认会在池中保留20个连接,这些连接不是一开始Hibernate初始化时就创建好的,而是在你需要使用连接时创建出来,使用完之后才加入到池中的。这里有一个叫closeConnection(Connection conn)的方法,这个方法将传入的连接不做任何处理,放到池中。而这个类内部的连接池实际是一个ArrayList,每次取得时候removeArrayList的第一个连接,用完后直接用add方法加入到ArrayList的最后。

  • 程序对数据库进行操作时,Hibernate会通过DriverManagerConnectionProvider得到一个连接Connection,在使用完之后,调用session.close()时,Hibernate会调用DriverManagerConnectionProvidercloseConnection方法(就是上面说的那个closeConnection(Connection conn)方法),这个时候,该连接会直接放到DriverManagerConnectionProviderArrayList中,从始至终也没有地方去调用Connection的close方法。

我们的那个SELECT操作是定时的,而我们对数据库的其它操作显然并不是定时的,而这两个操作都会向DriverManagerConnectionProvider获取一个连接,并且工作完之后都会将自己获得的连接放回连接池的末尾。

说到这里,问题就很明显了,我们可以假设出现下面的场景

SELECT操作进行的过程中,它会持有一个连接(记为connection1),此时数据库收到了其它的操作请求,由于此时SELECT操作还未结束,connection1并没有放回连接池,因此这个请求会再创建一个新的连接或者拿到连接池内部的一个连接(记为connection2)这两个操作都结束之后,connection1connection2都被放回了连接池中,并且是放到了连接池的末尾。

**那么,下一次的SELECT操作拿到的连接到底是connection1还是connection2呢? **
  答案是都有可能,这取决于上面的场景中连接被放回连接池的顺序以及两次SELECT操作之间是否有过其它的数据库操作,这些我们显然无法判断。

因此可以得出结论: 最终SELECT操作是否保持了连接的有效性我们无法判断

参考链接:
http://blog.csdn.net/nethibernate/article/details/6658855

方法二

  • 延长数据库的<b>wait_timeout</b>时间

这个方法我并没有使用,因为wait_timeout决定的是非交互连接的时间长短。

  • 将这个值设置得大了,可能会导致空闲连接过多。
    如果你的MySQL Server有大量的闲置连接,他们不仅会白白消耗内存,而且如果连接一直在累加而不断开,最终肯定会达到MySQL Server的连接上限数,这会报'too many connections'的错误。

  • 将这个值设置得小了,那就失去了使用方法二的意义。

因此可以得出结论: 方法二虽然可以解决问题,但是wait_timeout的值到底设置为多少比较合适没有一个固定的说法,不好把握。

参考链接:
http://www.cnblogs.com/jiunadianshi/articles/2475475.html
http://stackoverflow.com/questions/4440336/mysql-wait-timeout

方法三

  • 在程序中进行数据库操作之前检验数据库连接的有效性

这个方法虽然可行,但是过于繁琐,因为它要确保在进行对数据库的任何操作之前都要检验数据库连接是否断开。
我并没有使用这个方法,因为代码中的数据库操作已经封装好,再去进行修改一是麻烦,二是可能会带来其他bug。

方法四

  • 将数据库的 autoConnect属性设置为 true

这个方法也是Google上看到的最多的方案,不过在MySQL开发者文档中可以看到关于这个属性的描述

The use of this feature is not recommended, because it has side effects related to session state and data consistency
并不提倡使用这个特性,因为它可能会对会话状态和数据一致性产生副作用

到底副作用是什么,文档上并没有详细说明,在stackoverflow上搜索时倒是看到了

session state and transactions cannot be maintained over a new connection.
(原有的)会话状态和事务在新的连接中并不能被保持下来。

看到这里我就放弃了使用它的想法。

看来方法四并不能解决问题?

不过不要忘了我们是使用Hibernate框架而不是JDBC来进行数据库操作的,既然在MySQL配置中无法解决这个问题,那么我们可以考虑一下Hibernate配置。

最终解决方案
Hibernate中恰好有类似于autoReconnect这样的属性来自动保持数据库的连接,那就是使用c3p0连接池的testConnectionOnCheckout属性。

  • 数据库自动连接的方法就是在获取连接时检查一下,看看该连接是否还有效,即该Connection是否已经被MySQL数据库关闭了,如果关了就重新建立一个连接。

因此,我导入了c3p0的相关jar包,并且修改了Hibernate的配置文件

<hibernate-configuration>

    <session-factory>
        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/DatabaseName</property>
        <property name="connection.characterEncoding">utf-8</property>
        <property name="connection.username">root</property>
        <property name="connection.password">password</property>
        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
        <!-- c3p0连接池配置 -->
        <property name="hibernate.connection.provider_class">
            org.hibernate.connection.C3P0ConnectionProvider
        </property>
        <property name="hibernate.c3p0.max_size">20</property>
        <property name="hibernate.c3p0.min_size">5</property>
        <property name="hibernate.c3p0.timeout">50000</property>
        <property name="hibernate.c3p0.max_statements">100</property>
        <property name="hibernate.c3p0.idle_test_period">3000</property>
        <!-- 当连接池耗尽并接到获得连接的请求,则新增加连接的数量 -->
        <property name="hibernate.c3p0.acquire_increment">2</property>
        <property name="hibernate.c3p0.testConnectionOnCheckout">true</property>
        <!--mapping files-->
              ...... 
    </session-factory>

</hibernate-configuration>

上面配置中最重要的就是hibernate.c3p0.testConnectionOnCheckout这个属性,它保证了每次取出连接时都会检查该连接是否被关闭,如果被关闭,则重新建立连接。
至此,困扰我的这个问题就解决了,分享出解决这个问题的过程,希望对大家也能有所帮助。

参考链接:
http://stackoverflow.com/questions/270800/why-does-hibernate-jdbc-mysql-drop-connections-after-a-day-or-so
http://blog.csdn.net/nethibernate/article/details/6658855
http://hibernatedb.blogspot.com/2009/05/automatic-reconnect-from-hibernate-to.html
c3p0相关jar包下载地址:https://sourceforge.net/projects/c3p0/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容