🚨 Oracle 11g 数据库卡顿排查与实战优化:一次真实的慢 SQL 定位全过程
背景
生产环境 Oracle 11g 数据库突然"很卡",应用接口响应慢、CPU 飙高、用户大量超时。
本文完整记录一次 从现象 → 定位 → 分析 → 处置 → 优化方案 的全过程,适合 DBA / 后端开发 / 运维参考。
一、问题现象
- 应用大量接口响应慢
- 数据库 CPU 使用率持续 90%+
- 会话数正常,但 ACTIVE 会话长期不释放
- Oracle 11g,无 AWR 授权(只能靠动态视图)
二、第一步:查看当前正在执行的慢 SQL(实时)
1️⃣ 查看执行超过 10 秒的 SQL
vbnet
SELECT
s.sid,
s.serial#,
s.username,
s.status,
s.program,
sq.sql_id,
SUBSTR(sq.sql_text, 1, 100) AS sql_text,
s.last_call_et AS 已执行秒数,
s.event AS 等待事件,
s.wait_time,
s.seconds_in_wait
FROM v$session s
JOIN v$sql sq ON s.sql_id = sq.sql_id
WHERE s.status = 'ACTIVE'
AND s.username NOT IN ('SYS', 'SYSTEM')
AND s.last_call_et > 10
ORDER BY s.last_call_et DESC;
🔍 执行结果解读
-
多个会话执行时间 几十秒 ~ 几分钟
-
明显看到:
Table Scan: GSTPGG.CEN_CHECK_MEMBERIndex Fast Full Scan: GSTPGG.RISK_CLUE_BASIC_INFO
➡️ 已经可以初步判断:存在严重的全表扫描问题
三、第二步:查看"已执行完但仍在内存中"的慢 SQL
2️⃣ 从 v$sql 查历史慢 SQL
vbnet
SELECT
sql_id,
SUBSTR(sql_text, 1, 100) AS sql_text,
executions AS 执行次数,
ROUND(elapsed_time/1000000, 2) AS 总耗时_秒,
ROUND(cpu_time/1000000, 2) AS CPU时间_秒,
disk_reads AS 磁盘读取,
buffer_gets AS 缓冲区获取,
rows_processed AS 处理行数,
last_load_time AS 最后加载时间
FROM v$sql
WHERE elapsed_time/1000000 > 5
AND parsing_schema_name = 'GSTPGG'
ORDER BY elapsed_time DESC
FETCH FIRST 20 ROWS ONLY;
📊 关键发现
| 类型 | 对象 | 扫描方式 |
|---|---|---|
| 多条 SQL | CEN_CHECK_MEMBER | Table Scan(全表扫描) |
| 少量 SQL | RISK_CLUE_BASIC_INFO | Index Fast Full Scan |
➡️ 性能瓶颈明确:CEN_CHECK_MEMBER 表
四、第三步:定位正在执行的"长时间扫描任务"
3️⃣ 使用 v$session_longops 查看进度
vbnet
SELECT
l.sid,
l.serial#,
s.sql_id,
s.sql_text,
l.opname,
l.target,
l.sofar,
l.totalwork,
ROUND(l.sofar/l.totalwork*100, 2) AS pct_complete,
l.elapsed_seconds,
l.time_remaining
FROM v$session_longops l
JOIN v$session se ON l.sid = se.sid AND l.serial# = se.serial#
LEFT JOIN v$sql s ON se.sql_id = s.sql_id
WHERE l.opname LIKE '%Scan%'
AND l.username = 'GSTPGG'
AND l.time_remaining > 0
ORDER BY l.start_time;
🚨 结果非常危险
- 5 个会话同时
- 对 同一张表
CEN_CHECK_MEMBER - 进行 并发全表扫描
- 每个会话扫描进度仅 8% ~ 28%
➡️ 这会导致:
- Buffer Cache 被疯狂挤占
- 其他 SQL 全部变慢
- 数据库"看起来像死了一样"
五、第四步:分析问题表本身(核心)
4️⃣ 表基本信息
ini
SELECT
owner,
table_name,
num_rows,
blocks,
avg_row_len,
last_analyzed
FROM dba_tables
WHERE owner = 'GSTPGG'
AND table_name = 'CEN_CHECK_MEMBER';
📊 表分析结果
| 指标 | 数值 |
|---|---|
| 行数 | 20,496,308 行 |
| 数据块 | 440,297 blocks |
| 平均行长 | 149 字节 |
| 最近分析时间 | 3 天前 |
⏱️ 理论扫描耗时估算
bash
440,297 blocks × 8KB ≈ 3.44GB
3.44GB ÷ 200MB/s ≈ 17 秒(理想)
⚠️ 现实情况:
- 多会话并发
- Buffer Cache 争抢
- CPU 调度
➡️ 实际耗时:几十秒 ~ 数分钟
六、第五步:精准锁定"罪魁祸首 SQL"
5️⃣ 找出执行全表扫描的 SQL
ini
SELECT
s.sid,
s.serial#,
s.sql_id,
SUBSTR(q.sql_text, 1, 200) AS sql_text,
s.program,
s.last_call_et
FROM v$session s
JOIN v$sql q ON s.sql_id = q.sql_id
WHERE s.sql_id IN (
SELECT sql_id
FROM v$sql_plan
WHERE object_name = 'CEN_CHECK_MEMBER'
AND operation = 'TABLE ACCESS'
AND options = 'FULL'
);
🔎 发现多个会话在反复执行同一条 SQL
七、第六步:打印完整 SQL(关键)
6️⃣ 使用 v$sqltext 还原完整 SQL
sql
SELECT LISTAGG(sql_text, '')
WITHIN GROUP (ORDER BY piece) AS full_sql_text
FROM v$sqltext
WHERE sql_id = '9jdbd64yqbwcf';
✅ 完整 SQL 如下:
sql
SELECT ...
FROM CEN_CHECK_MEMBER
WHERE (card_id LIKE :1 AND IS_DELETE = :2)
八、问题本质终于浮出水面
🚨 核心性能问题总结
❌ 1. LIKE 条件导致索引失效
card_id LIKE '%xxx%'- B-Tree 索引无法使用
- Oracle 只能选择 全表扫描
❌ 2. 表太大 + 高频调用
- 单表 2000 万行
- SQL 执行 1599 次
- 总逻辑读 6.67 亿次
- 处理数据 2.85 亿行
❌ 3. 并发执行雪上加霜
- 多个 JDBC 会话同时扫描
- Buffer Cache 被"刷爆"
九、应急处理:立即止血
7️⃣ 生成批量 Kill 会话脚本
ini
SELECT
'ALTER SYSTEM KILL SESSION ''' || s.sid || ',' || s.serial# || ''' IMMEDIATE;' AS kill_cmd
FROM v$session s
WHERE s.sql_id = '9jdbd64yqbwcf'
AND s.username = 'GSTPGG'
AND s.status = 'ACTIVE'
AND s.last_call_et > 10;
⚠️ 仅用于应急,务必与业务确认
十、根本解决方案(重点)
✅ 方案一:创建正确索引
ini
CREATE INDEX IDX_CEN_CHECK_MEMBER_CARD_DEL
ON GSTPGG.CEN_CHECK_MEMBER(card_id, IS_DELETE)
NOLOGGING PARALLEL 8;
索引生效前提
| LIKE 写法 | 是否走索引 |
|---|---|
card_id LIKE '123%' |
✅ 是 |
card_id LIKE '%123%' |
❌ 否 |
✅ 方案二:业务 SQL 改造建议
sql
-- 原(危险)
WHERE card_id LIKE '%123%'
-- 优化(可控)
WHERE card_id LIKE '123%'
✅ 方案三:分页 + 列裁剪
sql
SELECT mem_number, mem_name, card_id
FROM CEN_CHECK_MEMBER
WHERE card_id LIKE :1
AND IS_DELETE = 0
AND ROWNUM <= 50;
十一、经验总结(血的教训)
Oracle 慢 SQL 排查三板斧:
- v$session:看现在谁在跑
- v <math xmlns="http://www.w3.org/1998/Math/MathML"> s q l / v sql / v </math>sql/vsql_plan:看谁最耗资源
- 执行计划 + 表规模:判断是不是"不该扫却在扫"
🎯 最终结论
数据库不是"突然变慢"的,而是某条 SQL 在特定条件下被放大了 1000 倍。
- 一次
%LIKE% - 一张 2000 万行表
- 几个并发会话
➡️ 足以拖垮整个 Oracle 实例