近期遇到生产数据库机器响应特别慢的场景。特别翻了达梦的文档。
官方说的内容太多了,做点小总结,快速定位问题。
官网说的一些工具可能比较好用,但是需要联系官方才能搞到。可参考:https://eco.dameng.com/document/dm/zh-cn/ops/tool-monitor.html
官方链接参考
安装前对数据库有对应规划
https://eco.dameng.com/document/dm/zh-cn/ops/before-installation.html# 比如磁盘性能:参考:http://linux.zanglikun.com/c/iostat.html
性能诊断
https://eco.dameng.com/document/dm/zh-cn/ops/performance-diagnosis.html
性能优化
https://eco.dameng.com/document/dm/zh-cn/ops/performance-optimization.html
SQL执行计划分析
https://www.zanglikun.com/21222.html
错误码清单
https://eco.dameng.com/document/dm/zh-cn/faq/faq-errorcode.html
正文
先获取硬件信息
- 硬件信息收集项。
| 硬件 | 硬件配置(命令) | 运行情况(命令) |
|---|---|---|
| CPU | cat /proc/cpuinfo | top\vmstat\sar |
| 内存 | cat /proc/meminfo | top\vmstat\free |
| 磁盘 | RAID | iostat |
| 网络 | ifconfig | ping\route\netstat |
不同Linux操作系统,可能命令不一定都齐全。无所谓,上述命令能看多少看多少。
看下磁盘压力 iostat -xmd
参考:iostat参数讲解:http://linux.zanglikun.com/c/iostat.html
[root@VM-4-14-centos ~]# iostat -xmd
Linux 3.10.0-1160.81.1.el7.x86_64 (VM-4-14-centos) 2025年04月07日 _x86_64_ (4 CPU)
Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.03 14.74 2.58 16.61 0.06 0.36 45.39 0.05 2.69 3.94 2.50 0.90 1.74
scd0 0.00 0.00 0.00 0.00 0.00 0.00 7.20 0.00 0.46 0.46 0.00 0.46 0.00
获取达梦基本信息
查看达梦版本信息
SELECT * FROM V$VERSION LIMIT 100;
输出
DM Database Server 64 V8
8.1
安全版
DB Version: 0x7000c
0XXXXXXXXX-20240712-234979-20093
Msg Version: 0
Gsu level(3-4) cnt: 3
Gsu level(5) cnt: 0
其他重要的排查信息
会话数: SELECT COUNT(*) FROM V$SESSIONS LIMIT 10;
事务数: SELECT COUNT(*) FROM V$TRX LIMIT 10;
等待事件:SELECT * FROM V$TRXWAIT LIMIT 100;
内存池: SELECT * FROM V$MEM_POOL LIMIT 100;
v$licens 优先排查 MAX_CPU_NUM
DATE_GEN:数据库安装时间
EXPIRED_DATE:证书过期时间
MAX_CPU_NUM:可使用最大核心数 如果是NULL 则代表不限制。(付费版会限制CPU数量,官网版本是不限制是NULL)
-- 关注MAX_CPU_NUM。这个可能会出现“一核有难,7核观战”的情况
SELECT * FROM v$license LIMIT 100;
检查大小写敏感
MySQL 的体系架构是单实例多库的。达梦的体系架构是单库多实例的,也就是没有多个数据库的概念了。https://eco.dameng.com/docs/zh-cn/article/article-010.html
注意达梦数据库的大小写敏感 是数据库实例的配置。一旦实例创建后,无法修改。只能说重新配置数据库实例!
SELECT SF_GET_CASE_SENSITIVE_FLAG();
--1 为大小写敏感,0 为大小写不敏感
解决方式:将数据库字段、表名称改为大写
下面只是一种解决方案,但是因为数据库权限问题,可能容易改到其他模式的表。但因为模式都是大写的话,倒也影响不大。这里也没涉及视图哦。这里只是简单记录一种方式。
改表名为大写
-- 使用前,记得查找一下、容易多查询出一些表。
SELECT table_name FROM user_tables WHERE table_name = LOWER(table_name);
-- 查询后,可执行如下,就会替换了。
BEGIN
FOR rec IN (
SELECT table_name
FROM user_tables
WHERE table_name = LOWER(table_name)
) LOOP
EXECUTE IMMEDIATE 'ALTER TABLE "' || rec.table_name || '" RENAME TO ' || UPPER(rec.table_name);
DBMS_OUTPUT.PUT_LINE('已修改表: ' || rec.table_name || ' -> ' || UPPER(rec.table_name));
END LOOP;
END;
/
改字段为大写
-- 创建批量更新字段名的存储过程
CREATE OR REPLACE PROCEDURE batch_rename_columns_to_upper
AS
v_sql VARCHAR2(1000);
v_success_count INT := 0;
v_error_count INT := 0;
v_error_msg VARCHAR2(4000);
BEGIN
DBMS_OUTPUT.PUT_LINE('开始批量更新字段名为大写...');
FOR rec IN (
SELECT
table_name,
column_name
FROM user_tab_columns
WHERE column_name = LOWER(column_name)
AND table_name NOT LIKE 'BIN$%' -- 排除回收站中的表
ORDER BY table_name, column_id
) LOOP
BEGIN
-- 生成修改SQL
v_sql := 'ALTER TABLE "' || rec.table_name || '" RENAME COLUMN "' || rec.column_name || '" TO ' || UPPER(rec.column_name);
-- 执行修改
EXECUTE IMMEDIATE v_sql;
-- 记录成功
v_success_count := v_success_count + 1;
DBMS_OUTPUT.PUT_LINE('成功: ' || rec.table_name || '.' || rec.column_name || ' -> ' || UPPER(rec.column_name));
EXCEPTION
WHEN OTHERS THEN
v_error_count := v_error_count + 1;
v_error_msg := SQLERRM;
DBMS_OUTPUT.PUT_LINE('失败: ' || rec.table_name || '.' || rec.column_name || ' - ' || v_error_msg);
END;
END LOOP;
-- 输出统计信息
DBMS_OUTPUT.PUT_LINE('批量更新完成!');
DBMS_OUTPUT.PUT_LINE('成功: ' || v_success_count || ' 个字段');
DBMS_OUTPUT.PUT_LINE('失败: ' || v_error_count || ' 个字段');
END;
/
-- 执行存储过程
SET SERVEROUTPUT ON;
CALL batch_rename_columns_to_upper();
-- 或者直接执行
BEGIN
batch_rename_columns_to_upper();
END;
/
查看达梦内存分配状态
dual 方式一
-- 总内存由 “系统缓冲区大小”与“共享内存池大小”共同组成。两者任意过大,都对整体性能影响很大。建议均衡配置。
-- BUFFER_SIZE:系统缓冲区大小,以 M 为单位。推荐值:系统缓冲区大小为可用物理内存的 60%~80%。有效值范围(8~1048576)。
-- MEM_POOL:共享内存池大小,以 M 为单位。共享内存池是由 DM 管理的内存。有效值范围:32 位平台为(642000),64 位平台为(6467108864)。
-- TOTAL_SIZE:BUFFER_SIZE 和 MEM_POOL 的总和。
SELECT
(SELECT SUM(n_pages) * PAGE()/1024/1024 FROM v$bufferpool)||'MB' AS 系统缓冲区大小,
(SELECT SUM(total_size)/1024/1024 FROM v$mem_pool)||'MB' AS 共享内存池大小,
(SELECT SUM(n_pages) * PAGE()/1024/1024 FROM v$bufferpool)+(SELECT SUM(total_size)/1024/1024 FROM v$mem_pool)||'MB' AS 总内存大小TOTAL_SIZE
FROM DUAL LIMIT 10;
v$sysstat 方式二:
SELECT name, stat_val/1024.0/1024.0 FROM v$sysstat where CLASSID=11 LIMIT 10;
查询结果中,字段含义如下:
- memory pool size in bytes:内存池总的大小。
- memory used bytes:内存池使用的内存大小。
- memory used bytes from os:内存池从操作系统分配的大小。
查看表大小
SELECT USR.NAME AS USER_NAME
,SCH.NAME AS SCHEMA_NAME
,TAB.NAME AS TABLE_NAME
,TABLE_ROWCOUNT(SCH.NAME,TAB.NAME) AS NUM_ROWS
,ROUND(TABLE_USED_PAGES(SCH.NAME,TAB.NAME) * PAGE / 1024.0 / 1024.0,2) AS TABLE_SIZE_MB
,ROUND(TABLE_USED_LOB_PAGES(SCH.NAME,TAB.NAME) * PAGE / 1024.0 / 1024.0,2) AS LOB_SIZE_MB
,IDX.CLUSTER_SIZE_MB AS DATA_SIZE_MB
,IDX.INDEX_SIZE_MB
,IDX.USED_TABLESPACE
FROM SYSOBJECTS TAB
JOIN SYSOBJECTS SCH ON SCH.TYPE$ = 'SCH' AND SCH.ID = TAB.SCHID
JOIN SYSOBJECTS USR ON USR.TYPE$ = 'UR' AND USR.ID = SCH.PID
JOIN (SELECT IDX.PID AS TABLE_ID
,SUM(NVL(DECODE(BITAND(IDXINFO.XTYPE,0x01),0,ROUND(INDEX_USED_PAGES(IDX.ID) * PAGE / 1024.0 / 1024.0,2)),0)) AS CLUSTER_SIZE_MB
,SUM(NVL(DECODE(BITAND(IDXINFO.XTYPE,0x01),1,ROUND(INDEX_USED_PAGES(IDX.ID) * PAGE / 1024.0 / 1024.0,2)),0)) AS INDEX_SIZE_MB
,LISTAGG(DISTINCT TS.NAME,',') WITHIN GROUP (ORDER BY TS.ID) AS USED_TABLESPACE FROM SYSOBJECTS IDX
JOIN SYSINDEXES IDXINFO ON IDXINFO.ID = IDX.ID
JOIN V$TABLESPACE TS ON TS.ID = IDXINFO.GROUPID
WHERE IDX.TYPE$ = 'TABOBJ'
AND IDX.SUBTYPE$ = 'INDEX'
GROUP BY IDX.PID
) IDX ON IDX.TABLE_ID = TAB.ID
WHERE TAB.TYPE$ = 'SCHOBJ'
AND TAB.SUBTYPE$ IN ('STAB','UTAB')
AND SCH.NAME = '模式名称'
--AND TAB.NAME = '表名'
ORDER BY TABLE_SIZE_MB DESC,TAB.NAME

正在执行的SQL
查看正在执行的SQL
-- 查询当前连接的信息。
SELECT
-- SYSDATE AS "当前时间,非字段",
USER_NAME AS "连接的账号",
STATE AS "状态",
SF_GET_SESSION_SQL(SESS_ID) AS "完整SQL",
SESS_ID AS "会话 ID,会话唯一标识",
SESS_SEQ ,
--SQL_TEXT ,
SEQ_NO ,
TRX_ID AS "事务号",
CREATE_TIME ,
CLNT_IP AS "客户端IP"
-- CLNT_HOST AS "客户端主机名",
-- APPNAME AS "客户端名称",
-- OSNAME AS "客户端操作系统"
FROM
v$sessions
WHERE
STATE IN ('ACTIVE', 'WAIT')
AND SF_GET_SESSION_SQL(SESS_ID) LIKE '%你的表名%'
LIMIT 100;
-- STATE有:ACTIVE=活跃的、WAIT=等待的、IDLE=闲置的。因为各种应用都会配备数据库连接池,就会有很多IDLE的连接。所以排查问题就不查询IDLE的状态了。
统计不同账号连接的数量
-- 统计不同账号连接的数量
SELECT
STATE AS "状态",
USER_NAME AS "连接的账号",
COUNT(1) AS "数量"
FROM
v$sessions
GROUP BY
STATE,USER_NAME
LIMIT 100
;
慢SQL排查
-- 慢日志记录信息
SELECT
SESS_ID AS "会话 ID,会话唯一标识",
-- SQL_ID AS "语句 ID,语句唯一标识",
SQL_TEXT AS SQL文本,
EXEC_TIME AS "执行时间(毫秒)",
FINISH_TIME AS "执行结束时间",
N_RUNS AS "执行次数",
SEQNO AS "编号",
TRX_ID AS "事务号"
FROM
V$LONG_EXEC_SQLS
LIMIT 100;
查看缓冲池命中率
SELECT
NAME AS "缓冲池名称",
SUM(PAGE_SIZE) * SF_GET_PAGE_SIZE / 1024 / 1024 AS "缓冲池大小_MB",
SUM(RAT_HIT) / COUNT(*) AS "命中率"
FROM
v$bufferpool
GROUP BY NAME LIMIT 100;
- KEEP: 适合存储频繁访问的数据,命中率为1,表明非常有效。
- RECYCLE: 适合不太频繁访问的数据,命中率较高但不如 KEEP。
- FAST: 设计用于快速访问,命中率几乎为1,表现优秀。
- NORMAL: 用于常规数据访问,命中率较高,但可以进一步优化。
- ROLL: 用于存储回滚信息,命中率为1,确保事务的完整性。
内存分配
- KEEP: 用于频繁访问的数据,建议设置较小但高命中率。
- NORMAL: 用于常规访问的数据,适当增大以提高命中率。
- RECYCLE: 用于不太频繁访问的数据,保持中等大小。
注意一个事项:缓冲池不是真实物理内存占用。
- 缓冲池是数据库系统用来提高性能的内存区域,存储在物理内存中的数据页。
- 它是一个逻辑概念,表示数据库如何使用物理内存,而不是物理内存本身。
- 优化缓冲池的大小和管理策略,可以显著提高数据库的性能和响应速度。
输出:
KEEP 1024 1
RECYCLE 3072 0.9513388117336025
FAST 1024 0.9999996457697699
NORMAL 9216 0.9777671570939879
ROLL 1024 1
查看死锁 🏮🏮🏮🏮🏮🏮🏮🏮🏮🏮🏮🏮🏮🏮🏮🏮
其实这里你应该关注的事:占用锁的会话ID,而不是被阻塞的会话ID
注意,这里使用的是:L.BLOCKED = 1。有的场景,只是任务没执行完成,终端关闭了,但是锁没释放掉。需要手动修改SQL移除这个条件。
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
LIMIT 100;
杀死 Session
事务是无法主动暂停释放的,只能通过杀死Session的方式来进行!
比如你在查看死锁的进程的时候,你会发现一个阻塞会话Id,与 占用锁的会话ID。你需要做的是:杀死 占用锁的会话ID。
SP_CLOSE_SESSION ( 占用锁的会话 ID );
在控制台输入这个函数 回车即可。注意:这个函数,需要超管才执行。
执行结果如下:
SQL> SP_CLOSE_SESSION (139557186045736);
DMSQL 过程已成功完成
已用时间: 0.895(毫秒). 执行号:58290406.
查询历史死锁
SELECT
DH.TRX_ID AS "事务ID",
SH.SESS_ID AS "SESSION_ID",
WM_CONCAT(TOP_SQL_TEXT) AS "SQL"
FROM
V$DEADLOCK_HISTORY DH,
V$SQL_HISTORY SH
WHERE
DH.TRX_ID =SH.TRX_ID
AND DH.SESS_ID=SH.SESS_ID
GROUP BY
DH.TRX_ID, SH.SESS_ID
LIMIT 100;
查看当前活跃的会话消耗的内存
-- 查看当前活跃的会话消耗的内存
SELECT
B.CURR_SCH AS "当前模式",
B.USER_NAME AS "操作账号",
B.SESS_ID AS "SESSION_ID",
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
AND B.STATE = 'ACTIVE'
GROUP BY
A.CREATOR,
B.SQL_TEXT
ORDER BY
TOTAL_M DESC
LIMIT 1000;
统计信息(提升很有效)
统计信息概述
统计信息主要是描述数据库中表和索引的大小数以及数据分布状况等的一类信息。比如:表的行数、块数、平均每行的大小、索引的高度、叶子节点数以及索引字段的行数等。
统计信息对于 CBO(基于代价的优化器)生成执行计划具有直接影响。例如在嵌套循环连接(链接)中需要选择小表作为驱动表,两个关联表哪个是小表完全取决于统计信息中记录的数据量信息。此外,访问一个表是否要走索引,关联查询能否采用其它关联方式等都是 CBO 基于统计信息确定的。因此,统计信息的准确是生成最优执行计划的必要前提。
收集统计信息
DM 收集统计信息的方法分为手动收集和自动收集。
⭐️⭐️⭐️为什么需要收集统计信息?⭐️⭐️⭐️
- 查询优化器依赖:数据库查询优化器需要统计信息来选择最优的执行计划
- 性能提升:准确的统计信息可以显著提高查询性能
- 索引利用:帮助优化器决定是否使用索引以及如何使用索引
- 数据分布:了解数据的分布情况,做出更好的查询决策
⭐️⭐️⭐️使用建议⭐️⭐️⭐️
- 定期执行:在数据量发生显著变化后重新收集
- 按需收集:可以根据业务需要选择收集范围(用户级、表级、列级)
- 监控性能:收集统计信息本身也会消耗资源,需要在合适的时间执行
手动收集
先明确一个概念:采样率
采样就是从整个数据集中选取一部分数据来分析,而不是扫描全部数据。
1. 性能考虑
全表扫描大表会消耗大量时间和资源
通过采样可以在合理时间内获得足够准确的统计信息
减少I/O操作,降低系统负载
2. 精度平衡
采样比例越高,统计信息越准确,但耗时越长
采样比例越低,速度越快,但可能影响统计信息质量
-- 1.收集指定用户下所有表所有列的统计信息:
DBMS_STATS.GATHER_SCHEMA_STATS('username',100,TRUE,'FOR ALL COLUMNS SIZE AUTO');
作用:收集指定用户(schema)下所有表的统计信息
'username':目标用户名
100:采样百分比(100表示全表扫描)
TRUE:是否收集直方图统计信息
'FOR ALL COLUMNS SIZE AUTO':为所有列自动收集统计信息,包括直方图
用途:用于全面优化整个用户下所有表的查询性能
-- 2.收集指定用户下所有索引的统计信息:
DBMS_STATS.GATHER_SCHEMA_STATS('usename',1.0,TRUE,'FOR ALL INDEXED SIZE AUTO');
作用:收集指定用户下所有索引的统计信息
'usename':用户名(注意这里可能是拼写错误,应该是'username')
1.0:采样比例(1.0表示100%采样)
TRUE:收集直方图信息
'FOR ALL INDEXED SIZE AUTO':仅为有索引的列收集统计信息
用途:专门优化索引相关的查询性能
-- 3.收集单个索引统计信息:
DBMS_STATS.GATHER_INDEX_STATS('username','IDX_T2_X');
作用:针对特定索引收集统计信息
'username':索引所属用户
'IDX_T2_X':具体的索引名称
用途:当某个特定索引的查询性能有问题时,单独更新其统计信息
-- 4.收集指定用户下某表统计信息:
DBMS_STATS.GATHER_TABLE_STATS('username','table_name',null,100,TRUE,'FOR ALL COLUMNS SIZE AUTO');
作用:收集指定表的统计信息
'username':表所属用户
'table_name':具体表名
null:分区名(null表示所有分区)
100:采样百分比
TRUE:收集直方图
'FOR ALL COLUMNS SIZE AUTO':为表的所有列收集统计信息
用途:当特定表的查询性能需要优化时使用
-- 5.收集某表某列的统计信息:
STAT 100 ON table_name(column_name);
作用:
为特定表的特定列收集统计信息
100:采样数量或百分比
table_name:表名
column_name:列名
用途:针对性地优化某个列上的查询条件
查看统计信息
--用于经过 GATHER_TABLE_STATS、GATHER_INDEX_STATS 或 GATHER_SCHEMA_STATS 收集之后展示。
dbms_stats.table_stats_show('模式名','表名');
--用于经过 GATHER_TABLE_STATS、GATHER_INDEX_STATS 或 GATHER_SCHEMA_STATS 收集之后展示。 返回两个结果集:一个是索引的统计信息;另一个是直方图的统计信息。
dbms_stats.index_stats_show('模式名','索引名');
--用于经过 GATHER_TABLE_STATS、GATHER_INDEX_STATS 或 GATHER_SCHEMA_STATS 收集之后展示。
dbms_stats.COLUMN_STATS_SHOW('模式名','表名','列名');
注意
统计信息收集过程中将对数据库性能造成一定影响,避免在业务高峰期收集统计信息。
自动收集
DM 数据库支持统计信息的自动收集,当全表数据量变化超过设定阈值后可自动更新统计信息。
--打开表数据量监控开关,参数值为 1 时监控所有表,2 时仅监控配置表
SP_SET_PARA_VALUE(1,'AUTO_STAT_OBJ',2);
--设置 SYSDBA.T 表数据变化率超过 15% 时触发自动更新统计信息
DBMS_STATS.SET_TABLE_PREFS('SYSDBA','T','STALE_PERCENT',15);
--配置自动收集统计信息触发时机
SP_CREATE_AUTO_STAT_TRIGGER(1, 1, 1, 1,'14:36', '2020/3/31',60,1);
/*
函数各参数介绍
SP_CREATE_AUTO_STAT_TRIGGER(
TYPE INT, --间隔类型,默认为天
FREQ_INTERVAL INT, --间隔频率,默认 1
FREQ_SUB_INTERVAL INT, --间隔频率,与 FREQ_INTERVAL 配合使用
FREQ_MINUTE_INTERVAL INT, --间隔分钟,默认为 1440
STARTTIME VARCHAR(128), --开始时间,默认为 22:00
DURING_START_DATE VARCHAR(128), --重复执行的起始时间,默认 1900/1/1
MAX_RUN_DURATION INT, --允许的最长执行时间(秒),默认不限制
ENABLE INT --0 关闭,1 启用 --默认为 1
);
*/
更新统计信息
--更新已有统计信息
DBMS_STATS.UPDATE_ALL_STATS();
删除统计信息
--表DBMS_STATS.DELETE_TABLE_STATS('模式名','表名','分区名',...);
--模式
DBMS_STATS.DELETE_SCHMA_STATS('模式名','','',...);
--索引
DBMS_STATS.DELETE_INDEX_STATS('模式名','索引名','分区表名',...);
--字段
DBMS_STATS.DELETE_COLUMN_STATS('模式名','表名','列名','分区表名',...);
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取最新全部资料 ❤
免责声明: 本站文章旨在总结学习互联网技术过程中的经验与见解。任何人不得将其用于违法或违规活动!所有违规内容均由个人自行承担,与作者无关。
