You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VB.NET多线程操作MySQL报错:连接关联已打开的DataReader

问题描述

调用MySQL数据获取方法时触发以下错误:

There is already an open DataReader associated with this Connection which must be closed first

我的实现代码如下:

1. 模块中的连接检查函数

Public Function CheckServerConn() As Boolean
        Try
            _ServerConnStr = New MySqlConnection(My.Settings.DbPath)
            If _ServerConnStr.State = ConnectionState.Open Then
                _ServerConnStr.Close()
            End If

            If _ServerConnStr.State = ConnectionState.Closed Then
                _ServerConnStr.Open()
                Return True
            End If

        Catch ex As Exception
            MsgBox("Check The Conn " & ex.Message, Me_MsgInfoStyle, Me_MsgCaptionStr)
            Return False
        End Try
#Disable Warning BC42353 ' Function doesn't return a value on all code paths
    End Function

2. ClsMySql类中的GetData子过程

'GetData
    Public Sub GetData(ByVal SqlStr As String, ByVal xDt As DataTable, ByVal xPar() As MySqlParameter)
        Using xCmd As New MySqlCommand() With {
                    .CommandType = CommandType.Text,
                    .CommandText = SqlStr,
                    .Connection = _ServerConnStr,
                    .CommandTimeout = 5000000
                    }

            If xPar IsNot Nothing Then
                For i As Integer = 0 To xPar.Length - 1
                    xCmd.Parameters.Add(xPar(i))
                Next
            End If

            Using xDa As New MySqlDataAdapter(xCmd)
                xDa.Fill(xDt)
            End Using
            xDt.Dispose()
        End Using
    End Sub

3. 数据表类中的数据获取方法

Public Sub MySql_Get_Daf()
    xClsMySql = New ClsMySql
    Dim SqlStr As String = "SELECT RegID,RegType, tblregs1.`RegRef`,`RegDate`, COUNT(`RegdID`) AS xCount 
                        ,IF(COUNT(`RegdID`) =3,'Ok','Error') AS xResult FROM tblregs1
                        INNER JOIN tblregs2 ON tblregs2.RegRef = tblregs1.RegRef
                        WHERE `RegType` = 'Daf'
                        GROUP BY tblregs1.`RegRef`  
                        ORDER BY COUNT(`RegdID`)  ASC"
    Dt_Get_Daf = New DataTable()
    xClsMySql.GetData(SqlStr, Dt_Get_Daf, Nothing)

End Sub

Public Sub MySql_Get_Qbd()
    xClsMySql = New ClsMySql
    Dim SqlStr As String = "SELECT RegID,RegType, tblregs1.`RegRef`,`RegDate`, COUNT(`RegdID`) AS xCount 
                        ,IF(COUNT(`RegdID`) =3,'Ok','Error') AS xResult FROM tblregs1
                        INNER JOIN tblregs2 ON tblregs2.RegRef = tblregs1.RegRef
                        WHERE `RegType` = 'Qbd'
                        GROUP BY tblregs1.`RegRef`  
                        ORDER BY COUNT(`RegdID`)  ASC"
    Dt_Get_Qbd = New DataTable()
    xClsMySql.GetData(SqlStr, Dt_Get_Qbd, Nothing)
End Sub

Public Sub MySql_Get_All()
    Dim xThread As Thread = New Thread(Sub() MySql_Get_Daf())
    Dim xThread2 As Thread = New Thread(Sub() MySql_Get_Qbd())

    xThread.Start()
    xThread2.Start()

End Sub

当通过按钮调用MySql_Get_All方法时触发上述错误,请问问题出在哪里?


问题原因与解决方法

核心问题

你在多线程环境下共享了同一个数据库连接对象_ServerConnStr。MySqlConnection本身不是线程安全的,当两个线程同时通过这个连接执行查询时,第一个线程的DataReader还在占用连接,第二个线程又尝试用同一个连接发起新操作,直接触发了连接被占用的错误。

另外代码还有两个额外问题:

  1. CheckServerConn每次调用都会创建新连接并打开,后续所有操作复用这个全局连接,既违背了连接池的最佳实践,也必然在多线程下冲突。
  2. GetData中调用了xDt.Dispose(),但xDt是外部传入的DataTable,你不应在方法内释放外部资源,会导致调用方后续无法使用该DataTable。

解决步骤

1. 移除全局连接,每个操作单独创建并管理连接

修改GetData方法,内部创建连接,不再依赖全局_ServerConnStr:

' 修改后的GetData方法
Public Sub GetData(ByVal SqlStr As String, ByVal xDt As DataTable, ByVal xPar() As MySqlParameter)
    ' 每个操作单独创建连接,Using会自动释放连接回连接池
    Using conn As New MySqlConnection(My.Settings.DbPath)
        Using xCmd As New MySqlCommand() With {
                    .CommandType = CommandType.Text,
                    .CommandText = SqlStr,
                    .Connection = conn,
                    .CommandTimeout = 5000000
                    }

            If xPar IsNot Nothing Then
                xCmd.Parameters.AddRange(xPar) ' 用AddRange替代循环更简洁
            End If

            conn.Open()
            Using xDa As New MySqlDataAdapter(xCmd)
                xDa.Fill(xDt)
            End Using
        End Using
    End Using
    ' 移除xDt.Dispose(),让调用方自己管理DataTable生命周期
End Sub

2. 废弃原有的CheckServerConn函数

现在每个操作都会自行创建并打开连接,不需要提前维护全局连接。连接池会自动管理连接复用,比手动维护高效且安全。

3. 优化多线程调用(可选)

可以用Task替代Thread,更符合.NET异步编程规范:

Public Async Sub MySql_Get_All()
    Await Task.WhenAll(
        Task.Run(AddressOf MySql_Get_Daf),
        Task.Run(AddressOf MySql_Get_Qbd)
    )
End Sub

关键原理

MySQL连接对象(多数ADO.NET连接对象)都是线程不安全的,绝对不能在多线程间共享。正确做法是每次数据库操作创建新连接实例,通过Using自动释放,连接池会在后台高效复用物理连接,不会造成性能损耗。


内容的提问来源于stack exchange,提问作者M.J

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 01:50:24