注册
达梦数据库卡顿问题排查实战
专栏/技术分享/ 文章详情 /

达梦数据库卡顿问题排查实战

潘栋民 2026/09/18 347 0 0
摘要

达梦数据库卡顿问题排查实战

分享卡顿问题排查步骤


一、30 秒定性:先分清是哪一类"卡"

先确认卡顿现属于什么类型:某个功能点转圈,其他功能正常;所有操作都慢,且持续;报表/统计类操作慢,交易正常;突然全库卡死,写入报磁盘错;每隔一段时间规律性卡一阵

现象 大概率归属 直接跳到
某个功能点转圈,其他功能正常 锁阻塞(SQL 本身不慢,在等别人) 二、锁阻塞
所有操作都慢,且持续 OS 资源瓶颈或全局 SQL 劣化 四、OS 资源
报表/统计类操作慢,交易正常 慢 SQL / 索引缺失 三、慢 SQL
突然全库卡死,写入报磁盘错 归档满 / redo 异常 五、归档 redo
每隔一段时间规律性卡一阵 定时任务(备份/统计信息)撞峰 检查撞上规律时间的业务

一条总入口 SQL(先跑这个,看活跃会话在等什么):

select s.sess_id "会话ID", ora_hash(sql_text) "sqlHash码", case when w.id is not null then '被阻塞,源sess='||(select cast(min(sess_id) as varchar(20)) from v$sessions ss where ss.trx_id=w.wait_for_id) when w2.wait_for_id is not null then '★阻塞源(杀我)' else '' end "阻塞", case when datediff(ss, s.last_recv_time, sysdate) between 0 and 8640000 then datediff(ss, s.last_recv_time, sysdate) else 0 end "执行秒", s.state "状态", 'sp_close_session('||cast(s.sess_id as varchar(20))||');' "关闭语句", s.trx_id "事务ID", (select t.ins_cnt||'/'||t.del_cnt||'/'||t.upd_cnt from v$trx t where t.id=s.trx_id) "增/删/改", (select decode(t.rollback_flag,1,'回滚中','') from v$trx t where t.id=s.trx_id) "回滚", s.user_name "用户", s.clnt_ip "客户端IP", s.appname "程序", round((select sum(m.total_size)/1024.0/1024 from v$mem_pool m where m.creator=s.thrd_id),1) "内存MB", dbms_lob.substr(sf_get_session_sql(s.sess_id),4000,1) "SQL" from v$sessions s left join v$trxwait w on s.trx_id = w.id left join v$trxwait w2 on s.trx_id = w2.wait_for_id where s.state = 'ACTIVE' or s.trx_id in (select id from v$trxwait) or s.trx_id in (select wait_for_id from v$trxwait) order by 4 desc;
  • 快速判断当前会话是否有锁;语句执行时长是不是太长

二、热点一:锁阻塞 —— 最高频的"假卡顿"

2.1 一条毫秒级 SQL 卡了 6 秒场景

构造场景:会话 A 更新一行不提交,会话 B 更新同一行。

-- 会话 A(持有锁,不提交) UPDATE T_LOCK_DEMO SET NAME='BY_SESS_A' WHERE ID=1; -- 会话 B(请求同一行 → 阻塞) UPDATE T_LOCK_DEMO SET NAME='BY_SESS_B' WHERE ID=1;

实测观察到的等待链:

V$TRXWAIT
ID                   WAIT_FOR_ID          WAIT_TIME   THRD_ID
-------------------- -------------------- ----------- -----------
42517                42515                5918        6842

会话 B 的执行结果:

影响行数 1
已用时间: 00:00:06.272. 执行号:1701.

关键解读:这条 UPDATE 本身只需毫秒级,实际却耗时 6.272 秒——等待时间占比超过 99.9%。

这就是锁阻塞的典型特征:SQL 没变慢,是在排队。若此时去优化 SQL、加索引,方向完全错误。

2.2 定位三板斧

第一斧:看等待链(谁在等谁)

SELECT ID, WAIT_FOR_ID, WAIT_TIME, THRD_ID FROM V$TRXWAIT; -- ID=等待方事务号 WAIT_FOR_ID=被等的事务号 WAIT_TIME=已等待毫秒数

第二斧:定位阻塞源头会话

SELECT S.SESS_ID, S.SQL_TEXT, S.CLNT_IP, S.APPNAME, S.USER_NAME, S.TRX_ID, S.LAST_RECV_TIME FROM V$SESSIONS S WHERE S.TRX_ID IN ( SELECT WAIT_FOR_ID FROM V$TRXWAIT );

拿到 SQL_TEXT 和 CLNT_IP 后,就能确定:是哪台机器、哪个应用、哪条 SQL 没提交。

第三斧:看源头会话开了多久、在干什么

SELECT SESS_ID, TRX_ID, CREATE_TIME, STATE, SQL_TEXT FROM V$SESSIONS WHERE TRX_ID = <上一步的 WAIT_FOR_ID>;

最常见的元凶:应用开启事务后未提交(AUTO_COMMIT=0 且代码中漏了 commit),
或某个批量任务中途异常退出留下悬挂事务。

2.3 处置

手段 命令 适用场景
通知提交(首选) 联系应用方提交/回滚 能定位到责任人
强制断开会话 SP_CLOSE_SESSION(&lt;sess_id>); 紧急恢复,事务自动回滚
-- 紧急处置:关闭阻塞源头会话 SP_CLOSE_SESSION(阻塞源头SESS_ID);

⚠️ 注意:关闭会话会回滚其未提交事务。若该事务很大,回滚本身可能耗时很久,期间锁仍不释放。生产环境务必先确认影响。

⚠️ 注意:万一关闭了大事务,可以通过以下语句查询回滚的事务剩余行数,只能等待事务完成回滚,万万不可胡乱重启数据库。

select SQL_TEXT 会话SQL, INS_CNT 插入回滚记录个数, DEL_CNT 删除回滚记录个数, UPD_CNT 更新回滚记录个数, ROLLBACK_FLAG 是否在回滚 from V$TRX T, V$SESSIONS S WHERE T.SESS_ID=S.SESS_ID AND STATE='ACTIVE';

2.4 根治建议

  1. 应用侧:事务边界最小化,杜绝"开启事务 → 调用外部接口 → 再提交"的写法
  2. 设置锁等待超时,避免无限期排队:
    -- 会话级:等待 N 秒后返回超时错误而非死等 ALTER SESSION SET DDL_WAIT_TIME = 10;
  3. 定期巡检:把 V$TRXWAIT 纳入监控告警(等待超过 5 秒即告警)

三、热点二:慢 SQL 与索引缺失 —— 真卡顿

3.1 实测对比:5 万行表的差距

构造 5 万行测试表,执行 WHERE CODE='C49999' 查询:

无索引(全表扫描)

1   #NSET2: [6, 1250, 112]
2     #PRJT2: [6, 1250, 112]
3       #SLCT2: [6, 1250, 112]; T_BIG_DEMO.CODE = 'C49999'
4         #CSCN2: [6, 50000, 112]; INDEX33555510(T_BIG_DEMO)

实测统计:EXEC_TIME=29ms,LOGIC_READ_CNT=197,TAB_SCAN_CNT=1

创建索引后

1   #NSET2: [1, 1250, 112]
2     #PRJT2: [1, 1250, 112]
3       #BLKUP2: [1, 1250, 112]; IDX_BIG_CODE(T_BIG_DEMO)
4         #SSEK2: [1, 1250, 112]; scan_range['C49999','C49999']

实测统计:EXEC_TIME=5ms,LOGIC_READ_CNT=26,TAB_SCAN_CNT=0

对比汇总:

指标 全表扫描 索引扫描 改善
优化器代价 6 1 ↓ 83%
逻辑读 197 26 ↓ 87%
执行耗时 29 ms 5 ms ↓ 83%
全表扫描次数 1 0 消除

5 万行的小表差距已达 6 倍。生产环境百万/千万行表,差距是数量级的,
且会连带推高 IO、挤占缓冲池,拖慢整库。

3.2 执行计划算子速读

操作符 含义 看到就要警惕
CSCN2 聚集索引全扫描(全表扫) 大表上出现
SSEK2 二级索引范围扫描 正常
BLKUP2 回表 回表量过大时考虑覆盖索引
NEST LOOP 嵌套循环 内层若全扫,代价爆炸
HASH JOIN 哈希连接 内存消耗大

判断口诀:代价估算(方括号第一个数)与实际行数差 10 倍以上 → 统计信息过期,先收集统计信息再优化。

-- 收集统计信息(成本最低的优化手段,优先做) dbms_stats.gather_table_stats('模式名','表名',null,100,TRUE,'FOR ALL COLUMNS SIZE AUTO');

3.3 定位 TOP SQL

-- 按执行耗时 SELECT TOP 10 SQL_ID, SQL_TXT, EXEC_TIME, LOGIC_READ_CNT, PHY_READ_CNT, IO_WAIT_TIME, TAB_SCAN_CNT, HASH_JOIN_CNT FROM V$SQL_STAT ORDER BY EXEC_TIME DESC; -- 按物理读(揪出全表扫描大户) SELECT TOP 10 SQL_ID, SQL_TXT, PHY_READ_CNT, LOGIC_READ_CNT FROM V$SQL_STAT ORDER BY PHY_READ_CNT DESC;

⚠️ 列名是 LOGIC_READ_CNT / PHY_READ_CNT


四、热点三:操作系统资源瓶颈

4.1 判定标准(实测基线对照)

本次采集的健康基线(8 核 / 8 GB / 空闲状态):

CPU: 8 核           load average: 0.05 0.07 0.05
内存: total 7963MB  used 1578MB  available 2978MB
磁盘: sda %util=0.60  await=1.07ms  r/s=2.00  w/s=12.00
资源 健康 预警 危险 判定命令
CPU 负载 < 核数×0.7 接近核数 > 核数×2 uptime / top
%wa(IO 等待) < 5% 10~20% > 30% top / vmstat 1
内存 swap used = 0 swap 少量波动 swap 持续增长 free -m / vmstat 1
磁盘 %util < 60% 60~80% > 90% iostat -x 1
磁盘 await < 10 ms 10~30 ms > 50 ms iostat -x 1

4.2 三板斧命令

top # ① us高=计算 sy高=系统调用 wa高=IO瓶颈 vmstat 1 5 # ② si/so 非零 = 已在换页(数据库场景基本等于事故) iostat -x 1 5 # ③ %util 接近100 = 磁盘饱和

4.3 典型组合判读

组合 根因 处置方向
%wa 高 + %util 高 磁盘瓶颈 查 TOP SQL 物理读;归档/备份是否撞峰;存储是否降级
%us 高 + 逻辑读高 SQL 劣化 索引/统计信息/SQL 改写
swap 持续增长 内存超配 调小 BUFFER,给 OS 预留 20%+ 内存
load 高但 CPU 闲 阻塞堆积 回到第二节查锁

物理内存充足时可适当上调(建议物理内存 40%~60%),但必须给 OS 留足 20%,
否则触发 swap 反而更慢。


五、热点四:归档与 redo(易被忽略的挂起源)

5.1 归档满导致全库挂起

现象:业务突然全部卡住,写入报归档相关错误。

-- 查看归档模式与配置 SELECT NAME, ARCH_MODE, STATUS$, ROLE$ FROM V$DATABASE; SELECT ARCH_NAME, ARCH_TYPE, ARCH_DEST FROM V$DM_ARCH_INI;
# 归档目录磁盘占用 df -h df -h /dm8/arch du -sh /dm8/arch

处置:确认归档已备份后清理过期归档,切勿直接 rm。

# 删除30天以前的归档日志(达梦数据库一般会通过设置归档总容量限制,磁盘紧急情况可以手动删除旧归档恢复系统) SF_ARCHIVELOG_DELETE_BEFORE_TIME (SYSDATE-30);

六、一页纸速查表

① 卡顿时第一跑:活跃会话

create or REPLACE PROCEDURE sysdba.gs as begin select s.sess_id "会话ID", ora_hash(sql_text) "sqlHash码", case when w.id is not null then '被阻塞,源sess='||(select cast(min(sess_id) as varchar(20)) from v$sessions ss where ss.trx_id=w.wait_for_id) when w2.wait_for_id is not null then '★阻塞源(杀我)' else '' end "阻塞", case when datediff(ss, s.last_recv_time, sysdate) between 0 and 8640000 then datediff(ss, s.last_recv_time, sysdate) else 0 end "执行秒", s.state "状态", 'sp_close_session('||cast(s.sess_id as varchar(20))||');' "关闭语句", s.trx_id "事务ID", (select t.ins_cnt||'/'||t.del_cnt||'/'||t.upd_cnt from v$trx t where t.id=s.trx_id) "增/删/改", (select decode(t.rollback_flag,1,'回滚中','') from v$trx t where t.id=s.trx_id) "回滚", s.user_name "用户", s.clnt_ip "客户端IP", s.appname "程序", round((select sum(m.total_size)/1024.0/1024 from v$mem_pool m where m.creator=s.thrd_id),1) "内存MB", dbms_lob.substr(sf_get_session_sql(s.sess_id),4000,1) "SQL" from v$sessions s left join v$trxwait w on s.trx_id = w.id left join v$trxwait w2 on s.trx_id = w2.wait_for_id where s.state = 'ACTIVE' or s.trx_id in (select id from v$trxwait) or s.trx_id in (select wait_for_id from v$trxwait) order by 4 desc; end; / create or replace public synonym gs for sysdba.gs;
-- ② 有结果=锁阻塞 SELECT ID, WAIT_FOR_ID, WAIT_TIME FROM V$TRXWAIT; -- ③ 找阻塞源头 SELECT SESS_ID, SQL_TEXT, CLNT_IP, APPNAME FROM V$SESSIONS WHERE TRX_ID IN (SELECT WAIT_FOR_ID FROM V$TRXWAIT); -- ④ 无锁则查 TOP SQL(本节三) SELECT TOP 10 SQL_ID, SQL_TXT, EXEC_TIME, LOGIC_READ_CNT, PHY_READ_CNT FROM V$SQL_STAT ORDER BY EXEC_TIME DESC; -- ⑤ 缓冲命中率(低于 90% 需关注) SELECT ID, NAME, ROUND(RAT_HIT*100,2) AS HIT_PCT FROM V$BUFFERPOOL WHERE N_LOGIC_READS > 0; -- ⑥ 紧急断会话 SP_CLOSE_SESSION(<sess_id>);

达梦生态社区 https://eco.dameng.com ;

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服