前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >检测数据库连接泄漏的最佳方法

检测数据库连接泄漏的最佳方法

作者头像
全栈程序员站长
发布2022-11-04 20:22:32
1.3K0
发布2022-11-04 20:22:32
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。

介绍

数据库连接不是免费的,这就是首先使用连接池解决方案的原因。但是,单独的连接池并不能解决与管理数据库连接相关的所有问题。应用程序开发人员必须确保Connection在不再需要时关闭每一个。在幕后,连接池提供了一个逻辑事务,当它被关闭时,它会返回到池中,以便其他并发事务可以进一步重用它。

当连接被获取而从未被关闭时,就会发生连接泄漏。

何时应检测到连接泄漏?

每个关系数据库都提供了一种检查底层连接状态的方法,因此可以轻松打开一个新的 SQL 终端并检查是否有任何悬空连接。但是,这种简约的方法是错误的,因为它意味着我们将应用程序的损坏版本部署到生产环境中。

在测试期间应检测连接泄漏,从而防止在生产环境中发生连接泄漏。

这篇文章将演示如何仅使用单元测试来自动化连接池检测。这种方法使我们能够在我们的实际代码库以及我们的测试例程中检测连接泄漏。如果单元测试正在泄漏连接,那么当达到最大数据库连接阈值时,持续集成过程将中断。

连接检漏仪

要检查给定的测试类是否泄漏连接,我们将检查 JUnit 测试运行器使用给定类之前和之后的悬空连接数:

1 2 3 4 5 6 7 8 9 10 11 12 13

@BeforeClass public static void initConnectionLeakUtility() { if ( enableConnectionLeakDetection ) { connectionLeakUtil = new ConnectionLeakUtil(); } } @AfterClass public static void assertNoLeaks() { if ( enableConnectionLeakDetection ) { connectionLeakUtil.assertNoLeaks(); } }

ConnectionLeakUtil实用程序如下所示:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

public class ConnectionLeakUtil { private JdbcProperties jdbcProperties = JdbcProperties.INSTANCE; private List idleConnectionCounters = Arrays.asList( H2IdleConnectionCounter.INSTANCE, OracleIdleConnectionCounter.INSTANCE, PostgreSQLIdleConnectionCounter.INSTANCE, MySQLIdleConnectionCounter.INSTANCE ); private IdleConnectionCounter connectionCounter; private int connectionLeakCount; public ConnectionLeakUtil() { for ( IdleConnectionCounter connectionCounter : idleConnectionCounters ) { if ( connectionCounter.appliesTo( Dialect.getDialect().getClass() ) ) { this.connectionCounter = connectionCounter; break; } } if ( connectionCounter != null ) { connectionLeakCount = countConnectionLeaks(); } } public void assertNoLeaks() { if ( connectionCounter != null ) { int currentConnectionLeakCount = countConnectionLeaks(); int diff = currentConnectionLeakCount - connectionLeakCount; if ( diff > 0 ) { throw new ConnectionLeakException( String.format( "%d connection(s) have been leaked! Previous leak count: %d, Current leak count: %d", diff, connectionLeakCount, currentConnectionLeakCount ) ); } } } private int countConnectionLeaks() { try ( Connection connection = newConnection() ) { return connectionCounter.count( connection ); } catch ( SQLException e ) { throw new IllegalStateException( e ); } } private Connection newConnection() { try { return DriverManager.getConnection( jdbcProperties.getUrl(), jdbcProperties.getUser(), jdbcProperties.getPassword() ); } catch ( SQLException e ) { throw new IllegalStateException( e ); } } }

IdleConnectionCounter接口定义了使用特定于数据库的实现来计算非活动连接数的协定。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

public interface IdleConnectionCounter { /** * Specifies which Dialect the counter applies to. * * @param dialect dialect * * @return applicability. */ boolean appliesTo(Class<? extends Dialect> dialect); /** * Count the number of idle connections. * * @param connection current JDBC connection to be used for querying the number of idle connections. * * @return idle connection count. */ int count(Connection connection); }

对于我们在测试期间使用的每个受支持的 Hibernate Dialect,都 需要有一个IdleConnectionCounter实现,以便我们可以检查泄漏连接的数量。

H2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

public class H2IdleConnectionCounter implements IdleConnectionCounter { public static final IdleConnectionCounter INSTANCE = new H2IdleConnectionCounter(); @Override public boolean appliesTo(Class<? extends Dialect> dialect) { return H2Dialect.class.isAssignableFrom( dialect ); } @Override public int count(Connection connection) { try ( Statement statement = connection.createStatement() ) { try ( ResultSet resultSet = statement.executeQuery( "SELECT COUNT(*) " + "FROM information_schema.sessions " + "WHERE statement IS NULL" ) ) { while ( resultSet.next() ) { return resultSet.getInt( 1 ); } return 0; } } catch ( SQLException e ) { throw new IllegalStateException( e ); } } }

甲骨文

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

public class OracleIdleConnectionCounter implements IdleConnectionCounter { public static final IdleConnectionCounter INSTANCE = new OracleIdleConnectionCounter(); @Override public boolean appliesTo(Class<? extends Dialect> dialect) { return Oracle10gDialect.class.isAssignableFrom( dialect ); } @Override public int count(Connection connection) { try ( Statement statement = connection.createStatement() ) { try ( ResultSet resultSet = statement.executeQuery( "SELECT COUNT(*) " + "FROM v$session " + "WHERE status = 'INACTIVE'" ) ) { while ( resultSet.next() ) { return resultSet.getInt( 1 ); } return 0; } } catch ( SQLException e ) { throw new IllegalStateException( e ); } } }

PostgreSQL

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

public class PostgreSQLIdleConnectionCounter implements IdleConnectionCounter { public static final IdleConnectionCounter INSTANCE = new PostgreSQLIdleConnectionCounter(); @Override public boolean appliesTo(Class<? extends Dialect> dialect) { return PostgreSQL91Dialect.class.isAssignableFrom( dialect ); } @Override public int count(Connection connection) { try ( Statement statement = connection.createStatement() ) { try ( ResultSet resultSet = statement.executeQuery( "SELECT COUNT(*) " + "FROM pg_stat_activity " + "WHERE state ILIKE '%idle%'" ) ) { while ( resultSet.next() ) { return resultSet.getInt( 1 ); } return 0; } } catch ( SQLException e ) { throw new IllegalStateException( e ); } } }

MySQL

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

public class MySQLIdleConnectionCounter implements IdleConnectionCounter { public static final IdleConnectionCounter INSTANCE = new MySQLIdleConnectionCounter(); @Override public boolean appliesTo(Class<? extends Dialect> dialect) { return MySQL5Dialect.class.isAssignableFrom( dialect ); } @Override public int count(Connection connection) { try ( Statement statement = connection.createStatement() ) { try ( ResultSet resultSet = statement.executeQuery( "SHOW PROCESSLIST" ) ) { int count = 0; while ( resultSet.next() ) { String state = resultSet.getString( "command" ); if ( "sleep".equalsIgnoreCase( state ) ) { count++; } } return count; } } catch ( SQLException e ) { throw new IllegalStateException( e ); } } }

测试时间

我构建了这个实用程序,以便我们可以跟踪Hibernate ORM项目中所有泄漏连接的单元测试。当针对 运行它时hibernate-core,我可以很容易地发现罪魁祸首测试:

1 2 3 4 5 6 7 8 9 10 11 12 13

:hibernate-core:test org.hibernate.jpa.test.EntityManagerFactoryClosedTest > classMethod FAILED org.hibernate.testing.jdbc.leak.ConnectionLeakException org.hibernate.jpa.test.EntityManagerFactoryUnwrapTest > classMethod FAILED org.hibernate.testing.jdbc.leak.ConnectionLeakException org.hibernate.jpa.test.cdi.NoCdiAvailableTest > classMethod FAILED org.hibernate.testing.jdbc.leak.ConnectionLeakException org.hibernate.jpa.test.factory.SynchronizationTypeTest > classMethod FAILED org.hibernate.testing.jdbc.leak.ConnectionLeakException

当我打开 的报告时EntityManagerFactoryClosedTest,我什至可以看到有多少连接被泄露:

1

org.hibernate.testing.jdbc.leak.ConnectionLeakException: 1 connection(s) have been leaked! Previous leak count: 0, Current leak count: 1

SynchronizationTypeTest甚至表明以前的连接也存在泄漏:

1

org.hibernate.testing.jdbc.leak.ConnectionLeakException: 1 connection(s) have been leaked! Previous leak count: 2, Current leak count: 3

结论

检测连接泄漏是每个企业应用程序的强制性要求。虽然您可以找到定期运行并终止所有空闲数据库连接的脚本,但这只是一种创可贴的方法。

处理连接泄漏的最佳方法是修复底层代码库,以便始终正确关闭连接。为了确保生产环境没有连接泄漏,每个集成测试都必须验证实际测试的源代码或测试逻辑本身没有泄漏连接。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/191784.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年9月19日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • 何时应检测到连接泄漏?
  • 连接检漏仪
    • H2
      • 甲骨文
        • PostgreSQL
          • MySQL
          • 测试时间
          • 结论
          相关产品与服务
          云数据库 MySQL
          腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档