如何重置 JDBC 连接池

Posted

技术标签:

【中文标题】如何重置 JDBC 连接池【英文标题】:How to reset the JDBC Connection Pool 【发布时间】:2015-02-25 21:24:18 【问题描述】:

我有一个问题,在我应用户的请求通过 tomcat Web 应用程序重置我的 mysql 数据库后,我得到了 tomcat 异常。到目前为止,我已尝试将其分解为设置、问题和我的分析,以帮助任何尝试阅读本文的人。

设置

重置基本上包括从 java 代码调用 bash 脚本:

删除root mysql用户密码 加载旧版本的数据库 在上面运行一些脚本 恢复所有密码

这是一个用户启动的过程,通常将数据库恢复到以前的状态,但它也用于从另一个系统导入数据库。一切完成后,用户将尝试访问 Web 应用程序的不同部分(即使用相同的会话而无需注销/重新登录),该部分执行数据库查询以获取一些数据。

问题

一旦tomcat应用查询到DB,就会出现异常:

Dec 29, 2014 3:49:50 PM ERROR BasicSecurityRealm:216 - 
ERROR: ----- SQLException -----

Dec 29, 2014 3:49:50 PM  INFO BasicSecurityRealm:218 - Exceptioncom.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 234,810 milliseconds ago.  The last packet sent successfully to the server was 12 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)
...
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:2540)
    at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2990)

即使用户注销并重新登录,我也会看到此异常。如果我刷新页面四次​​,页面每次都会加载一些不同的异常(上面的所有变体 - 由“EOFException:无法读取服务器的响应”引起的 CommunicationsException)。最后一次,一切似乎都运行正常了。

为了避免这些异常,我唯一能做的就是重新启动 tomcat。我想避免这种情况,因为这意味着当前登录的用户将失去他们的会话,并且必须等待 tomcat 重新启动才能重新登录。强制他们注销/重新登录可能是一个可以接受的妥协,但这并不能解决问题。

分析

据我所知,我认为问题与 JDBC 连接池有关。我正在使用 JNDI 数据源访问我的数据库,如下所示:

server.xml:

  <GlobalNamingResources>
    <Resource name="jdbc/mydb"
              auth="Container"
              type="javax.sql.DataSource"
              maxActive="30" maxIdle="30" maxWait="2147483647"
              username="x" password="x"
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/mydb?autoReconnect=true"/>

web.xml:

<!-- Data source definitions -->
<resource-ref>
    <res-ref-name>jdbc/mydb</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
    <res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

Java:

    // Get connection to specified database
    Context initCtx = new InitialContext();
    Context envCtx = (Context) initCtx.lookup("java:comp/env");
    DataSource ds = (DataSource) envCtx.lookup("jdbc/mydb");
    con = ds.getConnection();
    stmt = con.createStatement();
    rs = stmt.executeQuery("...");

我相信连接池包含过时/死的连接。每当我与ds.getConnection 建立连接时,它就会获得这些旧连接之一。第一次尝试使用它会失败并且连接被重置(注意我正在使用autoReconnect=true,所以第二次应该(并且确实)工作)。但是,池包含许多(在我的情况下,经验上是 4 或 5 个)陈旧连接,因此需要一段时间才能将它们全部正确重置。重置连接后,一切都会正常运行。

解决方案?

由于我使用了autoReconnect=true,因此我可以重新构建我的代码,以便在我尝试查询时遇到异常,我可以重试一次查询。如果它再次失败,那么我会知道确实有问题。如果通过,则重新建立连接成功。

问题在于代码中到处都有查询。将它们全部重构会花费大量时间和测试,如果有必要我会这样做,但我想避免。此外,如果查询由于其他原因而失败,则在报告之前会尝试两次。对于长查询,这可能会显着延迟用户体验,但仅限于错误情况。

另一种解决方案是强制重置/重新连接连接池中的所有连接。我可以通过编程方式(即在完成调用 bash 脚本时从我的 java 代码)或从 bash 脚本(例如,使用某种类型的命令行实用程序)来执行此操作。问题是,我不知道该怎么做,或者是否可能。

我找到了一些关于拦截器的文档,但我不确定这是否适用于重置连接。我会继续调查。

感谢大家的时间和帮助!

【问题讨论】:

解决方案:不要删除您在数据库连接池中配置的用户的凭据。事实上,你不应该为了改变数据库的状态而弄乱 mysql 用户数据。您应该只更改您正在使用的架构。 @LuiggiMendoza 谢谢。我曾想过,但长话短说,这可能不是一个选择。运行的脚本没有修改数据库的凭据,因此我需要删除 root 用户的密码。此外,加载的新数据库可能有不同的密码。 我曾想过,但长话短说,这可能不是一个选择然后默默忍受u_u。开个玩笑,您不能重置数据库连接池。避免这样做,或者让有足够权力的用户执行你的脚本。您可以创建另一个用户来执行此操作,或者关闭您的应用并重新启动它们并为此制定策略。 我猜你的整个方法给了你这个问题,你试图用一个肮脏的技巧来解决你的设计。我的建议是:是的,将此密码作为参数发送给脚本,甚至忘记弄乱 mysql 用户凭据根本。这就是问题所在,无论你如何描述它,你都应该避免这一步并使用更简洁的设计。 如果您了解数据库连接池的工作原理,那么如果您重新启动数据库引擎,您应该不会对这个结果感到惊讶。同样,解决方案不是重置连接池,而是建立重新启动数据库引擎的策略。如果您要重新启动引擎,请同时重新启动您的应用程序。 【参考方案1】:

您可以在从池中获取之前测试连接

默认情况下,Tomcat commond-dbcp 表示 Tomcat >= 7 它是 jdbc-pool

在这两种情况下,将下一个属性添加到连接池配置中:

validationQuery=<TEST SQL>
testOnBorrow=true

【讨论】:

这看起来是正确的方法。确保验证池的 validationInterval 属性。默认情况下是 30 秒,这意味着如果在过去 30 秒内测试了连接,则不会执行验证查询。在您的用例中,您似乎必须将validationInterval 设置为0。 @AndyDufresne 似乎有效。但是,如果我知道池中的所有连接都是陈旧的,有没有办法强制重置它们?这将比每次检查更有效。 没有找到任何 API 来做到这一点。 purge() 有帮助吗? @AndyDufresne 在什么对象上? 调用datasource.createPool()时返回的connectionPool对象【参考方案2】:

您可以在从池中获取之前测试连接。

添加连接池配置:

validationQuery=TEST SQL
testOnBorrow=true

【讨论】:

【参考方案3】:

由于您的项目包含多个ojdbc jar,例如 (ojdbc6 or ojdbc14),因此会引发异常添加任意一个jar 只推荐一个是ojdbc7 jar。

【讨论】:

如果您认为可以添加更多信息,可以编辑您现有的答案。避免为一个问题添加两个答案。

以上是关于如何重置 JDBC 连接池的主要内容,如果未能解决你的问题,请参考以下文章

DBCP,C3P0与Tomcat jdbc pool 连接池的比较

jdbc数据库连接池

如何调试/记录 Tomcat JDBC 连接池的连接?

如何记录 Tomcat 7 JDBC 连接池、连接创建

JDBC连接池概述

如何使用 Java JDBC 连接池?