确认死锁需交叉验证v$locked_object、v$session和v$sqltext:先查排他锁对象,再看阻塞会话链,最后提取SQL;ORA-00060需查trace文件中的Deadlock graph定位冲突语句;ATOMIC_REFRESH=FALSE可避免长事务锁表;MLOG$统计信息过期会导致刷新卡顿而非显式锁。

查v$locked_object发现MLOG$_*或基表被锁,怎么确认是不是死锁源头
不是所有锁都等于死锁,但物化视图刷新卡住时,v$locked_object里出现MLOG$_*表或主表名,大概率就是它——因为快速刷新必须读日志表+对基表做SELECT FOR UPDATE,而业务写入正改同一行,就容易形成A→B→A的闭环。
关键动作是交叉验证三张视图:
- 先跑
SELECT object_name, locked_mode FROM v$locked_object lo, dba_objects ao WHERE lo.object_id = ao.object_id,盯住locked_mode = 6(排他锁)的条目 - 再查
SELECT sid, serial#, sql_id, event, blocking_session FROM v$session WHERE sid IN (SELECT session_id FROM v$locked_object),看有没有会话的blocking_session指向另一个被锁会话 - 最后用
SELECT sql_text FROM v$sqltext WHERE (hash_value, address) IN (SELECT sql_hash_value, sql_address FROM v$session WHERE sid = ?)捞出对应SQL,确认是不是DBMS_MVIEW.REFRESH或长事务UPDATE
如果event是enq: TX - row lock contention且两个会话互相出现在对方的blocking_session字段里,基本可断定死锁已发生。
ORA-00060报错后,怎么从alert log和trace文件定位冲突SQL
ORA-00060一定写进alert_[SID].log,但日志只记“发生了”,不记“谁干的”。真正有用的线索在trace文件里——Oracle自动选一个牺牲者后,会在对应会话的trace路径下生成带死锁图的详细记录。
操作步骤很直接:
- 去
$ORACLE_BASE/diag/rdbms/[DBNAME]/[SID]/trace/目录下找最新生成的*_ora_*.trc文件(文件名含ORA-00060关键字) - 打开后搜
Deadlock graph,里面会明确标出Blocker和Waiter各自的sql_id、持有锁的rowid、等待的资源类型 - 拿
sql_id反查v$sql:SELECT sql_text FROM v$sql WHERE sql_id = 'xxx',就能看到具体哪条UPDATE和哪条MV刷新语句撞上了
注意:trace文件默认只保留最近几次,别等几天后再翻——当天出问题当天查。
DBMS_MVIEW.REFRESH调用时ATOMIC_REFRESH=FALSE为什么能破死锁
默认ATOMIC_REFRESH => TRUE会让整个刷新包在一个事务里跑:先删旧数据、再插新数据,全程加锁、写undo。对千万级分区表,这等于把基表锁死几十秒,业务DML进来必然排队、升级、死锁。
设成FALSE后,Oracle改走「TRUNCATE + INSERT /*+ APPEND */」路径:
- TRUNCATE不走undo,不记回滚段,也不触发触发器,锁粒度降到表级且瞬间完成
- INSERT用直接路径加载,跳过buffer cache,避免大量逻辑读引发的争用
- 代价是MV在刷新窗口期为空——应用层得能容忍这个短暂不可用,否则得配合分区交换方案
手动刷时写清楚:DBMS_MVIEW.REFRESH('MV_NAME', atomic_refresh => FALSE);定时任务里千万别依赖默认值,必须显式传参。
为什么查不到锁却刷新卡死?小心MLOG$统计信息过期
现象是v$locked_object空空如也,v$session里也没明显阻塞,但DBMS_MVIEW.REFRESH就是不动——八成是快速刷新卡在扫描MLOG$_*日志表上,而这张表没索引、统计信息过期,导致全表扫描+磁盘排序,看起来像“挂了”。
立刻检查三件事:
- 建索引:
CREATE INDEX idx_mlog_snap_seq ON MLOG$_TABLENAME (snaptime$$, sequence$$),顺序不能颠倒 - 更新统计信息:
EXEC DBMS_STATS.GATHER_TABLE_STATS(user, 'MLOG$_TABLENAME', estimate_percent => 100),别信默认采样率 - 清积压:
SELECT COUNT(*) FROM MLOG$_TABLENAME WHERE snaptime$$ ,如果结果超10万,马上<code>EXEC DBMS_MVIEW.PURGE_LOG('TABLENAME', 1)
这类问题不会报错,只会让你盯着光标发呆——它不是锁,是慢到像死了一样。


















