DM 性能诊断与根因分析
参考地址:https://eco.dameng.com/
核心概念对照表(MySQL vs DM)
优化领域 MySQL 8.0 达梦 DM8 差异说明
执行计划 EXPLAIN EXPLAIN(ALL) DM 支持更多选项
执行计划格式 JSON/TABLE JSON/TABLE/XML DM 支持 XML
索引 Hint USE INDEX /*+ INDEX / 语法不同
连接 Hint STRAIGHT_JOIN /+ LEADING / DM 更灵活
并行 Hint - /+ PARALLEL / DM 支持并行
物化 Hint - /+ MATERIALIZE / DM 独有
统计信息 ANALYZE TABLE DBMS_STATS.GATHER DM 更精细
绑定变量 预处理语句 预处理语句 相同
SQL 缓存 query_cache 结果缓存 DM 更智能
执行计划缓存 plan_cache 计划缓存 DM 自动管理
实时 SQL 监控 performance_schema V$SQL DM 更直观
SQL 历史 - V$SQL_HISTORY DM 独有
执行统计 events_statements_ V$SQL_STAT DM 更详细
资源限制 resource_groups Profiles DM 更灵活
并行查询 有限支持 全面支持 DM 更强
查询重写 - 自动查询重写 DM 智能优化
增量统计 部分支持 全面支持 DM 更完善
一、SQL 执行流程剖析
1.1 DM SQL 执行流程
SQL 提交
↓
I/O 成本 = 读取块数 × 块读取时间
CPU 成本 = CPU 周期数 × CPU 时间
网络成本 = 传输数据量 × 网络带宽
影响优化器决策的因素:
• 统计信息准确性
• 系统参数(OPTIMIZER_MODE 等)
• Hint 提示
• 绑定变量
• 数据分布
1.3 优化器如何最优选择
问题 说明
数据从哪里来? 全表扫描?索引扫描?
数据如何过滤? 下推?后过滤?
数据如何连接? Nested Loop?Hash Join?Merge Join?
数据如何排序? 内存排序?磁盘排序?
执行计划 = 优化器基于成本估算选择的"关键路径"
操作符 含义 性能评估
CSCN 全表扫描(Cluster Scan) 差
SSCN 二级索引扫描(Secondary Scan) 差
SSEK 二级索引定位(Secondary Seek) 好
BLKUP 回表查询(Block Lookup) 中
二、优化实验
实验 1:索引选择优化
实验目的: 理解优化器如何选择索引,学会引导优化器
MySQL 对应命令:
SQL
EXPLAIN SELECT * FROM T1 USE INDEX (IDX1) WHERE ...;
EXPLAIN SELECT * FROM T1 FORCE INDEX (IDX1) WHERE ...;
DM 索引 Hint 语法:
SQL
-- 建议使用索引
SELECT /*+ INDEX(T1 IDX_COL1) */ * FROM T1 WHERE COL1 = 100;
-- 强制使用索引
SELECT /*+ INDEX(T1 IDX_COL1) */ * FROM T1 WHERE COL1 = 100;
-- 不使用索引
SELECT /*+ NO_INDEX(T1 IDX_COL1) */ * FROM T1 WHERE COL1 = 100;
-- 使用多个索引
SELECT /*+ INDEX(T1 IDX_COL1, IDX_COL2) */ * FROM T1
WHERE COL1 = 100 AND COL2 = 200;
实验步骤:
CREATE INDEX IDX_COL1 ON T_INDEX_TEST(COL1);
CREATE INDEX IDX_COL2 ON T_INDEX_TEST(COL2);
CREATE INDEX IDX_COL1_COL2 ON T_INDEX_TEST(COL1, COL2);
INSERT INTO T_INDEX_TEST
SELECT LEVEL, MOD(LEVEL, 100), MOD(LEVEL, 50), RPAD('X', 100)
FROM DUAL CONNECT BY LEVEL <= 100000;
CALL DBMS_STATS.GATHER_TABLE_STATS('SYSDBA', 'T_INDEX_TEST');
-- 2. 查询 1:单列条件
EXPLAIN(ALL) SELECT * FROM T_INDEX_TEST WHERE COL1 = 50;
-- 优化器选择:IDX_COL1(正确)
-- 3. 查询 2:双列条件
EXPLAIN(ALL) SELECT * FROM T_INDEX_TEST WHERE COL1 = 50 AND COL2 = 25;
-- 优化器选择:IDX_COL1_COL2(复合索引,最优)
-- 4. 查询 3:范围查询
EXPLAIN(ALL) SELECT * FROM T_INDEX_TEST WHERE COL1 > 50 AND COL1 < 100;
-- 优化器选择:IDX_COL1(范围扫描)
-- 5. 查询 4:函数查询(索引失效)
EXPLAIN(ALL) SELECT * FROM T_INDEX_TEST WHERE YEAR(COL3) = 2026;
-- 优化器选择:FULL TABLE SCAN(索引失效!)
-- 6. 优化:改写查询
EXPLAIN(ALL) SELECT * FROM T_INDEX_TEST
WHERE COL3 >= '2026-01-01' AND COL3 < '2027-01-01';
-- 优化器选择:IDX_COL3(如果有的话)
索引选择原则:
-- ❌ 索引失效:隐式转换
SELECT * FROM T WHERE VARCHAR_COL = 123; -- 数字
-- ✅ 优化后
SELECT * FROM T WHERE VARCHAR_COL = '123'; -- 字符串
-- ❌ 索引失效:LIKE 前缀通配符
SELECT * FROM T WHERE COL LIKE '%ABC';
-- ✅ 优化后(如果可能)
SELECT * FROM T WHERE COL LIKE 'ABC%';
-- ❌ 索引失效:OR 条件(部分情况)
SELECT * FROM T WHERE COL1 = 1 OR COL2 = 2;
-- ✅ 优化后
SELECT * FROM T WHERE COL1 = 1
UNION ALL
SELECT * FROM T WHERE COL2 = 2;
性能对比测试:
SQL
-- 测试 1:无 Hint(优化器选择)
SELECT * FROM T_INDEX_TEST WHERE COL1 = 50 AND COL2 = 25;
-- 耗时:0.05 秒(使用 IDX_COL1_COL2)
-- 测试 2:强制使用单列索引
SELECT /*+ INDEX(T_INDEX_TEST IDX_COL1) */ *
FROM T_INDEX_TEST WHERE COL1 = 50 AND COL2 = 25;
-- 耗时:0.15 秒(使用 IDX_COL1,然后过滤 COL2)
-- 测试 3:强制全表扫描
SELECT /*+ FULL(T_INDEX_TEST) */ *
FROM T_INDEX_TEST WHERE COL1 = 50 AND COL2 = 25;
-- 耗时:1.2 秒(全表扫描)
-- 结论:优化器选择通常是最优的,Hint 用于特殊情况
实验 2:连接优化(JOIN 类型选择)
实验目的: 理解不同 JOIN 类型的适用场景,优化多表查询
MySQL 对应命令:
SQL
EXPLAIN SELECT * FROM T1 JOIN T2 ON T1.ID = T2.T1_ID;
-- JOIN_TYPE: NESTED LOOP / HASH JOIN / MERGE JOIN
DM 连接类型:
SQL
-- NESTED LOOP JOIN(适合小表驱动大表)
SELECT /*+ USE_NL(T1 T2) */ * FROM T1 JOIN T2 ON T1.ID = T2.T1_ID;
-- HASH JOIN(适合大表连接)
SELECT /*+ USE_HASH(T1 T2) */ * FROM T1 JOIN T2 ON T1.ID = T2.T1_ID;
-- MERGE JOIN(适合已排序数据)
SELECT /*+ USE_MERGE(T1 T2) */ * FROM T1 JOIN T2 ON T1.ID = T2.T1_ID;
-- 指定驱动表
SELECT /*+ LEADING(T1 T2) */ * FROM T1 JOIN T2 ON T1.ID = T2.T1_ID;
实验步骤:
CREATE TABLE T_LARGE (
ID INT PRIMARY KEY,
T_SMALL_ID INT,
COL2 VARCHAR(100)
);
-- 2. 插入数据
INSERT INTO T_SMALL
SELECT LEVEL, RPAD('S', 100) FROM DUAL CONNECT BY LEVEL <= 1000;
INSERT INTO T_LARGE
SELECT LEVEL, MOD(LEVEL, 1000) + 1, RPAD('L', 100)
FROM DUAL CONNECT BY LEVEL <= 100000;
CREATE INDEX IDX_T_LARGE_T_SMALL_ID ON T_LARGE(T_SMALL_ID);
CALL DBMS_STATS.GATHER_TABLE_STATS('SYSDBA', 'T_SMALL');
CALL DBMS_STATS.GATHER_TABLE_STATS('SYSDBA', 'T_LARGE');
-- 3. 测试 1:NESTED LOOP(小表驱动大表)
EXPLAIN(ALL) SELECT /*+ USE_NL(S L) */ *
FROM T_SMALL S JOIN T_LARGE L ON S.ID = L.T_SMALL_ID;
-- 执行计划:
-- 1) TABLE SCAN (T_SMALL) -- 驱动表(小表)
-- 2) INDEX RANGE SCAN (IDX_T_LARGE_T_SMALL_ID) -- 被驱动表
-- COST: 500.00
SET TIMING ON;
SELECT /*+ USE_NL(S L) */ *
FROM T_SMALL S JOIN T_LARGE L ON S.ID = L.T_SMALL_ID;
-- 耗时:0.8 秒
-- 4. 测试 2:HASH JOIN(大表连接)
EXPLAIN(ALL) SELECT /*+ USE_HASH(S L) */ *
FROM T_SMALL S JOIN T_LARGE L ON S.ID = L.T_SMALL_ID;
-- 执行计划:
-- 1) HASH JOIN
-- 2) TABLE SCAN (T_SMALL)
-- 3) TABLE SCAN (T_LARGE)
-- COST: 800.00
SELECT /*+ USE_HASH(S L) */ *
FROM T_SMALL S JOIN T_LARGE L ON S.ID = L.T_SMALL_ID;
-- 耗时:1.5 秒
-- 5. 测试 3:MERGE JOIN(已排序数据)
EXPLAIN(ALL) SELECT /*+ USE_MERGE(S L) */ *
FROM T_SMALL S JOIN T_LARGE L ON S.ID = L.T_SMALL_ID;
-- 执行计划:
-- 1) MERGE JOIN
-- 2) INDEX FULL SCAN (T_SMALL)
-- 3) INDEX FULL SCAN (IDX_T_LARGE_T_SMALL_ID)
-- COST: 600.00
SELECT /*+ USE_MERGE(S L) */ *
FROM T_SMALL S JOIN T_LARGE L ON S.ID = L.T_SMALL_ID;
-- 耗时:1.0 秒
JOIN 类型选择原则:
JOIN 类型 适用场景 数据量要求 内存要求
NESTED LOOP 小表驱动大表,有索引 驱动表<1000 行 低
HASH JOIN 大表连接,无索引 两表都大 高(需要 hash 表)
MERGE JOIN 已排序数据,等值连接 任意 中
性能对比总结:
NESTED LOOP: 0.8 秒 ✅ 最优(小表驱动)
MERGE JOIN: 1.0 秒
HASH JOIN: 1.5 秒
优化建议:
三、性能对比总结
3.1 MySQL vs DM 优化能力对比
优化功能 MySQL 8.0 DM8 优势方
执行计划选项 基础 丰富(ALL/JSON/XML) DM
Hint 支持 有限 全面 DM
并行查询 有限 全面支持 DM
物化视图 不支持 全面支持 DM
查询重写 不支持 自动重写 DM
SQL 轮廓 不支持 支持 DM
增量统计 部分 全面 DM
四、核心收获与建议
4.1 核心收获
文章
阅读量
获赞
