在数据库运维工作中,锁阻塞问题就像交通拥堵一样常见却又棘手。达梦8作为国产数据库的代表产品,其锁机制与阻塞场景有着独特的设计特点。这个巡检SQL脚本的诞生,源于我在实际运维中多次遇到的紧急情况——当业务系统突然卡顿,DBA需要快速定位到底是哪个会话阻塞了关键业务表,而手动排查往往要花费大量时间。
这个脚本的价值在于:它能像CT扫描仪一样,快速透视数据库内部的锁等待链条,精准定位阻塞源头。不同于简单的锁查询语句,它通过关联多张系统视图,自动构建阻塞关系树,并输出关键会话信息、SQL文本和等待时长,让DBA能在5分钟内完成过去需要半小时的故障定位。
达梦8采用多粒度锁机制,包含表级锁(TM)和行级锁(TX)。与Oracle的锁架构类似,但有以下特殊设计:
脚本的核心逻辑是通过递归CTE(WITH子句)遍历三张关键视图:
关键关联字段是SESSION_ID和BLOCKING_SESSION,通过BLOCKING_SESSION不为空的记录找到阻塞源头,再反向构建出完整的阻塞链条。
sql复制WITH BLOCK_TREE AS (
-- 基础查询:找出所有被阻塞的会话
SELECT
s.SESS_ID,
s.SQL_TEXT,
s.STATUS,
s.LOGON_TIME,
l.BLOCKED,
l.BLOCKING_SESS,
s.USERNAME,
s.CLIENT_ADDR,
0 AS LEVEL,
CAST(s.SESS_ID AS VARCHAR(1000)) AS PATH
FROM
V$SESSION s
JOIN V$LOCK l ON s.SESS_ID = l.SESS_ID
WHERE
l.BLOCKED = 1
UNION ALL
-- 递归部分:向上查找阻塞源
SELECT
s.SESS_ID,
s.SQL_TEXT,
s.STATUS,
s.LOGON_TIME,
l.BLOCKED,
l.BLOCKING_SESS,
s.USERNAME,
s.CLIENT_ADDR,
bt.LEVEL + 1,
CAST(bt.PATH || '->' || s.SESS_ID AS VARCHAR(1000))
FROM
BLOCK_TREE bt
JOIN V$SESSION s ON bt.BLOCKING_SESS = s.SESS_ID
JOIN V$LOCK l ON s.SESS_ID = l.SESS_ID
)
SELECT
LEVEL AS 阻塞层级,
SESS_ID AS 会话ID,
USERNAME AS 数据库用户,
CLIENT_ADDR AS 客户端IP,
STATUS AS 会话状态,
LOGON_TIME AS 登录时间,
SQL_TEXT AS 执行SQL,
PATH AS 阻塞路径
FROM
BLOCK_TREE
ORDER BY
LEVEL, PATH;
| 字段名 | 说明 |
|---|---|
| 阻塞层级 | 0表示直接受害者,数字越大离阻塞源越近 |
| 会话ID | 达梦会话的唯一标识符 |
| 数据库用户 | 执行操作的数据库账号 |
| 客户端IP | 发起会话的客户端地址 |
| 会话状态 | ACTIVE表示正在执行,INACTIVE表示空闲 |
| 登录时间 | 会话建立的时间点 |
| 执行SQL | 当前正在执行的SQL文本(可能被截断) |
| 阻塞路径 | 用"->"连接的会话ID链条,直观展示阻塞关系 |
在大型生产环境中,建议添加以下过滤条件:
sql复制WHERE
-- 只查活跃会话
STATUS = 'ACTIVE'
-- 排除系统会话
AND USERNAME NOT IN ('SYSDBA','SYSAUDITOR')
-- 只显示阻塞超过5分钟的情况
AND LOGON_TIME < SYSDATE - 5/1440
当收到业务系统卡顿报警时:
建议每天低峰期运行一次脚本,重点关注:
报表查询阻塞OLTP:
批量更新锁升级:
外键未索引:
为后续分析建议保存历史数据:
sql复制-- 创建历史记录表
CREATE TABLE DBA_BLOCK_HISTORY AS
SELECT * FROM BLOCK_TREE WHERE 1=0;
-- 定期插入数据(可配置为JOB)
INSERT INTO DBA_BLOCK_HISTORY
SELECT SYSDATE AS COLLECT_TIME, bt.*
FROM BLOCK_TREE bt;
基于历史数据可以构建:
对于已知模式的阻塞,可编写PL/SQL脚本自动处理:
sql复制DECLARE
v_top_blocker SYS_REFCURSOR;
BEGIN
-- 查找阻塞超过10分钟的会话
OPEN v_top_blocker FOR
SELECT SESS_ID FROM BLOCK_TREE
WHERE LEVEL = (SELECT MAX(LEVEL) FROM BLOCK_TREE)
AND LOGON_TIME < SYSDATE - 10/1440;
-- 自动kill阻塞源
FOR r IN v_top_blocker LOOP
EXECUTE IMMEDIATE 'ALTER SYSTEM KILL SESSION ''' || r.SESS_ID || '''';
DBMS_OUTPUT.PUT_LINE('Killed session: ' || r.Sess_ID);
END LOOP;
END;
重要提示:自动化KILL操作需谨慎,建议先保存会话信息再操作,并确保有审批流程