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

         一次朋友小聚,大家谈起了技术问题。一个在国企里的同学说,他们的系统很奇怪,每天早上都得重启一下应用程序,否则就提示连接数据库失败,他们都不知道该怎么办。  

我当时稍加思索说了一下,应该是连接超时引起的。

这样的问题在我的业务场景没有遇到过,所以我就仔细阅读了相关的资料,查看其解决方案,因此在这里记录形成文档便于后面查看翻阅。

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

其解决方案如下:

  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)这两个操作都结束之后,connection1和connection2都被放回了连接池中,并且是放到了连接池的末尾。

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

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

方法二

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

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

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

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

因此可以得出结论: 方法二虽然可以解决问题,但是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这个属性,它保证了每次取出连接时都会检查该连接是否被关闭,如果被关闭,则重新建立连接。

    原文作者:javashareauthor
    原文地址: https://blog.csdn.net/javashareauthor/article/details/103989986
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞