【问题标题】:Program not responding when executing OracleDataAdapter.Fill执行 OracleDataAdapter.Fill 时程序没有响应
【发布时间】:2021-10-06 01:01:08
【问题描述】:

我有一个使用 .NET Framework 3.5 和 Oracle.DataAccess 19.1 的 VB.NET 程序,它连接到 Oracle 远程数据库服务器 11g (pooling=false) 以查询数据。

程序运行良好,但千载难逢(不固定时间,最快可能是 2 周或几个月后),程序就挂了。在几次事件之后(每次事件后添加日志),我们能够确定并识别导致挂起的程序行,即 oracleDataAdapter.Fill(dataset),如下所示的代码 sn- p.

    Private Function FetchData(connection As OracleConnection, str_SQL As String, dataset As DataSet) As Integer
    Dim oracleDataAdapter As New OracleDataAdapter(connection.CreateCommand())

    Try
        If connection.State <> ConnectionState.Open Then
            connection.Open()
        End If

        oracleDataAdapter.SelectCommand.AddToStatementCache = False
        oracleDataAdapter.SelectCommand.NotificationAutoEnlist = False
        oracleDataAdapter.SelectCommand.CommandTimeout = 60
        oracleDataAdapter.SelectCommand.CommandText = str_SQL
        Dim int_Count As Integer = oracleDataAdapter.Fill(dataset)

        Return int_Count
    Catch ex As Exception
        LogError(ex)
    Finally
        oracleDataAdapter.Dispose()
    End Try
End Function

即使我们认为我们能够识别挂起的代码,但我们不知道为什么会挂起。我们确实尝试通过调用 OracleCommand.Cancel 来中断查询,但没有帮助。

我们虽然是网络问题,所以我们尝试通过在执行查询期间拔掉应用服务器上的网线并在几秒钟后将其重新插入来模拟oracle客户端和db服务器通信丢包。在某些情况下,程序只是挂在无限获取数据的那一行(如上面代码 sn-p 突出显示的那样)。

我们通过将数据库指向 XE 18c 进行了相同的测试。我们无法模拟上述挂起问题,而是在执行查询期间拔出和插入网线时抛出 ORA 03113 错误(通常发生在 60 秒内)。这仍然可以,因为程序能够继续而不是挂起,并且错误处理可以重试并正常恢复。

8 月 7 日添加 我们开发了程序(在 .NET 中)来尝试重现挂起问题。该程序只是不断从数据库中获取数据。在正常执行中,查询将花费约 4 秒来获取约 140,000 条记录。挂起可以模拟 15 次中的 4 次 (~30%)。

重现挂起的步骤如下

  1. 运行应用程序。该应用程序只是不断地从 数据库服务器。功能如下图。
  2. 观察查询执行,大约在第 2 或第 3 秒(基于屏幕上显示的日志),拔下网线
  3. 大约 15 秒后插回网络电缆
  4. 程序偶尔会挂在oracleDataAdapter.Fill(dataset)这一行

我们针对不同版本的非托管和托管 Oracle 客户端 11、12 和 19 以及数据库服务器 11 XE 和 18 XE 进行了测试,挂起问题能够重现。

这是行为还是 Oracle 客户端?有什么建议可以中断执行吗?或者有什么建议来处理这种行为?

【问题讨论】:

  • 开始删除 throw ex (这将终止堆栈跟踪,并且上层 catch 块无法知道错误发生的位置)。只留下 throw 但在此之前添加一个日志记录到文件以获取完整的异常数据。
  • 如果您不打算在 Catch 块中执行任何操作而是重新抛出,那么完全摆脱 Catch 块。如果您有 Finally 块,则不必拥有它。不过,正如建议的那样,如果您要重新抛出,请隐式地这样做,而不是明确地这样做。
  • 池化=假?您确定不会通过重用正在执行其他操作的连接而导致死锁吗?
  • 史蒂夫,在我的例子中,没有任何异常被捕获。该程序刚刚挂在 oracleDataAdapter.Fill(dataset)。如果出现异常,应用程序可以优雅地处理并继续。
  • @jmcilhinney,我明白这一点。在实际应用中,存在异常登录而不是重新抛出异常。我的问题是程序刚刚挂在oracleDataAdapter.Fill(dataset),没有抛出异常。因此程序无法继续。

标签: oracle vb.net


【解决方案1】:

根据OracleDataAdapter Class:

线程安全

所有公共静态方法都是线程安全的,尽管实例方法 不保证线程安全。

对于连接,建议仅在需要时才打开连接。如果一个连接持续被使用,保持它打开可能是有意义的。否则,每次都打开/关闭连接。当然,如果你还有其他需要使用连接执行的操作,你可以将连接作为参数传递给方法。但是,请在完成操作后关闭连接。

以下假设已安装 NuGet 包Oracle.ManagedDataAccess。

尝试以下方法之一:

Imports Oracle.ManagedDataAccess.Client

Public Module Helper

    Public Function FetchDataDS(connectionStr As String, str_SQL As String) As DataSet
        Dim int_Count As Integer = 0
        Dim ds As DataSet = New DataSet()

        Using connection As OracleConnection = New OracleConnection(connectionStr)
            'open
            connection.Open()

            Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

                int_Count = oracleDataAdapter.Fill(ds)
            End Using
        End Using

        Return ds
    End Function

    Public Function FetchDataDS(connection As OracleConnection, str_SQL As String) As DataSet
        Dim int_Count As Integer = 0
        Dim ds As DataSet = New DataSet()

        If connection.State = ConnectionState.Closed Then
            'open
            connection.Open()
        End If

        If Not connection.State = ConnectionState.Open Then
            Throw New Exception("Error (FetchDataDS) - Connection state is: " & connection.State.ToString())
        End If

        Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

            int_Count = oracleDataAdapter.Fill(ds)
        End Using

        Return ds
    End Function

    Public Function FetchDataDS(connectionStr As String, str_SQL As String, ds As DataSet) As Integer
        Dim int_Count As Integer = 0

        Using connection As OracleConnection = New OracleConnection(connectionStr)
            'open
            connection.Open()

            Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

                int_Count = oracleDataAdapter.Fill(ds)
            End Using
        End Using

        Return int_Count
    End Function

    Public Function FetchDataDS(connection As OracleConnection, str_SQL As String, ds As DataSet) As Integer
        Dim int_Count As Integer = 0

        If connection.State = ConnectionState.Closed Then
            'open
            connection.Open()
        End If

        If Not connection.State = ConnectionState.Open Then
            Throw New Exception("Error (FetchDataDS) - Connection state is: " & connection.State.ToString())
        End If

        Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

            int_Count = oracleDataAdapter.Fill(ds)
        End Using

        Return int_Count
    End Function

    Public Function FetchDataDT(connectionStr As String, str_SQL As String) As DataTable
        Dim int_Count As Integer = 0
        Dim dt As DataTable = New DataTable()

        Using connection As OracleConnection = New OracleConnection(connectionStr)
            'open
            connection.Open()

            Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

                int_Count = oracleDataAdapter.Fill(dt)
            End Using
        End Using

        Return dt
    End Function

    Public Function FetchDataDT(connection As OracleConnection, str_SQL As String) As DataTable
        Dim int_Count As Integer = 0
        Dim dt As DataTable = New DataTable()

        If connection.State = ConnectionState.Closed Then
            'open
            connection.Open()
        End If

        If Not connection.State = ConnectionState.Open Then
            Throw New Exception("Error (FetchDataDT) - Connection state is: " & connection.State.ToString())
        End If

        Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

            int_Count = oracleDataAdapter.Fill(dt)
        End Using

        Return dt
    End Function

    Public Function FetchDataDT(connection As OracleConnection, str_SQL As String, dt As DataTable) As Integer
        Dim int_Count As Integer = 0

        If connection.State = ConnectionState.Closed Then
            'open
            connection.Open()
        End If

        If Not connection.State = ConnectionState.Open Then
            Throw New Exception("Error (FetchDataDT) - Connection state is: " & connection.State.ToString())
        End If

        Using oracleDataAdapter As OracleDataAdapter = New OracleDataAdapter With {
                .SelectCommand = New OracleCommand With {
                        .AddToStatementCache = False,
                        .NotificationAutoEnlist = False,
                        .CommandTimeout = 60,
                        .Connection = connection,
                        .CommandText = str_SQL
                    }}

            int_Count = oracleDataAdapter.Fill(dt)
        End Using

        Return int_Count
    End Function
End Module

无论你在哪里捕获异常,也要捕获 Oracle.ManagedDataAccess.Client.OracleException -

类似于以下内容:

Try
    'ToDo: add desired code
       ...
Catch ex As OracleException
    'ToDo: log exception
Catch ex As Exception
    'ToDo: log exception
End Try

资源

【讨论】:

  • 在我的示例中,连接是从函数外部传递的。它在调用函数之前被打开。该应用程序在应用程序的整个生命周期中使用相同的打开连接。在程序结束之前不会关闭连接。这有关系吗?
  • 这是一个后端应用程序,它通过大量数据库事务快速执行任务,因此在应用程序的整个生命周期中保持打开连接。在函数中,我们确实实现了If not connection.open = ConnectionState.Open Then connection.open。但是应用程序有时仍然挂在oracleDataAdapter.Fill(dataset)。
  • 是否添加了连接未打开时会引发错误的代码?您的代码是在类还是模块中?在填充语句之前记录ConnectionState 和ConnectionTimeout 的值。您可以考虑在帖子中添加更多代码,例如调用 FetchData 的代码,包括 SQL 语句。
  • 您也可以尝试运行更接近您的数据库版本的Oracle.ManagedDataAccess 版本(即:12.1)
  • 此代码已添加If connection .State &lt;&gt; ConnectionState.Open Then connection.Open()。如果打开失败,将由 catch 异常块处理并记录错误。代码在一个类中。根据应用程序日志,没有发现任何错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多