如何跟踪数据库连接泄漏

tho*_*asb 7 database sql-server memory-leaks database-connection connection-leaks

我们有一个似乎有连接泄漏的应用程序(SQL Server表示已达到最大池大小).我独自在我的开发机器上(显然),只是通过导航应用程序,我触发了这个错误.SQL Server活动监视器使用我的数据库显示大量进程.

我想找到哪些文件打开连接但不使用它.我正在考虑使用像grep这样的东西,为每个文件计算".Open()"的数量和".Close()"的数量,并获得数字不相等的文件.这是现实的吗?

加分问题:SQL Server活动监视器中找到的进程是否与连接相对应?如果没有,我如何找出我的数据库中打开了多少个连接?

该应用程序是在asp.net(vb)3.5,与SQL Server 2005.我们目前不使用LINQ(尚未)或类似的东西.

谢谢

Fil*_*Vos 9

查看SQL Server端的代码时,您可以运行以下查询以获取上次在休眠连接上运行查询的视图.(打开连接什么都不做)

SELECT ec.session_id, last_read, last_write, text, client_net_address, program_name, host_process_id, login_name
FROM sys.dm_exec_connections  ec
JOIN sys.dm_exec_sessions es
  ON ec.session_id = es.session_id
CROSS APPLY sys.dm_exec_sql_text(ec.most_recent_sql_handle) AS dest
where es.status = 'sleeping'
Run Code Online (Sandbox Code Playgroud)

从应用程序端,您可以使用sos.dll进行调试,如以下文章中所述:

如果您需要有关如何使用windbg的更多信息,这些文章是一个很好的介绍:


Vla*_*cea 5

解决连接泄漏的最佳方法是在测试期间进行。

您可以使用自动化实用程序,以便每个测试都验证是否存在连接泄漏。

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

ConnectionLeakUtil如下所示:

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 );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

IdleConnectionCounter实现可以在这个[博客文章]中找到[1],和MySQL的版本是这样的:

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 );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,当您运行测试时,您会在连接泄漏时遇到故障:

:hibernate-core:test
 
org.hibernate.jpa.test.EntityManagerFactoryClosedTest > classMethod FAILED
    org.hibernate.testing.jdbc.leak.ConnectionLeakException
Run Code Online (Sandbox Code Playgroud)