PL/SQL中手动调用DBMS_TRANSACTION.PREPARE_TRANSACTION会绕过RECO自动2PC协调,导致本地与远端数据库在PREPARED状态长期持锁并互相等待,形成跨实例死锁闭环;SAVEPOINT回滚无法释放远程锁,IN-DOUBT事务残留亦会造成伪死锁。

PL/SQL里显式调用 DBMS_TRANSACTION.PREPARE_TRANSACTION 会绕过自动2PC协调
Oracle默认的分布式事务(通过数据库链接 DBLINK)由后台 RECO 进程自动管理两阶段提交(2PC),但一旦你在PL/SQL中手动执行 DBMS_TRANSACTION.PREPARE_TRANSACTION 或 DBMS_TRANSACTION.FORCE,就等于跳过了协调器的统一调度,直接进入“准备”状态并长期持锁。此时若远端数据库响应延迟、网络中断或本地事务未及时 COMMIT FORCE/ROLLBACK FORCE,本地会话就会在 PREPARED 状态下持续持有行锁/表锁,而远端可能也卡在等待确认——双方互相等对方“表态”,形成跨实例的死锁闭环。
DBLINK 查询嵌套在未提交事务中触发隐式分布式锁升级
常见错误模式:在一个本地事务里执行 SELECT ... FROM remote_table@dblink,接着又对本地表做 UPDATE。Oracle会将该查询识别为分布式操作,并在本地事务中提前申请全局事务ID(GTID),同时对涉及的远程对象加分布式意向锁。如果此时另一个会话也在做类似操作(比如它先查本地表再查远程表),且两者的资源访问顺序不一致(一个先本地后远程,另一个相反),就会复现经典的“交叉锁等待”结构。更麻烦的是,这种锁不是普通X/S锁,而是 DX(Distributed Transaction Entry)锁,v$lock 中类型为 DX,lmode=6(排他)且 block=1 时基本可判定为分布式死锁源头。
使用 SAVEPOINT + ROLLBACK TO 在分布式上下文中无法释放已获取的远程锁
PL/SQL开发者常误以为在分布式事务中设 SAVEPOINT 并回滚到它,就能清理所有中间状态。实际并非如此:ROLLBACK TO SAVEPOINT 只能回滚本地DML变更和本地锁,但通过 DBLINK 已触发的远程查询或DML所持有的锁(尤其是远端数据库上的行级X锁)不会被释放。这些锁会一直挂在那里,直到整个分布式事务超时(默认60分钟)或被DBA手工清理。期间任何尝试访问相同远程行的会话都会被阻塞,进而可能诱发链式等待——尤其当多个PL/SQL过程反复重试同一逻辑时,问题会指数级放大。
真正难排查的是 IN-DOUBT 事务残留导致的伪死锁
网络闪断或远端实例崩溃后,本地数据库可能留下状态为 IN-DOUBT 的事务(查 dba_2pc_pending 可见)。这些事务虽不再活跃,但它们占用的本地锁资源(如 TX 锁)并未释放,且 Oracle 不会主动唤醒或清理它们。此时你看到的现象是:某个 UPDATE 语句莫名被阻塞,v$session 显示 event = 'enq: TX - row lock contention',但找不到谁在持锁——因为持锁者是早已“失联”的 IN-DOUBT 事务。这种情况不是传统意义的死锁(Oracle不会报ORA-00060),但行为完全等效,且必须靠 EXECUTE DBMS_TRANSACTION.RECOVER 或人工 COMMIT FORCE/ROLLBACK FORCE 解决。
最易被忽略的一点:分布式事务的锁生命周期不遵循本地事务的ACID边界。一次 DBLINK 调用引发的锁,可能跨越多个PL/SQL块、甚至多个会话连接,只要GTID没终结,锁就还在。别只盯着当前SQL,得顺藤摸瓜查 global_transaction_id 和 dba_2pc_pending。











