【问题标题】:SQL Server connection remains open, even after closingSQL Server 连接保持打开状态,即使在关闭后也是如此
【发布时间】:2019-09-11 03:44:01
【问题描述】:

我有一个设计为多层的网站。我的代码有效,但我注意到我的应用程序变得越大,SQL 数据库连接开始堆积并保持打开状态就越多。这最终会导致此错误:

System.InvalidOperationException: '超时已过期。超时 从池中获取连接之前经过的时间。这 可能已经发生,因为所有池连接都在使用中并且最大 已达到池大小。'

我的代码分为 3 层。它们是:

  1. 应用层。每次它要 CRUD,都是调用业务层。
  2. 业务层 - 执行业务逻辑。当它想与 MS SQL db 接口时,它通过ConnectionAdapter 层连接。
  3. ConnectionAdapter 继承自 SqlConnectionAdapter 类并执行实际的数据库交互。

以下是每个的伪代码:

应用程序
我的应用程序可能会多次调用业务层。特别是在执行 AJAX 请求时。一个例子是:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
    Dim dp As New DataProxy
    Dim listOfObs As New List(Of MyObject)
    dim someId as integer = 1
    Try
        If Not Page.IsPostBack Then
          listOfObs = dp.ExampleReadFuncion(someId)
        End If
    Catch ex As Exception
        Throw
    Finally
        dp.dispose()
        dp = Nothing
        SetMenue()
    End Try
End Sub

DatatProxy(业务层)

Public Class DataProxy
    Dim scConAdapter As New ConnectionAdapter

    Public Sub New()
        Try          
            scConAdapter.Connect()        
        Catch ex As Exception
            Throw      
        End Try    
    End Sub    

    Public Sub dispose()
        scConAdapter.Dispose()
    End Sub

    Private Shared Sub Main()
    End Sub

    Public Function ExampleReadFuncion(ByVal someId As Integer) As List(Of MyObject)
        Dim successFactor As LogStatusEnum = LogStatusEnum.INFO
        Dim newEx As Exception = Nothing
        Dim conn As New ConnectionAdapter
        Dim myObj As ActivityMarker
        Dim listOfObs As New List(Of MyObject)
        Dim dr As SqlDataReader = Nothing

        Try
            successFactor = LogStatusEnum.INFO
            conn.Connect()
            dr = conn.ExampleReadFuncion(someId)
            Using dr
                If (dr.HasRows = True) Then
                    While dr.Read
                        myObj = New myObj
                        myObj.Marker_Id = dr.Item("id")
                        myObj.Acitvity_Id = dr.Item("someValue")   

                        listOfObs.Add(myObj)
                    End While
                End If
            End Using
            Return listOfObs
        Catch ex As Exception
            successFactor = LogStatusEnum.ERRORS         
            Throw
        Finally
            dr.Close()
            dr = Nothing
            conn.Dispose()
            conn = Nothing     
        End Try
    End Function

End class

连接适配器

Public Class ConnectionAdapter
    Inherits SqlConnectionAdapter

    Public Sub New()
    End Sub

    Public Function ExampleReadFuncion(ByVal someId As Integer) As SqlDataReader
        Try
            Dim dr As SqlDataReader = Nothing
            Dim selectString As New StringBuilder
            Dim cmd As SqlCommand = Nothing
            Try
                cmd = CreateCommand()

                selectString.Append("SELECT * " & vbCrLf)
                selectString.Append("FROM " & vbCrLf)
                selectString.Append("dbo.mytable " & vbCrLf)
                selectString.Append("WHERE  " & vbCrLf)
                selectString.Append("id = @SOME_ID " & vbCrLf)              

                With cmd
                    .CommandType = CommandType.Text
                    .CommandText = selectString.ToString
                    .Parameters.Add("@SOME_ID", SqlDbType.Int).Value = someId

                    dr = .ExecuteReader
                End With

            Catch ex As Exception
                Throw
            Finally           
                cmd.Dispose()
            End Try
            Return dr
        Catch ex As Exception
            Throw ex
        End Try
    End Function
end class

SqlConnectionAdapter

Public MustInherit Class SqlConnectionAdapter
    Protected CurrentTransaction As SqlTransaction
    Public Property db As SqlConnection
    Public Property Password As String
    Public Property TNSName As String
    Public Property User As String
    Public Property DBName As String
    Public Property PortNumber As Integer   

    Public Overridable Sub Dispose()
        Try
            If Not CurrentTransaction Is Nothing Then
                CurrentTransaction.Commit()
            End If

        Catch ex As Exception
            Throw
        Finally
            If Not db Is Nothing Then
                db.Close()
                db.Dispose()
                db = Nothing
            End If
        End Try
    End Sub

    Public Overridable Sub Connect()
        Try
            Dim appSettings = ConfigurationManager.AppSettings

            If (appSettings("releaseVersion") = "DEBUG") Then
                Connect(appSettings("db_sqlHost"), appSettings("db_sqlDb"))
            Else
                Connect(appSettings("db_sqlHost"), appSettings("db_sqlPort"), appSettings("db_sqlDb"), appSettings("db_sqlUser"), appSettings("db_sqlPassword"))
            End If

        Catch ex As Exception
            Throw
        End Try
    End Sub

    Public Sub Connect(ByVal GetServername As String, ByVal GetDatabaseName As String)
        Try
            TNSName = GetServername
            DBName = GetDatabaseName

            db = New SqlConnection
            db = SqlConnectionUtilities.GetConnection(GetServername, GetDatabaseName)
        Catch ex As Exception
            Throw
        End Try
    End Sub

    Public Sub Connect(ByVal GetServerName As String, ByVal GetPort As Long, ByVal GetDatabase As String, ByVal GetUsername As String, ByVal Getpassword As String)
        Try            
            User = GetUsername
            Password = Getpassword
            PortNumber = GetPort
            DBName = GetDatabase
            TNSName = GetServerName

            db = New SqlConnection
            db = SqlConnectionUtilities.GetConnection(GetServerName, GetPort, GetDatabase, GetUsername, Getpassword)
        Catch ex As Exception
            Throw
        End Try
    End Sub

    Protected Function CreateCommand() As SqlCommand
        Dim ret As SqlCommand = Nothing

        Try
            ret = db.CreateCommand

            If Not CurrentTransaction Is Nothing Then
                ret.Transaction = CurrentTransaction
            End If
        Catch ex As Exception
            Throw
        Finally

        End Try
        Return ret
    End Function
    Public Sub BeginTransaction()
        If CurrentTransaction Is Nothing Then
            CurrentTransaction = db.BeginTransaction
        End If
    End Sub
    Public Sub CommitTransaction()
        If Not CurrentTransaction Is Nothing Then
            CurrentTransaction.Commit()
            CurrentTransaction.Dispose()
            CurrentTransaction = Nothing
        End If
    End Sub
    Public Sub RollbackTransaction()
        If Not CurrentTransaction Is Nothing Then
            CurrentTransaction.Rollback()
            CurrentTransaction.Dispose()
            CurrentTransaction = Nothing
        End If
    End Sub
       Protected Overrides Sub Finalize()
        MyBase.Finalize()
    End Sub
End Class

实用程序类

Public Class SqlConnectionUtilities    

    Public Shared Property connectionString As String

    Public Shared Function GetConnection(ByVal ServerName As String, ByVal Port As String, ByVal Database As String, ByVal username As String, ByVal password As String) As SqlConnection
        Dim connString As New StringBuilder
        Dim con As SqlConnection
        Try
            connString.Append("Server=tcp:" & ServerName & "," & Port & ";")
            connString.Append("Initial Catalog = " & Database & "; Persist Security Info=False;")
            connString.Append("User ID = " & username & ";")
            connString.Append("Password = " & password & ";")
            connString.Append("MultipleActiveResultSets = False;")
            connString.Append("Encrypt = True;TrustServerCertificate=False;Connection Timeout=30;")

            connectionString = connString.ToString

            con = New SqlConnection(connString.ToString)
            con.Open()
            Return con
        Catch ex As Exception
            Throw
        End Try
    End Function

    Public Shared Function GetConnection(ByVal Servername As String, ByVal DatabaseName As String) As SqlConnection
        Dim ConnectString As String
        Dim con As SqlConnection
        Try
            ConnectString = "Data Source=" & Servername & ";Initial Catalog=" & DatabaseName & ";Integrated Security=True"
            connectionString = ConnectString
            con = New SqlConnection(ConnectString)
            con.Open()
            Return con
        Catch ex As Exception
            Throw
        End Try
    End Function   
End class

我可以通过运行以下 SQL 语句判断连接保持打开状态:

SELECT 
    DB_NAME(dbid) as DBName, 
    COUNT(dbid) as NumberOfConnections,
    loginame as LoginName
FROM
    sys.sysprocesses
WHERE 
    dbid > 0
GROUP BY 
    dbid, loginame

当我的 DataProxy 类被调用时,我设置了断点。我运行 SQL 代码,可以看到打开了一个新连接。然后,我在处理 DataProxy 类时再次运行代码,我可以看到连接仍然存在。这将建立,直到它达到 101 个连接,然后它会导致上述错误。我怎么没有正确处理连接?

【问题讨论】:

  • 请记住,我们在 ADO.NET 中有 ConnectionPooling。然而,有趣的是,在所有这些代码之间,您没有发布关键的 Connect 并传递了两个或 5 个参数。我还看到一个返回 SqlConnection 的 GetDbConnection 方法,我们不知道该连接是否正确处理。
  • 很抱歉。我已经添加了连接方法。参数来自 webconfig 文件。 GetDbConnection 是无关代码,从未调用过。我删除了它。感谢您查看此内容!
  • 在你开始调整池行为之前,请 a) 删除所有仅包含 throwcatch 块,它们 100% 毫无意义,b) 删除所有 Finally 块在局部变量上调用 Dispose 并在所述变量上用 Usings 替换它们,c) 删除仅调用 Base.FinalizeFinalize 子程序,以及 d) SqlConnectionAdapter 中的 properly implement the Disposable pattern (这样做,请考虑标准做法是在 dispose 时回滚未提交的事务,而不是提交它)。

标签: asp.net sql-server vb.net


【解决方案1】:

System.InvalidOperationException: '超时已过期。在从池中获取连接之前超时时间已过。这可能是因为所有池连接都在使用中并且已达到最大池大小。'

我怎么没有正确处理连接?

您正在“泄漏”连接。 IE 你有一些打开 SqlConnection 的代码路径,并且不会关闭/处理它。 SqlConnection 保持打开状态并位于托管堆上。 最终它将被 GC 处理,其终结器将关闭连接。但是,如果您在此之前泄漏 100 个连接,则会出现此错误。

因此,您需要确保您的 SqlConnections 始终使用 USING 块关闭,或者由一些使用 USING 块关闭的 other 对象管理。

请注意,如果您从函数返回 SqlDataReader,则有一个特殊的CommandBehavior 将在 SqlDataReader 关闭时关闭 SqlConnection。

【讨论】:

  • 谢谢。这做到了。我仍然有一些浮动连接保持打开状态 (3),但我想我只需要追踪它们是从哪里打开的。
猜你喜欢
  • 2011-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 2017-06-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多