在达梦数据库运维和优化中,慢 SQL 是绕不开的话题。这一周我完整走了一遍慢 SQL 分析流程:从 DMLOG 收集慢 SQL,到看懂执行计划,再到更新统计信息、使用 hint 干预执行路径。本文把这套流程整理成可复用的经验,分享给同样在做达梦优化的朋友。
实例级 SQL 日志分析工具,DMLOG 工具是一款简单易用的达梦数据库 SQL 日志分析工具,通过分析数据库的 SQL 日志文件(达梦数据库可通过开启 SVR_LOG 捕捉数据库中运行的所有 SQL,并生成 SQL 日志文件),来统计日志中最长执行时间和执行最高频次的 SQL 语句,直观地反映 SQL 执行情况,对于 SQL 的优化工作提供了极大的便利。
DMLOG 工具可实现如下功能:
java -version
[root@localhost DMLOG8]# java -jar Dmlog_DM_8.18.jar
会生成两个两个excel表格:
more_than_0_ms_log_result.xls:统计的是执行时间大于 0 毫秒的 SQL 日志分析结果。
more_than_0_times_log_result.xls:统计的是执行次数大于 0 次的 SQL 日志分析结果。
在达梦数据库的日志分析中,发现一条执行次数为 59 次、平均执行时间约 18.5 秒(最大 46.7 秒)的慢 SQL。该 SQL 用于从 bpmqregbillnoderecord 表中查询待处理记录,并按 srcdocid 分组取最新一条,最后进行分页返回。
执行计划是数据库在执行 SQL 前规划的一条“执行路线图”,描述了数据从底层表被读取、过滤、排序、连接,最终返回结果的完整过程。你写 SQL 只是告诉数据库“要什么”,执行计划决定了它“怎么拿”。
同一条 SQL 可能对应多种执行方式,数据库会基于代价选出它认为最优的方案,并以操作符形式展示每一步动作。
以一条三层嵌套的分页查询为例,用 EXPLAIN 查看执行计划:
SELECT id, srcdocid, sortorder, processedtimes, errmsg, content,
timestamp_createdon, timestamp_createdby,
timestamp_lastchangedon, timestamp_lastchangedby, version
FROM (
SELECT rownum AS rn_outer, t.*
FROM (
SELECT id, srcdocid, sortorder, processedtimes, errmsg, content,
timestamp_createdon, timestamp_createdby,
timestamp_lastchangedon, timestamp_lastchangedby, version
FROM (
SELECT id, srcdocid, sortorder, processedtimes, errmsg, content,
timestamp_createdon, timestamp_createdby,
timestamp_lastchangedon, timestamp_lastchangedby, version,
ROW_NUMBER() OVER (PARTITION BY srcdocid ORDER BY sortorder) AS rn
FROM test_biz_record
WHERE (processedtimes < 3 AND timestamp_lastchangedon <= SYSDATE - 30)
OR (processedtimes = 0 AND timestamp_lastchangedon > SYSDATE - 30)
) temp1
WHERE rn = 1
ORDER BY sortorder
) t
) temp3
WHERE rn_outer BETWEEN 1 AND 5000;
加入explain,分析执行计划:
为什么操作符又有2又有3的,难道好多种全表扫描吗
数字越大,通常是越新的实现版本,但不代表功能不同,核心作用是一样的。比如CSCN->全表扫描(基础版),CSCN2->全表扫描(优化版)。
| 序号 | 名称 | 描述 | 含义 |
|---|---|---|---|
| 13 | CSCN2 | CLUSTER SCAN | 全表扫描,读取 test_biz_record 全部 20 万行 |
| 12 | SLCT2 | SELECT | 过滤,执行 WHERE 中的 processedtimes 和 timestamp_lastchangedon 条件 |
| 11 | SORT3 | SORT | 排序,为窗口函数 ROW_NUMBER() 做准备 |
| 10 | AFUN | ANALYTIC FUNCTION | 分析函数,执行 ROW_NUMBER() OVER (PARTITION BY srcdocid ORDER BY sortorder) |
| 9 | PRJT2 | PROJECT OPERATION | 投影,输出窗口函数计算后的中间结果 |
| 8 | SLCT2 | SELECT | 过滤,执行 WHERE rn = 1,每组只保留一条 |
| 7 | SORT3 | SORT | 排序,执行 ORDER BY sortorder |
| 6 | PRJT2 | PROJECT OPERATION | 投影,准备输出给外层 |
| 5 | RNSK | ROWNUM STOP KEY | ROWNUM 停止键,执行分页限制,凑够 5000 行即停 |
| 4 | PRJT2 | PROJECT OPERATION | 投影,输出 rn_outer 等列 |
| 3 | SLCT2 | SELECT | 过滤,执行 rn_outer BETWEEN 1 AND 5000 |
| 2 | PRJT2 | PROJECT OPERATION | 投影,最终输出字段 |
| 1 | NSET2 | RESULT SET | 结果集收集,返回给客户端 |
CSCN2 全表扫描,SLCT2 过滤行,SORT3 排序,AFUN 分析函数(窗口函数),PRJT2 投影(决定输出哪些列、计算表达式),NSET2 结果收集。
| 类别 | 操作符 | 描述(英文) | 含义 |
|---|---|---|---|
| 数据访问 | CSCN2 | CLUSTER SCAN | 全表扫描 |
| SSEK | SECONDARY INDEX SEEK | 二级索引范围扫描 | |
| CSEK | CLUSTER INDEX SEEK | 聚集索引范围扫描 | |
| SSCN | SECONDARY INDEX SCAN | 二级索引全扫描 | |
| BLKUP | BACK LOOP UP | 回表(通过 rowid 回原表取数据) | |
| 过滤 | SLCT2 | SELECT | 按条件过滤行 |
| 投影 | PRJT2 | PROJECT OPERATION | 选择输出列、计算表达式 |
| 排序 | SORT3 | SORT | 对数据排序 |
| 分析函数 | AFUN | ANALYTIC FUNCTION | 执行窗口函数(如 ROW_NUMBER) |
| 连接 | NEST LOOP | NESTED LOOP JOIN | 嵌套循环连接 |
| HASH JOIN | HASH JOIN | 哈希连接 | |
| MERGE JOIN | MERGE JOIN | 归并排序连接 | |
| 聚集 | AAGR | SIMPLE AGGREGATE | 简单聚集(无 GROUP BY) |
| HAGR | HASH GROUP AGGREGATE | 哈希分组聚集 | |
| SAGR | STREAM GROUP AGGREGATE | 流分组聚集 | |
| 分页优化 | RNSK | ROWNUM STOP KEY | ROWNUM 停止键,提前终止扫描 |
| 结果输出 | NSET2 | RESULT SET | 结果集收集,返回客户端 |
统计信息是数据库对表中数据分布情况的“描述”,比如:
这些信息数据库不会实时去数,而是定期收集一次,存下来,供优化器估算代价时用。
数据库执行 SQL 前,优化器要决定“走哪条路最快”,而它的判断依据就是统计信息。
举个例子:
test_biz_record 有 20 万行,优化器可能觉得全表扫描也行。统计信息越准,优化器选的路就越靠谱。
因为数据是会变的:
如果统计信息一直不更新,优化器就会拿着过时的数据做判断,可能选错执行计划,导致 SQL 变慢。
所以需要定期重新收集,让统计信息跟上数据的真实情况。
'SYSDBA':收集 SYSDBA 模式下的所有对象。100:采样率 100%,全量扫描,结果最准。FALSE:不使用数据页采样,按行采样。'FOR ALL COLUMNS SIZE AUTO':自动决定为哪些列收集分布信息(直方图)。8:并行度 8,同时开 8 个线程收集,速度更快。-- 看表的行数、块数
SELECT TABLE_NAME, NUM_ROWS, BLOCKS, LAST_ANALYZED
FROM USER_TABLES
WHERE TABLE_NAME = 'TEST_BIZ_RECORD';
-- 看列的唯一值数、是否收集直方图
SELECT COLUMN_NAME, NUM_DISTINCT, NUM_NULLS, HISTOGRAM, LAST_ANALYZED
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = 'TEST_BIZ_RECORD';
1. 唯一值数量(NUM_DISTINCT)
ID 列有 200000 个唯一值 → 正好等于表的总行数,说明 ID 是唯一的(主键)。SRCDOCID 列有 5000 个唯一值 → 说明 20 万行数据被分到了 5000 个不同的 srcdocid 组里。PROCESSEDTIMES 列只有 5 个唯一值 → 说明这个字段取值很集中(比如只有 0、1、2、3、4 这几种状态)。2. 空值数量(NUM_NULLS)
3. 直方图类型(HISTOGRAM)—— 这是最关键的
FREQUENCY(频率直方图):出现在 SRCDOCID、SORTORDER、PROCESSEDTIMES、TIMESTAMP_CREATEDON 等列上。说明这些列唯一值数量较少(如 PROCESSEDTIMES 只有 5 个值),优化器为每个值都记录了精确的出现频率。HEIGHT BALANCED(高度平衡直方图):出现在 ID、ERRMSG、CONTENT 等列上。说明这些列唯一值数量太多(接近行数),优化器无法为每个值单独记录,只能把数据粗略描述分布。4. 最后收集时间(LAST_ANALYZED)
hint 是写在 SQL 注释里的手动干预指令,用来强制优化器选择指定的索引、连接方式或并行度,比如执行一条sql,优化器走了A路,我们想让他走B路。
对于第三节中的sql,加入hint,尝试改变执行计划:
EXPLAIN SELECT /*+ INDEX(test_biz_record idx_test_biz_1) */ id, srcdocid, sortorder, processedtimes, errmsg, content,
timestamp_createdon, timestamp_createdby,
timestamp_lastchangedon, timestamp_lastchangedby, version
FROM (
SELECT rownum AS rn_outer, t.*
FROM (
SELECT id, srcdocid, sortorder, processedtimes, errmsg, content,
timestamp_createdon, timestamp_createdby,
timestamp_lastchangedon, timestamp_lastchangedby, version
FROM (
SELECT id, srcdocid, sortorder, processedtimes, errmsg, content,
timestamp_createdon, timestamp_createdby,
timestamp_lastchangedon, timestamp_lastchangedby, version,
ROW_NUMBER() OVER (PARTITION BY srcdocid ORDER BY sortorder) AS rn
FROM test_biz_record
WHERE (processedtimes < 3 AND timestamp_lastchangedon <= SYSDATE - 30)
OR (processedtimes = 0 AND timestamp_lastchangedon > SYSDATE - 30)
) temp1
WHERE rn = 1
ORDER BY sortorder
) t
) temp3
WHERE rn_outer BETWEEN 1 AND 5000;
对一条三层嵌套的分页查询,尝试用 /*+ INDEX(…) */ hint 强制走索引。执行计划从全表扫描(CSCN2,代价 36)变为二级索引全扫描 + 回表(SSCN + BLKUP2,代价 863)。虽然 hint 成功干预了执行路径,但由于需要大量回表,代价反而大幅上升,说明优化器选择全表扫描是合理的。这也验证了 hint 是“强制手段”而非“优化手段”,使用前需对比实际效果。
文章
阅读量
获赞
