注册
数据库性能优化诊断
培训园地/ 文章详情 /

数据库性能优化诊断

harrison辉 2025/12/24 308 1 0

在系统运行过程中,数据库性能问题是引发系统响应缓慢、服务不稳定的常见原因之一。从运维排障的角度出发,围绕数据库性能问题的典型现象,系统介绍性能瓶颈的定位思路、分析方法以及对应的处理与优化措施,为快速排查和解决问题提供参考。
一、性能问题的定位
性能问题的定位需要收集大量的系统信息,包括硬件资源使用情况、数据库软件配置、SQL 执行状况等。

  1. 硬件信息收集
    硬件系统资源使用情况可以通过 nmon、OSW(OSWatcher)等操作系统监控工具 进行全面观测,能够快速定位系统层面的异常问题,例如网络丢包、内存持续增长(潜在内存泄漏)、进程或线程数异常暴增等,这类问题往往会直接或间接影响数据库性能。
    nmon 监控数据采集配置
通过操作系统作业调度功能配置数据库服务器性能监控程序,将对应CPU平台的nmon文件上传到对应目录,需要将文件名称修改为nmon。
[root@~]# mkdir /home/dmdba/dm/nmon_log
[root@~]# crontab -e
添加如下内容, 路径以实际部署环境为准
0 0 * * * /home/dmdba/dm/nmon -s20 -c4320 -fT -m /home/dmdba/dm/nmon_log > /dev/null 2>&1
0 0 * * * find /home/dmdba/dm/nmon_log  -type f -mtime +30 -exec rm -f {} \;
-s20:采样间隔 20 秒
-c4320:采样次数,共约 24 小时
-fT:以时间戳命名文件
-m:指定 nmon 文件输出目录

  1. 数据库软件信息收集
    了解数据库的运行环境对于诊断性能问题至关重要。需要收集的关键信息包括:
    数据库版本与架构配置(如单机、HA、主备、DSC、MPP 等),数据库的交易类型(OLTP、OLAP),会话数、事务数、热点表、热点 SQL 等信息。
    达梦数据库同样提供了类似 Oracle AWR(Automatic Workload Repository) 的性能数据采集,能够帮助运维人员从时间维度和负载维度分析性能变化情况,快速判断性能瓶颈是来源于系统资源、数据库内部机制,还是具体业务 SQL。
---开启awr:
SQL> sp_init_awr_sys(1);

---检查数据库DBMS_WORKLOAD_REPOSITORY系统包的启用状态(0:未启用;1:已启用)以及awr快照信息和表空间信息
select sf_check_awr_sys;
select * from sys.wrm$_snapshot;
select tablespace_name from dba_tablespaces;

--设置快照生成的时间间隔:默认为60,单位为分钟。关闭快照间隔值设定0即可
SQL> DBMS_WORKLOAD_REPOSITORY.AWR_SET_INTERVAL(30);  ###30分钟生成一次

---修改快照生成间隔为2小时,保留6天。单位分钟,2小时=120分钟,6天=8640分钟
DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS(8640,60);

select snap_id,end_interval_time from SYS.WRM$_SNAPSHOT where end_interval_time between to_date('2025-09-04 00:00:00','yyyy-mm-dd hh24:mi:ss') and to_date('2025-09-04 23:59:00','yyyy-mm-dd hh24:mi:ss');

需要收集2025年09月04日10:00--11:00的awr报告,并将生成的文件存放在/home/dmdba/dm/文件夹下,生成的文件命名为1675_1676.html,方法如下:
SQL>  CALL SYS.AWR_REPORT_HTML(1675,1676,'/home/dmdba/dm','1675_1676.html');

---清理快照id为1-2(包含快照号为1和2的快照)之间的快照:
DBMS_WORKLOAD_REPOSITORY.DROP_SNAPSHOT_RANGE(1,2);

---删除“2025-12-19 16:10:29.488000”时间点之前创建的所有 snapshot,并立即释放相应分区表空间。 
CALL DBMS_WORKLOAD_REPOSITORY.DROP_SNAPSHOT_BY_TIME('2025-12-19 16:10:29.488000',TRUE);

---清理所有的历史快照信息
SQL> call dbms_workload_repository.awr_clear_history();

从上述去看,因为清理的方式非常有可能与Oracle类似逐行删除,如果对于大型的数据库来说,其实简单的办法可以是暂时关闭AWR功能,直接删除表空间的形式,更快速清理空间的占用。
--删除AWR功能
SP_INIT_AWR_SYS(0);
删除后sysaux表空间不在存在。

3.用户信息收集
与数据库的使用者沟通,了解是否有新业务上线、硬件升级、用户增加等变动,以确认是否是环境变化导致的性能问题。

4.慢SQL和阻塞监控
在达梦数据库中,可通过动态性能视图与SQL 日志机制两种方式获取慢 SQL 信息,分别适用于实时/近期分析和历史追溯分析场景。
(1)当前会话慢sql

---已执行超过2秒的活动 SQL
select  *  from
        (
                SELECT
                        user_name                               ,
                        clnt_ip                                 ,
                        sess_id                                 ,
                        sql_text                                ,
                        datediff(ss, last_send_time, sysdate) ss,
                        SF_GET_SESSION_SQL(SESS_ID) fullsql     
                FROM
                        V$SESSIONS
                WHERE
                        STATE='ACTIVE' and user_name!='SYSDBA'
        )
where ss>=2 order by 5 desc;   

(2)锁超时查询阻塞会话

SELECT
        DS.SESS_ID "被阻塞的会话ID",
        DS.SQL_TEXT "被阻塞的SQL",
        DS.TRX_ID "被阻塞的事务ID",
        (CASE L.LTYPE WHEN 'OBJECT' THEN '对象锁' WHEN 'TID' THEN '事务锁' END CASE ) "被阻塞的锁类型",
        DS.CREATE_TIME "开始阻塞时间",
        SS.SESS_ID "占用锁的会话ID",
        SS.SQL_TEXT "占用锁的SQL",
        SS.CLNT_IP "占用锁的IP",
        L.TID "占用锁的事务ID"
FROM
        V$LOCK L
LEFT JOIN V$SESSIONS DS
ON
        DS.TRX_ID = L.TRX_ID
LEFT JOIN V$SESSIONS SS
ON
        SS.TRX_ID = L.TID
WHERE
        L.BLOCKED = 1

(3)查询历史慢SQL

可以通过查询动态视图 V$LONG_EXEC_SQLS 或 V$SYSTEM_LONG_EXEC_SQLS 来获取慢SQL语句。需开启SQL监控(ENABLE_MONITOR=1、MONITOR_TIME=1,默认开启)
---统计历史慢SQL执行情况
select sql_text,
         COUNT(0) 执行次数,
         max(EXEC_TIME) 最大执行时间,
         TRUNC(avg(exec_time),2) 平均执行时间
    from v$long_exec_sqls a
group by sql_text
order by COUNT(0) desc,
         max(EXEC_TIME);

---查询系统启动后执行最长的20条SQL语句
set echo off
set feedback off
set timing off
set verify off
set lineshow off
SELECT
        SESS_ID    ,
        EXEC_TIME  ,
        FINISH_TIME,
        N_RUNS     ,
        TRX_ID     ,
        substr(sql_text,0,20)
FROM
        V$SYSTEM_LONG_EXEC_SQLS
ORDER BY
        EXEC_TIME DESC,
        N_RUNS DESC;

也可通过开启 SQL 日志(sqllog)进行采集,跟踪日志开启后存放于数据目录中,通常以dmsql_实例名.log命名,当存在多分日志文件时会在文件名后追加具体时间信息(数据库运行日志在数据库初始化完成后是存放在数据库安装目录log下,非数据目录)

--设置 SQL 过滤规则,只记录必要的 SQL,生产环境不要设成1
2 只记录 DML 语句    
3 只记录 DDL 语句   
22 记录绑定参数的语句
25 记录 SQL 语句和它的执行时间    
28 记录 SQL 语句绑定的参数信息
SELECT SF_SET_SYSTEM_PARA_VALUE('SQL_TRACE_MASK','2:3:22:25:28',0,1);

--同步日志会严重影响系统效率,生产环境必须设置为异步日志
SELECT SF_SET_SYSTEM_PARA_VALUE('SVR_LOG_ASYNC_FLUSH',1,0,1);

--下面这个语句设置只记录执行时间超过 200ms 的语句
SELECT SF_SET_SYSTEM_PARA_VALUE('SVR_LOG_MIN_EXEC_TIME',200,0,1);

--下面的语句查看设置是否生效
SELECT * FROM V$DM_INI where para_name='SVR_LOG_ASYNC_FLUSH';
SELECT * FROM V$DM_INI where para_name='SQL_TRACE_MASK';
SELECT * FROM V$DM_INI where para_name='SVR_LOG_MIN_EXEC_TIME';

--开启 SQL 日志:
SP_SET_PARA_VALUE(1, 'SVR_LOG', 1);

--关闭 SQL 日志:
SP_SET_PARA_VALUE(1, 'SVR_LOG', 0);

二、常见的性能问题典型场景
2.1、CPU 性能问题排查
当数据库主机出现CPU使用率异常升高时,可根据CPU使用类型(us/sys)的不同,采取不同的排查思路。
1、CPU us% 占比高(用户态消耗高)
(1)现象描述
通过top命令查看数据库服务进程的 CPU 使用情况,发现 us%(用户态)占比明显偏高

top -p <数据库服务进程PID>

该场景通常意味着:数据库正在大量执行 SQL 计算、SQL 执行效率低,消耗大量 CPU 资源、应用侧请求并发过高或存在异常 SQL
当发现 dmserver 进程 CPU 使用率异常升高时,可进一步定位到具体消耗 CPU 的线程

top -H -p `ps -ef |grep dmserver|grep -v grep|awk '{print $2}'`
select * from v$sessions where thrd_id =’线程号’;

(2)应急处理措施
若发现大量低效 SQL:
与应用方确认是否可进行 限流
评估是否可终止异常会话
终止异常会话

SP_CLOSE_SESSION(<SESS_ID>);

2、CPU sys% 占比高(内核态消耗高)
(1)现象描述
在 top 中观察到sys% 明显偏高,甚至出现 sys% 高于 us% 的情况,该场景通常意味着:系统调用频繁、线程调度、锁竞争、IO 调用或内核资源争用严重。
当服务器操作系统已安装并支持 perf 工具,通过 perf top 命令,可实时观察系统与进程的热点函数,用于定位 CPU 主要消耗位置,将输出结果(尤其是热点函数)截图保存,反馈给 DBA 或达梦数据库工程师进行分析。
(2)结合 perf record/perf report 进行深度分析

当 perf top 无法明确问题原因时,可进行短时间采样分析。
1. 采样系统调用栈(建议 ≤ 30 秒)
perf record -F 99 -ag -- sleep 30
说明:
-F 99:每秒采样 99 次
-a:采集所有 CPU
-g:记录调用栈
注意:不要长时间运行,避免对系统造成额外负载

2. 分析采样结果
perf report -n -T -U --children --max-stack=15
重点关注:
调用栈顶部函数
dmserver → 内核函数的调用路径
CPU 消耗是否集中在某一调用链

2.2、IO性能问题排查
在数据库性能问题中,IO 瓶颈是最常见也是最容易被误判的问题之一。通过 iostat 工具可以快速判断磁盘是否存在 IO 拥堵、响应时间过长或负载异常的情况。

iostat -dmx 2 10
参数说明:
-d:仅显示磁盘设备信息
-m:以 MB 为单位显示吞吐量
-x:显示扩展 IO 指标(重点)
2 :采样间隔 2 秒
10:采样次数 10 次

---关键指标
%util(磁盘繁忙度):表示磁盘在采样周期内的忙碌时间比例

await(IO 平均响应时间,毫秒):表示一次IO 请求从提交到完成的平均耗时,此值> 20 ms代表IO 性能明显下降,若SSD 环境下await持续>10ms,基本可判定异常。

r/s、w/s、rkB/s、wkB/s:反映读写 IO 的请求次数与吞吐量,需关注是否存在 突发性 IO 洪峰,是否与业务高峰时间一致

avgqu-sz(IO 队列长度):表示等待处理的 IO 请求数量,如持续>1:说明磁盘来不及处理 IO,持续增长往往伴随 await 升高

r_await / w_await:分别表示读 / 写 IO 的平均响应时间,判断是读慢还是写慢,结合业务类型(OLTP / OLAP)分析

在实际排查过程中,常见现象包括磁盘Busy长时间接近100%,IO 响应时间显著升高。需要注意的是,这种情况往往并非业务变慢的根本原因,而更可能是问题的“结果”。其背后原因通常包括:

  • 存在 执行效率不高的 SQL 语句,产生大量无效或重复的磁盘 IO;
  • 单条或多条 SQL 导致 IOPS 超过磁盘或存储阵列的承载能力;
  • 无效的顺序/随机IO占用了大量磁盘带宽,引发 IO 队列堆积和整体 IO 拥堵。
    因此,当出现磁盘繁忙、IO 响应时间过长时,不能仅停留在硬件层面扩容或更换存储,而应进一步结合数据库层面的 SQL 执行计划、逻辑读/物理读情况以及业务访问模式进行综合分析,从源头上定位和解决性能瓶颈,避免“治标不治本”。

2.3、内存问题排查
1、内存泄露排查
内存泄漏是指数据库进程分配的内存未被正确释放,长期积累会导致 内存占用异常升高,甚至触发 OOM(Out of Memory)。
(1)通过系统视图 V$BUFFERPOOL和V$MEM_POOL 可获得数据库分配的内存总量,与TOP命令统计到的数据库进程的内存res值对比。若二者相差不大,则内存无泄漏,若差距较大,可能存在内存泄漏。

SELECT 
       (SELECT SUM(CAST(n_pages AS BIGINT) * page_size)/1024/1024 FROM v$bufferpool) AS BUFFER_SIZE_MB,
       (SELECT SUM(CAST(total_size AS BIGINT))/1024/1024 FROM v$mem_pool) AS MEM_POOL_MB,
       (SELECT SUM(CAST(n_pages AS BIGINT) * page_size)/1024/1024 FROM v$bufferpool) +
       (SELECT SUM(CAST(total_size AS BIGINT))/1024/1024 FROM v$mem_pool) AS TOTAL_SIZE_MB
FROM dual;

(2)达梦数据库提供内存池泄漏检测功能,可动态开启:

SP_SET_PARA_VALUE(1, 'MEMORY_LEAK_CHECK', 1);
通过系统视图 V$MEM_REGINFO 可以查看内存分配扩展次数最多的文件和行号,用于根因分析。
SELECT
       SUM(reserved_size)/1024/1024 AS RESERVED_SIZE_MB,
       fno,
       lineno,
       fname,
       SUM(refnum) AS REFNUM
FROM
       v$mem_reginfo
GROUP BY
       fno,lineno,fname
ORDER BY
       REFNUM DESC;

REFNUM 高且内存占用持续增长的条目进行优先分析,可结合 SQL/模块调用栈排查具体内存泄漏点

2、定位使用内存较多会话和执行sql
如果在运行期间知道内存一直在上涨,通过v$mem_pool和v$sessions视图,定位可能导致OOM的sql,查询语句如下:

---单个会话内存使用总量
SELECT
        A.CREATOR                          ,
        B.SQL_TEXT                         ,
        SUM(A.TOTAL_SIZE)/1024.0/1024.0 TOTAL_M,    --当前总量(包括扩展)
        SUM(A.DATA_SIZE) /1024.0/1024.0 DATA_SIZE_M  --实际使用量
FROM
        V$MEM_POOL A,
        V$SESSIONS B
WHERE
        A.CREATOR = B.THRD_ID
GROUP BY
        A.CREATOR,
        B.SQL_TEXT
ORDER BY
        TOTAL_M DESC;

---使用内存较多的会话执行SQL
with cte as(
        select
                regexp_replace(name,'[0-9]'),count(*),
                trunc(sum((org_size    /1024.0/1024))) 初始,
                trunc(sum((data_size   /1024.0/1024))) 在用,
                trunc(sum((total_size  /1024.0/1024))) 总的,
                trunc(sum((target_size /1024.0/1024))) 水位,
                max(creator) as thrd_id 
                
        from
                v$mem_pool
        group by
                regexp_replace(name,'[0-9]')
                )
select *,(select sql_text from v$sessions where thrd_id=a.thrd_id) as sql_text
from cte a
order by 总的 desc;

2.4、慢SQL优化排查
当 CPU 异常、IO 瓶颈或内存异常定位到某条 SQL 时,须对该 SQL 进行深入分析,达梦数据库提供了 ET(Execution Trace)机制,ET 是达梦内置的系统存储过程工具,能够对 SQL 执行过程进行精细化统计,按操作符维度记录每个执行步骤的时间消耗和执行情况。通过 ET 分析,可以准确定位 SQL 执行过程中耗时较高或效率较低的操作环节,例如全表扫描、索引访问、排序、连接等,从而识别真正的性能瓶颈。基于 ET 的分析结果,可以有针对性地对 SQL 语句、索引结构或表设计进行优化,避免盲目调优。

达梦ET默认未启用,设置启动以下三个参数可以启用ET(ENABLE_MONITOR、MONITOR_TIME和MONITOR_SQL_EXEC);

其中,ENABLE_MONITOR和MONITOR_TIME默认已开启,如果未开启可以使用如下方法开启:
SP_SET_PARA_VALUE(1,'ENABLE_MONITOR',1);
SP_SET_PARA_VALUE(1,'MONITOR_TIME',1);

MONITOR_SQL_EXEC为会话级动态参数,可以设置只针对当前会话开启:
SF_SET_SESSION_PARA_VALUE('MONITOR_SQL_EXEC',1);
执行SQL语句,我们会看到一个执行号,直接点这个执行号,即可调用ET;
在知道执行号的情况下,CALL ET(124571);

对于较长的 SQL,可以采用 SELECT、FROM、WHERE 三段拆分方法进行分析:在 SELECT 与 FROM 之间避免标量子查询或自定义函数以防重复扫描表;在 FROM 与 WHERE 之间重点关注大表、子查询或视图的执行效率,并检查是否支持谓词下推或视图合并,同时注意内连接与外连接的驱动顺序;在 WHERE 后面重点分析子查询可否展开(UNNEST)以及过滤列是否使用函数,以确保索引有效。
在排查慢 SQL 或性能瓶颈时,常用优化手段包括索引优化、统计信息维护、Hint 使用以及注入执行计划(inject hint)。
(1)索引优化
合理的索引设计能够显著降低 SQL 执行成本、提升查询响应效率。在索引优化过程中,需要结合实际业务访问模式进行综合分析,重点关注 查询条件、关联方式及排序方式,并评估索引的 选择性与覆盖能力。同时,还需充分考虑索引带来的维护成本,避免因索引数量过多或设计不合理,增加数据写入开销和系统资源消耗,从而在查询性能提升与系统整体负载之间取得平衡
(2)收集统计信息
数据库统计信息是优化器生成执行计划的重要依据,其准确性直接影响 SQL 的执行效率。通过定期收集和维护统计信息,尤其是对热点表、频繁变更数据表及核心业务表的统计信息进行重点维护,可以有效避免因统计信息失真导致的执行计划偏差,确保优化器能够选择更合理的访问路径和执行策略,从而提升整体数据库性能与稳定性。

--全库级别
CALL SP_DB_STAT_INIT (); --对库上所有表及索引 生成统计信息
--收集shema统计信息
CALL DBMS_STATS.GATHER_SCHEMA_STATS('大写的用户名',100,TRUE,'FOR ALL COLUMNS SIZE AUTO');
--收集表的统计信息
CALL DBMS_STATS.GATHER_TABLE_STATS('大写的用户名', '大写的表名',NULL,100,TRUE,'FOR ALL COLUMNS SIZE AUTO');
--收集索引的统计信息
DBMS_STATS.GATHER_INDEX_STATS('username','IDX_T2_X');

(3)增加hints
在 SQL 性能排查中,通过执行计划可以发现以下问题:部分表有索引,但 SQL 实际走了全表扫描,条件中含有 OR、子查询或复杂表达式时,优化器选择的执行计划不理想,此时可以通过 Hint 强制优化器控制索引、Join 类型、执行顺序,从而提高 SQL 执行效,需结合统计信息和 SQL 三段拆分分析:确保 Hint 改变的执行计划是合理且可行的。

---常见hints
/*+INDEX(TABLENAME, IDX_NAME)*/--强制使用索引;
/*+ENABLE_HASH_JOIN(0)*/--禁用hash join关联;
/*+OPTIMIZER_OR_NBEXP(2)*/--条件中含有or时可以使用;
--/*+ENABLE_RQ_TO_NONREF_SPL(3)*/--执行计划中有SPL时使用。
0:不启用该优化;
1:对查询项中出现的相关子查询表达式进行优化处理;
2:对查询项和 WHERE 表达式中出现的相关子查询表达式进行优化处理;
4:相关查询采用 SPL 方式去相关性后,可以作为单表过滤条件
支持使用上述有效值的组合值,如 3 表示同时进行 1 和 2 的优化
 /*+ use_htab(0)*/  禁止使用临时表
/*+HJ_BUF_SIZE(1000000)*/ 消耗在hash连接上。通过添加hint进行性能优化
/*+ORDER(con,this,con2,cli,mem)*/  修改表的访问顺序
/*+ hintSTAT(TT_MEMBER,100000) */   指定表的统计信息
/*+ ENABLE_INDEX_JOIN(0) */   不走索引
/*+ OP_SUBQ_CVT_IN_FLAG(0) NONREFED_SUBQUERY_AS_CONST(1)*/  子查询不走索引

(4)inject hint绑定执行计划
实际生产中,部分慢SQL执行频率较高、业务短期内没有升级需求或者其他特殊情况,无法及时处理,可以通过模糊绑定hint方式,使sql强制走hint的执行计划。

使用SF_INJECT_HINT需要开启DM.INI的ENABLE_INJECT_HINT 需设置为 1,否则无法使用,这个需要关注。
--模糊绑定部分SQL:
sf_inject_hint(
sql_text => 'select A.H1,A.H2,B.H1,B.H2 from HASH_TEST1 A,HASH_TEST2 B where A.H1=B.H1;',
hint_text =>'USE_NL(A,B)',
name =>'TESTSQL',
description => 'this is a USE_NL hint',
 validate => true);
 
 ---模糊匹配
 sf_inject_hint(
 sql_text => 'from HASH_TEST1 A,HASH_TEST2 B where A.H1=B.H1;',
 hint_text =>'USE_NL(A,B)',
 name =>'TESTSQL',
 description => 'this is a USE_NL hint',
 validate => true,
 fuzzy => true);

参数说明:
第一个参数:SQL语句这个我们数据库有性能的语句,这个语句贴到函数中,请勿格式化,也请勿前后带有空格,否则不匹配;
第二个参数:ENABLE_INDEX_JOIN(0),这里填写需要新增的hint,直接填写合适的hint即可,无需加/*+ */这类标识符,通常我们写在SQL中才需要这样加;
第三个参数:HINT_1207_1这个是只是一个NAME名称;
第四个参数:可以理解成一种注解;
第五个参数:是否理解生效,我们一般救急肯定就设置TRUE了
第六个参数:匹配规则为精准匹配或模糊匹配,设置NULL和TRUE是模糊匹配,否则FALSE 或缺省时精确匹配,如果SQL明确建议精确匹配;

--查询当前系统中的inject hint
select        name                     ,        dbms_lob.substr(sql_text) sql_text,        dbms_lob.substr(hint_text) hint_text,  CRTDATEfrom        sysinjecthintwhere'查询的sql' like CONCAT('%',sql_text,'%')order by        crtdate;

--删除不需要的inject hint
sf_deinject_hint('TESTSQL')

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服