分享卡顿问题排查步骤
先确认卡顿现属于什么类型:某个功能点转圈,其他功能正常;所有操作都慢,且持续;报表/统计类操作慢,交易正常;突然全库卡死,写入报磁盘错;每隔一段时间规律性卡一阵
| 现象 | 大概率归属 | 直接跳到 |
|---|---|---|
| 某个功能点转圈,其他功能正常 | 锁阻塞(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;
构造场景:会话 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、加索引,方向完全错误。
第一斧:看等待链(谁在等谁)
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),
或某个批量任务中途异常退出留下悬挂事务。
| 手段 | 命令 | 适用场景 |
|---|---|---|
| 通知提交(首选) | 联系应用方提交/回滚 | 能定位到责任人 |
| 强制断开会话 | SP_CLOSE_SESSION(<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';
-- 会话级:等待 N 秒后返回超时错误而非死等
ALTER SESSION SET DDL_WAIT_TIME = 10;
V$TRXWAIT 纳入监控告警(等待超过 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、挤占缓冲池,拖慢整库。
| 操作符 | 含义 | 看到就要警惕 |
|---|---|---|
CSCN2 |
聚集索引全扫描(全表扫) | 大表上出现 |
SSEK2 |
二级索引范围扫描 | 正常 |
BLKUP2 |
回表 | 回表量过大时考虑覆盖索引 |
NEST LOOP |
嵌套循环 | 内层若全扫,代价爆炸 |
HASH JOIN |
哈希连接 | 内存消耗大 |
判断口诀:代价估算(方括号第一个数)与实际行数差 10 倍以上 → 统计信息过期,先收集统计信息再优化。
-- 收集统计信息(成本最低的优化手段,优先做)
dbms_stats.gather_table_stats('模式名','表名',null,100,TRUE,'FOR ALL COLUMNS SIZE AUTO');
-- 按执行耗时
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
本次采集的健康基线(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 |
top # ① us高=计算 sy高=系统调用 wa高=IO瓶颈
vmstat 1 5 # ② si/so 非零 = 已在换页(数据库场景基本等于事故)
iostat -x 1 5 # ③ %util 接近100 = 磁盘饱和
| 组合 | 根因 | 处置方向 |
|---|---|---|
%wa 高 + %util 高 |
磁盘瓶颈 | 查 TOP SQL 物理读;归档/备份是否撞峰;存储是否降级 |
%us 高 + 逻辑读高 |
SQL 劣化 | 索引/统计信息/SQL 改写 |
| swap 持续增长 | 内存超配 | 调小 BUFFER,给 OS 预留 20%+ 内存 |
| load 高但 CPU 闲 | 阻塞堆积 | 回到第二节查锁 |
物理内存充足时可适当上调(建议物理内存 40%~60%),但必须给 OS 留足 20%,
否则触发 swap 反而更慢。
现象:业务突然全部卡住,写入报归档相关错误。
-- 查看归档模式与配置
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 ;
文章
阅读量
获赞
