Oracle 锁等待、会话阻塞故障复盘:生产事故排查步骤

Oracle 锁等待、会话阻塞故障复盘:生产事故排查步骤

凌晨两点,告警群突然炸了:订单服务大面积超时,数据库 CPU 不算高,但应用线程池被打满。登录数据库一查,发现大量会话状态是 WAITING,等待事件集中在 enq: TX - row lock contention。这不是性能慢,而是典型的锁等待与会话阻塞事故。本文结合真实复盘经验,梳理一套可直接落地的排查步骤,帮你在黄金时间内快速止血、定位根因。

一、先止血:快速识别"锁源"

锁问题的核心,永远是找到阻塞源头(Blocker) ,而不是盲目杀会话。

1. 查看当前阻塞关系

vbnet 复制代码
SELECT
    s.sid,
    s.serial#,
    s.username,
    s.status,
    s.sql_id,
    s.event,
    s.wait_class,
    s.seconds_in_wait,
    s.blocking_session,
    s.blocking_session_status
FROM v$session s
WHERE s.blocking_session IS NOT NULL
ORDER BY s.seconds_in_wait DESC;

重点关注:

  • blocking_session:谁在阻塞我
  • event:等待事件,如 enq: TX - row lock contention
  • seconds_in_wait:已经等了多久

2. 定位顶层阻塞者(Root Blocker)

很多时候是"连环堵",A 堵 B,B 堵 C。要找到最顶层的 A:

vbnet 复制代码
SELECT
    s.sid,
    s.serial#,
    s.username,
    s.status,
    s.sql_id,
    s.event,
    s.seconds_in_wait
FROM v$session s
WHERE s.sid IN (
    SELECT DISTINCT blocking_session
    FROM v$session
    WHERE blocking_session IS NOT NULL
)
AND s.blocking_session IS NULL;

这个查询返回的,就是没有被人阻塞、但正在阻塞别人的会话,通常是事故源头。

3. 紧急止血:谨慎杀会话

确认是异常会话后,可临时 Kill:

sql 复制代码
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;

⚠️ 注意:

  • 优先杀源头会话,而不是被阻塞的"受害者"
  • 涉及事务的会话被 Kill 后会回滚,大事务回滚期间仍可能持续占用资源
  • 生产环境务必确认会话对应的业务模块,避免误杀核心任务

二、再定位:搞清楚"卡在哪"

找到阻塞会话后,下一步是确认它在做什么、锁了什么对象。

1. 查看会话正在执行的 SQL

sql 复制代码
SELECT sql_id, sql_text
FROM v$sql
WHERE sql_id = '上一步拿到的sql_id';

常见场景:

  • 未提交的 UPDATE / DELETE
  • 长事务中忘记 COMMIT
  • 批量更新缺少索引,导致锁范围放大

2. 查看被锁的对象

vbnet 复制代码
SELECT
    l.session_id,
    l.locked_mode,
    o.owner,
    o.object_name,
    o.object_type
FROM v$locked_object l
JOIN dba_objects o ON l.object_id = o.object_id
ORDER BY l.session_id;

locked_mode 含义速查:

  • 2:Row Share(RS)
  • 3:Row Exclusive(RX)
  • 4:Share(S)
  • 5:Share Row Exclusive(SRX)
  • 6:Exclusive(X)------ 最严格,极易引发阻塞

3. 查看事务与回滚段

ini 复制代码
SELECT
    t.xidusn,
    t.xidslot,
    t.xidsqn,
    t.status,
    t.start_time,
    t.used_ublk,
    s.sid,
    s.serial#,
    s.username
FROM v$transaction t
JOIN v$session s ON t.addr = s.taddr;

如果看到某个事务 START_TIME 很早、USED_UBLK 很大,基本可以判定是长事务惹的祸。

三、深复盘:为什么会发生

从多次生产事故中总结,Oracle 锁等待的高频根因集中在以下几类:

1. 忘记提交事务(最高频)

开发人员手动在 PL/SQL Developer 中执行了 UPDATE,改完数据后去开会、吃饭、下班,会话一直挂着,锁迟迟不释放。

典型特征

  • 阻塞会话 STATUSINACTIVE
  • EVENTSQL*Net message from client
  • 事务开始时间很早

优化建议

  • 应用使用连接池时,连接归还前必须 COMMIT/ROLLBACK
  • 禁止在线上手工执行 DML 后长时间不提交
  • 设置 IDLE_TIME 资源限制,自动清理长期空闲会话

2. 大事务批量更新

一次性更新几十万行,且没分批提交,导致:

  • 持有锁时间过长
  • 回滚段暴涨
  • 阻塞其他会话

优化建议

  • 批量更新改为 FORALL + LIMIT
  • 每 1000~5000 行 COMMIT 一次
  • 低峰期执行,并提前通知 DBA

3. 索引缺失导致锁升级

UPDATE t_order SET status = 'PAID' WHERE order_no = 'xxx'

如果 order_no 没有唯一索引,Oracle 会锁定更多行,甚至全表扫描时的锁范围会大幅放大。

优化建议

  • 高频更新字段必须有索引
  • 避免 UPDATE 条件走全表扫描

4. 并发设计不合理

多个线程同时更新同一行数据,例如秒杀场景下的库存扣减,没有使用:

  • SELECT ... FOR UPDATE NOWAIT
  • 或者应用层分布式锁

导致大量会话互相等待。

优化建议

  • 热点行更新尽量串行化
  • 使用 SKIP LOCKED 实现队列化处理
  • 业务层做幂等和重试控制

5. DDL 引发的阻塞

ALTER TABLECREATE INDEX 等 DDL 需要表级锁,会阻塞所有 DML。

优化建议

  • DDL 必须在维护窗口执行
  • 使用 ONLINE 选项(如 CREATE INDEX ONLINE
  • 执行前检查 v$locked_object

四、标准化排查 SOP(可直接照抄)

遇到锁等待告警,按以下顺序操作,通常 10 分钟内可以定位问题:

  1. 确认现象

    • 应用超时增多
    • 数据库 CPU 不高
    • 等待事件集中在 enq: TX - row lock contention
  2. 找阻塞源

    • 执行"顶层阻塞者"查询
    • 记录 SIDSERIAL#SQL_ID
  3. 判断会话状态

    • ACTIVE:正在执行 SQL,可能是慢 SQL 或大事务
    • INACTIVE:多半是忘记提交
  4. 定位 SQL 与对象

    • v$sql 获取 SQL 文本
    • v$locked_object 确认锁表
  5. 沟通与决策

    • 联系相关开发或业务方
    • 确认是否可以 Kill
    • 必要时上报值班领导
  6. 事后处理

    • Kill 会话
    • 监控回滚进度
    • 记录事故时间线

五、防患于未然:运维侧建议

  • 开启 AWR,保留至少 7 天快照,便于事后回溯
  • 配置锁等待告警(如阻塞会话超过 30 秒触发告警)
  • 定期巡检长事务:v$transaction.start_time
  • 对核心表建立索引审计,避免锁范围失控
  • 推动开发规范:禁止长事务、强制短事务、DML 必须提交

六、复盘总结模板(可直接用)

每次事故后,建议输出一份简短复盘:

  • 故障时间:2026-08-XX 02:15 ~ 02:40

  • 影响范围:订单服务超时,影响约 3% 请求

  • 根因:开发人员在测试环境误连生产,执行 UPDATE 后未提交

  • 处理动作:Kill SID=1234 会话,事务回滚耗时 2 分钟

  • 改进项

    1. 生产账号禁用 DML 权限
    2. 增加锁等待告警阈值
    3. 培训开发规范:DML 必须显式提交
相关推荐
大勇前进1 小时前
Oracle 慢 SQL 优化避坑:索引建了为什么依然走全表扫描
后端
良在掘金542231 小时前
匹配服排行榜位置原子交换与锁粒度设计
后端
用户233376852181 小时前
Docker部署Kafka排障实录
后端
程序员cxuan2 小时前
GPT - 6 Astra 的使用焚诀
人工智能·后端·程序员
南雨北斗2 小时前
Tp6 + Nginx 配置文件上传目录为public同级目录(宝塔面板)
后端
techdashen3 小时前
Go Map 详解:键值对实际上是如何存储的
开发语言·后端·golang
云上小朱3 小时前
部署安全的pg数据库,搭配pgadmin实现web界面管理
后端
行百里er3 小时前
HandlerInterceptor 和 WebFilter:两套 HTTP 请求拦截
后端·架构·监控
爱勇宝3 小时前
初创公司的“自己人”,到底能当多久?
前端·后端·程序员