注册
性能故障问题与解决
专栏/技术分享/ 文章详情 /

性能故障问题与解决

🌊Amaharashi 2026/09/23 232 0 0
摘要

1 概述

数据库问题的排查应遵循"由硬到软,自底向上"的思路:硬件资源是软件正常运行的基础,网络抖动、存储 IO 异常都可能造成不可预估的异常;绝大多数问题源于不正确的使用(不合理的规划、低效 SQL、索引使用不当),但必须先排除硬件与服务层,再进入数据库内部。
排查层次(自底向上):

  1. 硬件层:CPU / 内存 / 磁盘 IO / 网络 —— 用 top、vmstat、free 确认资源瓶颈
  2. 服务层:dmserver 进程状态、守护进程(dmwatcher)、监视器(dmmonitor)、归档/redo 日志
  3. 数据库层:会话堆积、锁阻塞、慢 SQL、执行计划、统计信息。
    现象 优先排查方向 排查入口
    无法建立新会话/连接缓慢 连接数满、监听异常 vsessions、dmserver状态 CPU飙升、系统负载高 高并发或低效SQL top → vmstat → vsessions
    页面加载慢、查询慢 慢SQL 、锁阻塞 慢SQL定位→执行计划
    写入慢/事务卡住 锁等待、归档阻塞 vlock 、vtrxwait
    集群应用部分不可用 守护通信异常、双主 dmmonitor全局视图、守护/实例日志

2 系统资源诊断

2.1 TOP查看系统进程

TOP命令有什么作用:
top命令是Linux下实时查看系统进程状态的常用工具,是排查系统资源问题的首要工具。它以动态刷新的方式展示当前系统整体负载、CPU使用率、内存使用情况,以及各个进程的资源占用明细。
在数据库性能排查中,用它确认数据库服务进程的CPU占用率与系统负载,作为故障现象的第一手观察。执行top后,需要重点关注进程行的%CPU列以及顶部的load average。负载值超过CPU核数说明系统繁忙、有任务在排队。
top命令查看系统进程状态:
image.png
若发现dmserver进程的CPU利用率很高,系统负载偏高。而CPU占用高有两种可能,要么是业务正常高并发,要么是存在异常消耗CPU的操作,需要进一步明确CPU花费在何处,接下来进一步分析。

2.2 CPU细化分析

vmstat 1 5
即每1秒刷新一次,一共刷新5次,执行后观察CPU与运行队列的细节。
vmstat是Virtual Memory Statistics的缩写,即虚拟内存统计。可以用于对操作系统的虚拟内存、进程、IO读写、CPU活动等进行监视,是top命令的细化补充。与top命令输出结果不同的是,top看到的是"整体快照",而vmstat则是进一步回答"CPU到底花在哪些地方",通过细化分析,判断瓶颈来源。
image.png

2.3 内存检查

free -h
image.png

3 数据库状态诊断

3.1 活动会话确认

因为资源正常但CPU高,所以合理怀疑是数据库会话堆积导致的。因此,先查询活动会话数:
select count(*) 活动会话 from v$sessions where state=‘ACTIVE’;
正常时的活动会话数应该为个位数。
会话堆积有两种可能,一种是在等待锁释放,另一种纯粹是SQL执行缓慢导致的。于是的分析思路很明确,即查看是不是发生了锁阻塞。

3.2 锁阻塞排查

(1)快速判断是否存在锁等待
select O.NAME 表名, L.* from VLOCK L, SYSOBJECTS O where L.TABLE_ID=O.ID and BLOCKED=1; 若结果显示:未选定行,即当前不存在任何锁等待,可以排除锁阻塞因素,那么结合前面分析的资源正常,可以推断真正的瓶颈为SQL执行效率低下,也就是大量会话反复执行同一条低效SQL,消耗CPU。 (2)查询阻塞链路 在这个场景中,可以看到是没有阻塞的,但这条命令只能用来判断"有没有阻塞",而无法回答"谁阻塞了谁",所以如果在别的场景下,发现存在锁阻塞,则不能获得更多的消息去解决这个锁阻塞。 当发现有锁阻塞时,要进一步排查: SELECT ID, WAIT_FOR_ID, WAIT_TIME FROM VTRXWAIT;

4 慢SQL定位

执行聚合查询,按SQL文本分组,统计活动会话中各条SQL的执行频次:
SELECT SQL_ID, START_TIME,
(TIME_USED/1000) AS TIME_MS,
SUBSTR(TOP_SQL_TEXT,1,70) AS SQL_TEXT
FROM VSQL_HISTORY WHERE TOP_SQL_TEXT LIKE '%ORDERS_TP%' ORDER BY TIME_USED DESC;; 根据输出结果,哪个SQL的会话数多、卡的时间长,再核对VLOCK中阻塞锁指向哪张表,两者交叉验证锁定被阻塞的 SQL。定位到具体SQL后,接下来需要分析它的执行计划,并根据执行计划找出这条SQL执行效率低下的原因。
image.png

5 执行计划分析

对刚刚找到的那条慢SQL执行explain,查看执行计划:
EXPLAIN SELECT * FROM (
SELECT inner_query.*, ROWNUM rnum
FROM (SELECT o_id, o_totalprice FROM SYSDBA.orders_tp
WHERE o_custkey = 1 ORDER BY o_id DESC) inner_query
WHERE ROWNUM <= 5
) WHERE rnum > 0;;

从图中可以看到执行计划关键节点:
第 9 行 #CSCN2: [37, 300000, 54] → CSCN = Cluster Scan(全表扫描),300000 就是它准备把整张表 30 万行从头到尾扫一遍
优化器只能把 30 万行全部读出来,再一行行过滤找那7500行。

6 优化方案

6.1 已有索引的检查

在建立索引前,首先检查表上是否已存在索引,以及现有索引结构是否合理:
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION, DESCEND
FROM ALL_IND_COLUMNS WHERE TABLE_OWNER = ‘SYSDBA’ AND TABLE_NAME = ‘ORDERS_TP’ ORDER BY INDEX_NAME, COLUMN_POSITION;
image.png
o_custkey上没有二级索引,优化器无索引可用 → 只能 CSCN2 全表扫 30 万行

6.2 数据分析

无法随意认定建立索引就可以比走全表扫描更快,因为当数据的分布不均匀时,走索引反而会要回表更多次,带来的代价更大。这一点需要进行选择性分析和倾斜性分析后才知道到底需要选择哪一种方案。

6.2.1 选择性分析

先看当前统计信息的真实状态
SELECT TABLE_NAME, NUM_ROWS, LAST_ANALYZED
FROM ALL_TAB_STATISTICS
WHERE TABLE_OWNER = ‘SYSDBA’ AND TABLE_NAME = ‘ORDERS_TP’;
image.png
查不到行,解释了为什么优化器7500。
先看过滤列的选择性,即平均每个值对应多少行来知道大概的情况,如果每个值对应的行数相对而言很多,那就直接选择走全表扫描,否则会需要回表很多次,代价很大。
SELECT COUNT(DISTINCT o_custkey) AS NDV,
COUNT(*) AS TOTAL_ROWS
FROM SYSDBA.ORDERS_TP;
image.png
优化器在执行计划生成时,依据选择率估算 WHERE 条件过滤后的命中行数:预估行数 = 总行数 × 选择率。在列值均匀分布的假设下,理想选择率 ≈ 1/NDV(NDV 为列去重值个数)。本例 ORDERS_TP 表无统计信息时,优化器按默认经验值(约 2.5%)预估命中 7500 行;而实测该列 NDV=5000,理想选择率 1/5000=0.02%,实际命中仅 60 行,预估偏差约 125 倍,导致优化器错选全表扫描(CSCN2)而非索引扫描。

6.2.2 倾斜分析

根据以上分析,还需要进一步检查数据分布是否存在倾斜,即是否存在少数大客户独占大部分数据的现象,另取 FAULT_LOG 表为例:
SELECT LOG_TYPE, COUNT(*) AS CNT
FROM FAULT.FAULT_LOG
GROUP BY LOG_TYPE
ORDER BY CNT DESC;
image.png
选择率估算建立在值分布均匀的假设之上;当列值分布严重不均时(如 FAULT_LOG 表 LOG_TYPE 列按 85%/10%/5% 分布),单一 NDV 无法刻画真实分布,均匀假设失效,优化器仍会给出失真的行数预估。此类场景需通过收集直方图统计信息,记录高频值分布,使优化器按实际分布校正选择率。

6.3 建立索引

根据慢 SQL 的 WHERE 条件列(O_CUSTKEY)与 ORDER BY 排序列(O_ID),依据最左匹配原则设计复合索引,并纳入查询所需列(O_TOTALPRICE)作为覆盖列,避免回表。
CREATE INDEX SYSDBA.IDX_TP_CUSTKEY ON SYSDBA.ORDERS_TP(O_CUSTKEY, O_ID, O_TOTALPRICE);
更新统计信息,防止新建索引后,优化器还是根据旧的统计信息生成的执行计划:
CALL DBMS_STATS.GATHER_TABLE_STATS(‘SYSDBA’,‘ORDERS_TP’,NULL,100);

7 优化效果验证

7.1 再次查看执行计划

EXPLAIN SELECT * FROM (
SELECT inner_query.*, ROWNUM rnum
FROM (SELECT o_id, o_totalprice FROM SYSDBA.orders_tp
WHERE o_custkey = 1 ORDER BY o_id DESC) inner_query
WHERE ROWNUM <= 5
) WHERE rnum > 0;
image.png
可以看到,扫描方式由建索引前的全表扫描(CSCN2)变为索引扫描(SSEK2),scan_range[(1,min,min),(1,max,max)) 已从全索引范围缩小到目标客户(O_CUSTKEY=1)的精确范围扫描,即先过滤再选择;同时通过索引降序扫描(scan_type(DESC))直接满足 ORDER BY O_ID DESC 排序要求,配合 TOPN2 取前 5 行即停,原先的全表扫描与排序均已消除,优化生效。

7.2 执行时间对比

建完组合索引后,再次查询,结果如下:

执行计划由全表扫描转为索引精确扫描后,SQL 返回耗时大幅下降,瓶颈消除

7.3 会话恢复验证

select count(*) 活动会话 from v$sessions where state=‘ACTIVE’;
建立索引后优化效果明显,活动会话数降至个位数,会话疏通,系统性能也随之恢复。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服