WinForms应用使用LOAD DATA LOCAL INFILE导入CSV至MySQL时,文件在Excel打开状态下触发ContextSwitchDeadlock异常问题排查
问题分析与解决方案
首先,我们拆解一下你遇到的问题根源:
为什么Excel打开文件时会触发异常?
- Excel打开CSV文件时会独占锁定文件(不允许其他程序读取该文件),而Notepad/Notepad++这类文本编辑器使用的是共享读取锁定,所以你的WinForms程序能正常读取文件并发送给MySQL服务器。
- 当文件被Excel独占锁定时,你的代码执行
SQL.ExecuteNonQuery()会一直卡在等待读取文件的状态,而这段代码是在WinForms的UI线程(STA线程)中执行的。STA线程要求定期处理Windows消息泵,长时间阻塞(超过60秒)就会触发ContextSwitchDeadlock这个调试助手提示——本质是UI线程太久没响应消息,系统认为可能出现了死锁。 - HeidiSQL能正常导入的原因:它大概率是先将文件内容读取到内存(或者使用了允许绕过独占锁定的文件访问方式),再发送给MySQL服务器;另外它的导入操作可能是在后台线程执行的,不会阻塞STA线程的消息处理。
具体解决方案
方案1:规避文件独占锁定——复制文件到临时目录再导入
既然Excel锁定了原文件,我们可以先把CSV文件复制到一个临时位置,再导入这个临时文件,绕过锁定限制:
' 先复制文件到临时目录 Dim tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() & ".csv") File.Copy(selectedPath & fileName, tempPath, True) ' 修改导入语句为临时文件路径 importStr = "Load data local infile '" + tempPath.Replace("\", "\\") + "' into table " + targetTable + " columns terminated by '" + TextBox1.Text + "' optionally enclosed by '""' lines terminated by '" + TextBox2.Text + "' ignore 1 lines" ' 执行导入... Queries.ExecQueryAdminNoSess(importStr, MysqlConn) ' 导入完成后删除临时文件(可选) File.Delete(tempPath)
方案2:将导入操作移到后台线程,避免阻塞UI线程
ContextSwitchDeadlock的直接原因是UI线程被长时间阻塞,所以把导入逻辑放到后台线程执行,让UI线程能正常处理消息:
方式一:使用BackgroundWorker(WinForms传统实现)
' 在UI按钮点击事件中启动后台任务 Private Sub btnImport_Click(sender As Object, e As EventArgs) Handles btnImport.Click Dim worker As New BackgroundWorker() AddHandler worker.DoWork, AddressOf ImportInBackground AddHandler worker.RunWorkerCompleted, AddressOf ImportCompleted ' 传递需要的参数到后台线程 worker.RunWorkerAsync(New With { .SelectedPath = selectedPath, .FileName = fileName, .TargetTable = targetTable, .Delimiter = TextBox1.Text, .LineTerminator = TextBox2.Text }) End Sub Private Sub ImportInBackground(sender As Object, e As DoWorkEventArgs) Dim args = e.Argument Dim importStr = "Load data local infile '" + args.SelectedPath.Replace("\", "\\") + args.FileName + "' into table " + args.TargetTable + " columns terminated by '" + args.Delimiter + "' optionally enclosed by '""' lines terminated by '" + args.LineTerminator + "' ignore 1 lines" ' 注意:后台线程不要直接操作UI,所有UI反馈放到RunWorkerCompleted Using conn As New MySqlConnection(yourConnectionString) ' 建议在这里创建独立连接,避免跨线程共享 Try conn.Open() Using cmd As New MySqlCommand(importStr, conn) cmd.CommandTimeout = 1200 cmd.ExecuteNonQuery() End Using Catch ex As MySqlException e.Result = ex ' 传递异常到完成事件 End Try End Using End Sub Private Sub ImportCompleted(sender As Object, e As RunWorkerCompletedEventArgs) If e.Error IsNot Nothing Then MsgBox("导入失败:" & e.Error.Message) ElseIf e.Result IsNot Nothing AndAlso TypeOf e.Result Is MySqlException Then Dim ex = DirectCast(e.Result, MySqlException) MsgBox("Error: " & ex.Number & " - " & ex.Message) Else MsgBox("导入成功!") End If End Sub
方式二:使用Task.Run(更现代的异步实现)
Private Async Sub btnImport_Click(sender As Object, e As EventArgs) Handles btnImport.Click btnImport.Enabled = False ' 禁用按钮防止重复点击 Try ' 提前读取UI值,避免后台线程访问UI控件 Dim delimiter = TextBox1.Text Dim lineTerminator = TextBox2.Text Dim tempImportStr = $"Load data local infile '{selectedPath.Replace("\", "\\") + fileName}' into table {targetTable} columns terminated by '{delimiter}' optionally enclosed by '""' lines terminated by '{lineTerminator}' ignore 1 lines" Await Task.Run(Sub() Using conn As New MySqlConnection(yourConnectionString) conn.Open() Using cmd As New MySqlCommand(tempImportStr, conn) cmd.CommandTimeout = 1200 cmd.ExecuteNonQuery() End Using End Using End Sub) MsgBox("导入成功!") Catch ex As MySqlException MsgBox("Error: " & ex.Number & " - " & ex.Message) Catch ex As Exception MsgBox("导入失败:" & ex.Message) Finally btnImport.Enabled = True ' 恢复按钮状态 End Try End Sub
方案3:确认MySQL客户端配置(辅助检查)
确保你的MySQL连接字符串启用了AllowLoadLocalInfile选项,虽然你的代码在文件未锁定时能正常运行,这个配置应该没问题,但可以再次确认:
Dim connStr = "server=yourServer;user=yourUser;password=yourPass;database=yourDb;AllowLoadLocalInfile=True"
总结
优先推荐方案1+方案2的组合:既解决了Excel独占文件的问题,又避免了UI线程阻塞导致的异常,同时还能提升用户体验(导入过程中UI不会卡死)。
内容的提问来源于stack exchange,提问作者Ivaylo
相关产品推荐
相关产品推荐

