必须用 OracleException.Number == 60 精准识别死锁,严禁依赖异常消息字符串;重试需紧贴DML块、显式回滚事务、重建连接与事务、加随机延迟及3次上限,并校验连接与事务状态有效性。

Oracle死锁(ORA-00060)在C#中不能靠捕获异常字符串来识别,必须用驱动返回的错误码精准判断;重试必须配合手动事务控制、显式回滚、随机延迟和次数限制,否则极易陷入重试风暴或状态错乱。
如何准确捕获 ORA-00060 而不是误判
Oracle Data Provider for .NET(ODP.NET)抛出的 OracleException 中,Number 属性值为 60 是唯一可靠依据。依赖 Message 包含 "deadlock" 或 "ORA-00060" 极其危险——日志埋点、SQL trace 开启、多语言环境都可能导致字符串不一致。
正确做法是:
- 只检查
ex.Number == 60,其他字段(如DataSource,ErrorCode)在 ODP.NET 中不保证稳定 - 避免在
catch (Exception ex)后用as OracleException转换,应直接catch (OracleException ex) - 不要在
finally或外层通用异常处理器里统一处理,必须紧贴可能触发死锁的 DML 执行块
为什么不能把重试逻辑写在 using (var conn = new OracleConnection()) 内部
Oracle 触发 ORA-00060 后,当前语句被中断,但连接仍处于打开状态,事务也未自动回滚(仅语句级回滚)。若直接在同一个 OracleTransaction 上重试,会遇到 ORA-00068(事务状态不一致)或 ORA-01002: fetch out of sequence 等衍生错误。
关键操作必须在每次重试前重置:
- 调用
transaction.Rollback()—— 不做这步,后续 DML 会沿用脏事务上下文 - 关闭并重新创建
OracleConnection实例 —— 复用旧连接可能携带已标记为失败的状态 - 重新
BeginTransaction(),而不是复用旧 transaction 对象
重试策略必须带随机延迟和上限,且不能裸循环
两个并发线程同时遭遇死锁后立即重试,大概率以完全相同顺序再次争抢资源,形成“死锁共振”。Oracle 的死锁检测是事后机制,窗口期极短,靠同步重试无法错开。
实操要点:
- 用
Thread.Sleep((int)(new Random().NextDouble() * 100))生成 0–100ms 随机休眠,避免周期性碰撞 - 硬编码重试上限为 3 次,第 4 次直接抛出原异常 —— 超过说明业务逻辑存在根本问题(如更新顺序不统一、外键缺索引)
- 每次重试都从完整业务逻辑起点开始,不要只重试单条 SQL —— 死锁常发生在多语句组合中,局部重试会破坏原子性
最容易被忽略的:重试前确认连接和事务可用性
C# 里没有类似 Spring 的 TransactionStatus.isCompleted(),但必须手动验证:连接是否仍 State == ConnectionState.Open,事务是否未提交/未回滚(transaction.Connection != null 且未调用过 Commit() 或 Rollback())。否则重试时调用 ExecuteNonQuery() 会直接抛 InvalidOperationException:“The transaction is either not active or has been disposed.”
这个检查点不在文档里强调,但在高并发下高频出现 —— 尤其当网络抖动或 Oracle RAC 切换导致连接静默失效时,不校验就重试,结果不是死锁复现,而是连接异常泛滥。


















