
1. 从一次真实的慢查询排查说起那天下午监控系统突然告警一个核心报表的生成时间从平时的3秒飙升到了近2分钟。业务方电话直接打到了我这里语气里满是焦急。登录到达梦数据库服务器第一件事就是抓取当前正在执行的慢SQL。当看到那条熟悉的、原本运行良好的多表关联查询语句时我心里咯噔一下。没有新增数据没有修改代码问题出在哪我立刻调出了这条SQL的执行计划。计划显示原本应该走索引的NEST LOOP嵌套循环连接不知为何变成了全表HASH JOIN哈希连接其中一个超过百万行的大表被选作了哈希构建表瞬间耗光了临时表空间性能断崖式下跌。这个场景相信每一位和达梦数据库DM打过交道的DBA或开发都不会陌生。SQL优化不是纸上谈兵它始于对执行计划的精准解读。执行计划就像是数据库引擎的“思维导图”它清晰地告诉你为了得到结果数据库打算先做什么、后做什么、用什么方法做。看不懂它优化就无从谈起看懂了它你就能像医生看X光片一样直击SQL性能的病灶。本文将结合我多年处理达梦数据库性能问题的实战经验抛开晦涩的理论直接带你上手如何获取、解读执行计划并基于此进行有的放矢的优化。2. 获取执行计划不止是EXPLAIN在动手优化之前你必须先拿到SQL的执行计划。在达梦中主要有三种方式每种都有其特定的使用场景和“坑点”。2.1 基础武器EXPLAIN 命令这是最常用、最直接的方式。在管理工具如DM管理工具、DBeaver或命令行中在SQL前加上EXPLAIN即可。EXPLAIN SELECT a.order_id, b.customer_name, SUM(c.amount) FROM orders a JOIN customers b ON a.customer_id b.customer_id JOIN order_details c ON a.order_id c.order_id WHERE a.create_date DATE 2023-01-01 GROUP BY a.order_id, b.customer_name;执行后你会得到一个文本格式的执行计划输出。这里有一个关键细节EXPLAIN默认生成的是预估的执行计划。它基于统计信息如表的行数、索引的选择性来计算成本并选择它认为最优的路径。但“预估”和“实际”可能有差距尤其是当统计信息过时或分布不均时。我遇到过很多次EXPLAIN显示完美走索引实际跑起来却是全表扫描根源就是统计信息太久没更新。2.2 实战利器EXPLAIN FOR 与动态性能视图要看到SQL实际执行时的计划尤其是在它已经跑起来的时候就需要用到EXPLAIN FOR和动态性能视图。方法一EXPLAIN FOR先获取会话的SESSIDSELECT SESSID FROM V$SESSIONS WHERE STATEACTIVE AND SQL_TEXT LIKE %你的SQL关键词%;然后使用该SESSIDEXPLAIN FOR SESSID 123456; -- 替换为实际的SESSID这能输出该会话当前正在执行语句的实际计划对于诊断正在发生的慢查询极其有用。方法二查询V$SQL_PLAN和V$SQL_PLAN_DETAIL当SQL执行完毕后其执行计划会被缓存。你可以通过以下关联查询来获取SELECT * FROM V$SQL_PLAN p, V$SQL_PLAN_DETAIL d WHERE p.ADDRESS d.ADDRESS AND p.HASH_VALUE d.HASH_VALUE AND p.SQL_TEXT LIKE %你的SQL关键词% ORDER BY p.ADDRESS, p.HASH_VALUE, d.PLAN_ID;V$SQL_PLAN_DETAIL包含了更详细的执行步骤信息如访问的表、索引、连接方法等。这里有个经验有时你会发现同一条SQL有多个ADDRESS和HASH_VALUE这可能是因为SQL文本有细微差别如空格、大小写导致数据库认为是不同的语句分别进行了硬解析和缓存。2.3 图形化辅助管理工具与第三方工具对于复杂的执行计划文本阅读比较费力。达梦自带的DM管理工具可以将EXPLAIN的结果以图形化方式展示节点之间的流向、成本占比一目了然非常适合初步分析。此外像DBeaver这类通用的数据库客户端在连接达梦后需正确配置JDBC驱动也支持图形化显示执行计划对于习惯使用这类工具的开发者来说更加方便。注意图形化工具虽好但有时会隐藏一些底层细节。在进行深度优化时我仍然建议结合文本计划一起看特别是V$SQL_PLAN_DETAIL中的具体操作符和谓词信息。3. 拆解执行计划读懂操作符的“语言”拿到一份文本执行计划你可能会被一堆诸如NSET2、PRJT2、SLCT2、HASH2 INNER JOIN、CSCN2、SSEK2等术语搞得头晕。别慌我们来逐一拆解。达梦的执行计划是树形结构缩进代表层级通常从最内层叶子节点往最外层根节点阅读。3.1 核心操作符详解NSET(Nested Set): 这是计划树的根节点表示整个查询的结果集收集。它本身不进行数据操作只是协调其子节点的执行。PRJT(Project): 投影操作。负责从子节点传递上来的行中选择投影出最终查询需要的列。例如SELECT a, b FROM t就会有一个PRJT节点来过滤掉其他列。SLCT(Select): 选择操作。对应SQL中的WHERE子句根据条件过滤行。JOIN系列: 描述表连接方式这是优化重中之重。NEST LOOP INDEX JOIN: 嵌套循环索引连接。适用于驱动表外层循环结果集较小且内层表连接字段有高效索引的情况。它是逐行匹配的。HASH JOIN: 哈希连接。通常用于没有高效索引或结果集较大的等值连接。它会选择一个小表或过滤后的小结果集在内存中构建哈希表然后扫描大表进行探测。风险点如果优化器错误地选择了大表作为哈希构建表会消耗大量内存和临时空间导致性能骤降。这正是我开篇遇到的问题。MERGE JOIN: 归并连接。要求两个输入集在连接键上都是有序的。如果表上有合适的索引或者前序步骤如SORT已经排好序可能会选择此方式。表扫描方式:CSCN(Cluster Scan): 聚簇扫描全表扫描。顺序读取表的所有数据页。SSEK(Secondary Index Seek): 二级索引查找。通过非聚簇索引定位到rowid再回表获取数据。CSEK(Cluster Index Seek): 聚簇索引查找。如果表是聚簇表索引组织表通过聚簇索引直接定位数据。BLKUP(Bookmark Lookup): 回表操作。当使用SSEK后需要根据索引中的rowid去数据块中取出完整的行数据时出现。SORT和HAGR(Hash Aggregate) /SAGR(Sort Aggregate): 排序和聚合操作。GROUP BY、DISTINCT、ORDER BY可能会引发这些操作。HAGR在内存中哈希聚合适合分组键区分度高的场景SAGR先排序再聚合当分组数量极大或内存不足时可能被选用。3.2 关键字段解读执行计划中每一行都附带重要信息#CSCN2: [1, 1000, 4]: 这里的[1, 1000, 4]是[估算行数, 估算代价, 输出行宽度]。估算行数是优化器认为该步骤将输出的行数与实际行数的偏差是导致错误计划的主要原因之一。predicates: 显示该步骤应用的过滤条件。检查这里是否有效利用了索引。access predicates: 索引访问谓词说明利用索引的哪些列进行查找。filter predicates: 过滤谓词在访问到数据后进行的额外过滤。一个简单的分析流程从最内层缩进的操作开始看看它扫描了哪个表TAB字段用什么方式扫描的CSCN还是SSEK估算行数是否合理。然后一层层往外看连接方式和顺序重点关注JOIN节点的类型和估算代价。最终成本最高的那个节点COST值最大往往就是性能瓶颈所在。4. 基于执行计划的优化实战看懂计划只是第一步如何根据计划中的“不良信号”进行优化才是核心价值所在。4.1 场景一索引失效与全表扫描问题特征执行计划中出现大量CSCN全表扫描而你认为应该走索引。排查与解决检查谓词首先看SLCT或扫描操作符的predicates。确保WHERE子句中的列确实有索引并且谓词形式允许使用索引。例如对索引列使用函数WHERE UPPER(name) ABC、进行数学运算WHERE amount*2 100或者使用!、NOT IN都可能导致索引失效。检查统计信息执行DBMS_STATS.GATHER_TABLE_STATS(模式名,表名);更新统计信息。优化器严重依赖统计信息来判断是走索引快还是全表扫描快。如果统计信息显示表很小或者索引列的数据分布极度倾斜比如90%的值都是同一个优化器可能认为全表扫描更划算。检查索引选择性创建一个高选择性的索引才有意义。选择性 不同值数量 / 总行数。比值越接近1选择性越好。为status这种只有‘Y’‘N’两种值的列建索引通常效果甚微。使用HINT强制索引慎用如果确信索引更优而优化器顽固不化可以尝试使用HINT。例如SELECT /* INDEX(t idx_name) */ * FROM t WHERE name xxx;。但这是最后的手段因为数据分布变化后强制索引可能反而更糟。务必在测试环境充分验证。4.2 场景二错误的连接顺序与连接方式问题特征多表连接时执行计划选择的驱动表不合适或者该用NEST LOOP却用了HASH JOIN导致性能低下。排查与解决分析驱动表在NEST LOOP中驱动表应该是结果集小、过滤条件强的表。检查执行计划看是否把小结果集的大表放在了外层。你可以通过改变SQL写法来“暗示”优化器例如将筛选条件最严格的表放在FROM子句首位并非总是有效或者使用/* LEADING(t1 t2) */HINT来指定连接顺序。评估HASH JOIN的构建表在HASH JOIN中内存中构建哈希表的应该是较小的那个结果集。如果执行计划显示大表被选为构建表HASH JOIN的左子节点通常是构建表这将是灾难性的。解决方法是确保连接条件中的小表有高效的过滤条件好的WHERE子句或者考虑为该表在连接列上建立索引促使优化器选择NEST LOOP。检查连接条件索引对于NEST LOOP内层表的连接列必须有索引。对于HASH JOIN虽然不强制要求索引但如果在连接前内层表能有索引快速过滤掉大量数据同样能极大提升性能。4.3 场景三昂贵的排序与聚合问题特征执行计划中出现SORT或SAGR且代价COST非常高尤其是在处理大量数据时。排查与解决ORDER BY或GROUP BY的列是否与索引顺序一致如果经常按(create_date, region)分组或排序那么创建一个(create_date, region)的复合索引数据库可能直接利用索引的有序性来避免排序操作即“索引覆盖排序”。是否真的需要所有数据排序前端分页查询时常写SELECT * FROM t ORDER BY id LIMIT 20。如果id有索引这很快。但如果写成SELECT * FROM t ORDER BY name LIMIT 20而name无索引数据库会先对全表排序再取前20条极其低效。确保排序列有索引。考虑使用HAGR代替SAGR如果GROUP BY导致SAGR可以尝试调大HAGR_BUF_GLOBAL_SIZE等内存参数使得哈希聚合能在内存中完成但需权衡内存消耗。4.4 场景四子查询与视图的性能陷阱问题特征执行计划显示对视图或子查询进行了物化临时结果集或者子查询被重复执行DEPENDENT SUBQUERY。排查与解决将相关子查询重写为JOIN很多情况下EXISTS、IN子查询可以等价地改写为JOIN优化器能更好地为JOIN选择执行计划。例如-- 原语句 SELECT * FROM orders o WHERE EXISTS (SELECT 1 FROM customers c WHERE c.id o.customer_id AND c.status VIP); -- 改写为 SELECT o.* FROM orders o JOIN customers c ON o.customer_id c.id WHERE c.status VIP;谨慎使用视图视图在逻辑上简化了查询但物理上可能是一个“黑盒”。优化器有时无法将外层查询的条件“下推”Push Down到视图内部导致视图先全量计算再进行过滤。对于复杂视图考虑将其逻辑直接写入主查询或使用达梦的“视图合并”优化需评估。使用WITH子句公共表表达式的考量WITH CTE AS (...)可以提高可读性但在达梦中CTE可能会被物化为临时表。如果CTE数据量小且被多次引用这是好事如果数据量大且只引用一次则可能增加额外开销。需要根据执行计划判断。5. 高级调优并行执行与参数干预当单条SQL优化到极致后还可以从更宏观的层面提升性能。5.1 并行查询PQO的启用与把控达梦支持并行执行计划对于大表扫描、大量数据连接或聚合操作并行化能充分利用多核CPU资源。检查是否启用执行计划中操作符带有P标识如PCSCN即表示并行扫描。如何启用会话级SET ENABLE_PARALLEL_DML 1;(DML并行) /ALTER SESSION ENABLE PARALLEL QUERY;语句级使用HINTSELECT /* PARALLEL(t, 4) */ ... FROM t;指定对表t使用4个并行度。注意事项并行不是银弹。它会增加CPU和内存开销对于大量短小查询开启并行反而会降低整体吞吐量。并行度DOP设置需谨慎一般不建议超过CPU物理核心数。对于OLTP型的高并发短事务通常关闭并行。5.2 关键优化器参数的影响达梦数据库有一些初始化参数能影响优化器的全局行为OPTIMIZER_MODE: 优化器模式。通常保持默认ALL_ROWS即可它倾向于获得最佳吞吐量的计划。在某些交互式场景可尝试设置为FIRST_ROWS让优化器优先考虑快速返回前几行。USE_PLN_POOL: 是否使用计划缓存。生产环境务必开启1避免相同的SQL反复进行硬解析。PK_WITH_CLUSTER: 主键是否自动创建聚簇索引。理解聚簇索引表数据按索引顺序物理存储和非聚簇索引索引单独存储存的是rowid的区别对设计高性能表结构至关重要。修改这些参数需要重启数据库实例影响全局。切忌在生产环境盲目调整。任何参数变更都应在测试环境基于真实负载进行验证。6. 构建持续优化的闭环工具与习惯SQL优化不是一次性的任务而是一个持续的过程。建立慢SQL监控定期从达梦的动态性能视图V$LONG_EXEC_SQLS或V$SQL_HISTORY中抓取执行时间长、逻辑读/物理读高的SQL。这是发现潜在性能问题的源头。使用AWR/ASH报告如果版本支持达梦数据库的企业版通常提供类似Oracle AWR的性能报告工具。它能提供特定时间段内的系统负载、TOP SQL、等待事件等全景信息是进行深度性能分析的利器。制定SQL审核规范在开发阶段介入对复杂查询、多表连接、大数据量操作进行执行计划审查避免有性能隐患的SQL进入生产环境。定期更新统计信息为核心表设置定时任务在业务低峰期如凌晨自动收集统计信息。对于数据变化剧烈的表收集频率需要更高。绑定变量与计划稳定性对于高并发OLTP应用务必使用绑定变量如WHERE id ?避免因字面值不同导致的大量硬解析和SQL注入风险。但也要注意有时绑定变量可能导致“绑定变量窥探”问题即第一次执行时传入的值生成的计划不适用于后续传入的其他值。达梦有相应的参数和HINT来管理此行为。回到开头的那个案例我通过分析执行计划迅速定位到是统计信息陈旧导致优化器错估了中间结果集的大小进而选择了错误的连接顺序和方式。在紧急情况下我使用/* LEADING(A B) USE_NL(B) */HINT强制了正确的连接顺序和嵌套循环方式让报表暂时恢复正常。随后在业务低峰期我对相关表进行了全面的统计信息收集并建立了定期更新任务从根本上解决了问题。解读执行计划就像是与数据库优化器对话。它告诉你它的“想法”而你需要基于对数据特性、业务逻辑和系统资源的理解去判断这个“想法”是否合理并在必要时巧妙地引导它。这个过程没有绝对的公式需要的是不断的实践、观察和思考。每一次成功的优化不仅解决了眼前的问题更是对你数据库知识体系的一次加固。