数据库问题的排查应遵循"由硬到软,自底向上"的思路:硬件资源是软件正常运行的基础,网络抖动、存储 IO 异常都可能造成不可预估的异常;绝大多数问题源于不正确的使用(不合理的规划、低效 SQL、索引使用不当),但必须先排除硬件与服务层,再进入数据库内部。
排查层次(自底向上):
TOP命令有什么作用:
top命令是Linux下实时查看系统进程状态的常用工具,是排查系统资源问题的首要工具。它以动态刷新的方式展示当前系统整体负载、CPU使用率、内存使用情况,以及各个进程的资源占用明细。
在数据库性能排查中,用它确认数据库服务进程的CPU占用率与系统负载,作为故障现象的第一手观察。执行top后,需要重点关注进程行的%CPU列以及顶部的load average。负载值超过CPU核数说明系统繁忙、有任务在排队。
top命令查看系统进程状态:
若发现dmserver进程的CPU利用率很高,系统负载偏高。而CPU占用高有两种可能,要么是业务正常高并发,要么是存在异常消耗CPU的操作,需要进一步明确CPU花费在何处,接下来进一步分析。
vmstat 1 5
即每1秒刷新一次,一共刷新5次,执行后观察CPU与运行队列的细节。
vmstat是Virtual Memory Statistics的缩写,即虚拟内存统计。可以用于对操作系统的虚拟内存、进程、IO读写、CPU活动等进行监视,是top命令的细化补充。与top命令输出结果不同的是,top看到的是"整体快照",而vmstat则是进一步回答"CPU到底花在哪些地方",通过细化分析,判断瓶颈来源。
free -h
因为资源正常但CPU高,所以合理怀疑是数据库会话堆积导致的。因此,先查询活动会话数:
select count(*) 活动会话 from v$sessions where state=‘ACTIVE’;
正常时的活动会话数应该为个位数。
会话堆积有两种可能,一种是在等待锁释放,另一种纯粹是SQL执行缓慢导致的。于是的分析思路很明确,即查看是不是发生了锁阻塞。
(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;
执行聚合查询,按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执行效率低下的原因。
对刚刚找到的那条慢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行。
在建立索引前,首先检查表上是否已存在索引,以及现有索引结构是否合理:
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;
o_custkey上没有二级索引,优化器无索引可用 → 只能 CSCN2 全表扫 30 万行
无法随意认定建立索引就可以比走全表扫描更快,因为当数据的分布不均匀时,走索引反而会要回表更多次,带来的代价更大。这一点需要进行选择性分析和倾斜性分析后才知道到底需要选择哪一种方案。
先看当前统计信息的真实状态
SELECT TABLE_NAME, NUM_ROWS, LAST_ANALYZED
FROM ALL_TAB_STATISTICS
WHERE TABLE_OWNER = ‘SYSDBA’ AND TABLE_NAME = ‘ORDERS_TP’;
查不到行,解释了为什么优化器7500。
先看过滤列的选择性,即平均每个值对应多少行来知道大概的情况,如果每个值对应的行数相对而言很多,那就直接选择走全表扫描,否则会需要回表很多次,代价很大。
SELECT COUNT(DISTINCT o_custkey) AS NDV,
COUNT(*) AS TOTAL_ROWS
FROM SYSDBA.ORDERS_TP;
优化器在执行计划生成时,依据选择率估算 WHERE 条件过滤后的命中行数:预估行数 = 总行数 × 选择率。在列值均匀分布的假设下,理想选择率 ≈ 1/NDV(NDV 为列去重值个数)。本例 ORDERS_TP 表无统计信息时,优化器按默认经验值(约 2.5%)预估命中 7500 行;而实测该列 NDV=5000,理想选择率 1/5000=0.02%,实际命中仅 60 行,预估偏差约 125 倍,导致优化器错选全表扫描(CSCN2)而非索引扫描。
根据以上分析,还需要进一步检查数据分布是否存在倾斜,即是否存在少数大客户独占大部分数据的现象,另取 FAULT_LOG 表为例:
SELECT LOG_TYPE, COUNT(*) AS CNT
FROM FAULT.FAULT_LOG
GROUP BY LOG_TYPE
ORDER BY CNT DESC;
选择率估算建立在值分布均匀的假设之上;当列值分布严重不均时(如 FAULT_LOG 表 LOG_TYPE 列按 85%/10%/5% 分布),单一 NDV 无法刻画真实分布,均匀假设失效,优化器仍会给出失真的行数预估。此类场景需通过收集直方图统计信息,记录高频值分布,使优化器按实际分布校正选择率。
根据慢 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);
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;
可以看到,扫描方式由建索引前的全表扫描(CSCN2)变为索引扫描(SSEK2),scan_range[(1,min,min),(1,max,max)) 已从全索引范围缩小到目标客户(O_CUSTKEY=1)的精确范围扫描,即先过滤再选择;同时通过索引降序扫描(scan_type(DESC))直接满足 ORDER BY O_ID DESC 排序要求,配合 TOPN2 取前 5 行即停,原先的全表扫描与排序均已消除,优化生效。
建完组合索引后,再次查询,结果如下:
执行计划由全表扫描转为索引精确扫描后,SQL 返回耗时大幅下降,瓶颈消除
select count(*) 活动会话 from v$sessions where state=‘ACTIVE’;
建立索引后优化效果明显,活动会话数降至个位数,会话疏通,系统性能也随之恢复。
文章
阅读量
获赞
