使用.NET MySQL Connector时并发事务导致锁死超时问题排查
问题根因与解决方案
核心根因
- 代码未对开启的事务执行显式
Commit/Rollback操作,FOR UPDATE获取的行锁会被事务长期持有,直到连接被回收才会释放,超出预期的等待周期。 - 单进程内多async函数运行时,WinForm/WPF等带UI同步上下文的环境中,
await默认会尝试回到原同步上下文执行后续代码,多个async调用争抢同一个UI线程上下文会导致逻辑阻塞,第一个事务无法及时提交释放锁,第二个查询等待锁超时。这也是多进程场景下运行正常的原因:每个进程有独立的同步上下文,不会出现同上下文争抢问题。 - 连接、命令对象未做规范化释放管理,异常场景下容易出现连接泄漏,耗尽连接池资源后会进一步加剧连接超时问题。
修复方案
1. 新增事务显式提交/回滚逻辑,所有await调用加ConfigureAwait(false)
显式控制事务生命周期,同时禁用await对原同步上下文的捕获,避免线程争抢死锁:
Dim conn As New MySqlConnection(MySqlConnectionString) Dim trans As MySqlTransaction = Nothing Try Await conn.OpenAsync().ConfigureAwait(false) trans = conn.BeginTransaction() Dim cmd As New MySqlCommand("Set time_zone='-4:00'; Select * From Products For Update;", conn, trans) Await cmd.ExecuteNonQueryAsync().ConfigureAwait(false) Await Task.Delay(10000).ConfigureAwait(false) trans.Commit() ' 业务执行完成后立即提交事务释放行锁 Catch ex As Exception If trans IsNot Nothing Then trans.Rollback() ' 异常场景下回滚事务释放锁 MsgBox("error " + ex.Message) Finally conn.Close() conn.Dispose() End Try
2. 用Using块自动管理资源释放
不需要手动编写资源释放逻辑,避免连接/命令对象泄漏:
Using conn As New MySqlConnection(MySqlConnectionString) Dim trans As MySqlTransaction = Nothing Try Await conn.OpenAsync().ConfigureAwait(false) trans = conn.BeginTransaction() Using cmd As New MySqlCommand("Set time_zone='-4:00'; Select * From Products For Update;", conn, trans) cmd.CommandTimeout = 15 ' 可选:设置命令超时时间,避免无限等待锁 Await cmd.ExecuteNonQueryAsync().ConfigureAwait(false) End Using Await Task.Delay(10000).ConfigureAwait(false) trans.Commit() Catch ex As Exception If trans IsNot Nothing Then trans.Rollback() MsgBox("error " + ex.Message) End Try End Using ' 代码块执行完后自动释放连接资源
3. 可选优化
如果业务场景允许,可以调整MySQL事务隔离级别,或者为查询增加NOWAIT/SKIP LOCKED语法,避免查询长时间阻塞等待锁。
内容的提问来源于stack exchange,提问作者LegendaryWings
相关产品推荐
相关产品推荐

