资讯详情

达梦数据库执行计划详解:从EXPLAIN到ET,定位慢SQL瓶颈的实战指南

📅 2026/9/28 13:58:07 | 华诺云谱 👁 阅读
达梦数据库执行计划详解:从EXPLAIN到ET,定位慢SQL瓶颈的实战指南
达梦数据库的执行计划我最早是在一个迁移项目里被逼着认真研究的。当时业务方反馈某个报表查询在MySQL上跑1秒多迁到DM8之后直接飙到30秒所有人第一反应都是“国产库不行”。后来抓出执行计划一看驱动表选错嵌套循环连接把大表当内表扫了好几遍加个索引、改了一下join顺序直接回到1.2秒。从那之后我就养成了一个习惯任何达梦的慢SQL第一件事不是猜原因而是把执行计划抓出来读一遍。这个内容适合谁达梦DBA、正在做MySQL或Oracle迁到达梦的应用开发、运维工程师以及准备达梦DCP认证但总被执行计划劝退的同学。它解决的核心问题就一个达梦执行计划长什么样、怎么抓、怎么读、怎么靠它定位慢SQL的真正瓶颈。下面我按照自己的实操经验把这一整套东西掰开揉碎讲清楚。1. 执行计划是达梦优化的第一课1.1 先搞清楚执行计划到底是什么执行计划说白了就是数据库优化器给你这条SQL选的一条“路”。你写的是“我要什么”优化器负责决定“怎么拿”——是全表扫一圈还是走索引是先查A表再连B表还是反着来两个大表join是用嵌套循环还是hash join。这条路怎么走直接决定这条SQL是毫秒级还是分钟级。达梦在这方面有一个很典型的特点它的整体架构和优化器模型跟Oracle的相似度很高SQL语法兼容度也高连执行计划的排版风格都带着几分Oracle的影子。但注意相似不等于相同它毕竟还是自研的内核很多细节诸如操作符命名、代价模型、统计信息机制都各有各的脾气。我在项目里见过太多人把Oracle的执行计划经验原封不动套到达梦上结果踩坑踩得莫名其妙。达梦DM8的优化器是基于代价的CBO。优化器会根据表的统计信息、索引情况、系统参数、数据分布等因素给每种可能的执行路径算一个“代价”最后选代价最小的那条。这套机制的优点是大多数情况下能自动选出合理路径缺点也很明显——如果统计信息不准、或者某些参数设置不合适它算出来的“最优”很可能就是个坑。1.2 为什么达梦执行计划比MySQL的更难读懂很多从MySQL转过来的朋友第一次看到达梦执行计划是懵的。原因很简单MySQL的EXPLAIN输出是一张扁平的表格一行一个表字段就那么几个简单直给。达梦的输出是一棵缩进的树每个节点带操作符、代价、行数、字节数层级关系靠缩进体现初次接触确实不太适应。打个不太恰当的比方MySQL的执行计划像一张购物清单达梦的像一棵组织架构图。购物清单你扫一眼就知道要买什么但组织架构图你得先搞清楚谁向谁汇报、哪个部门在哪一层才能真正看懂这家公司的权力流向。执行计划里的“权力流向”就是数据流动方向和数据获取方式。好消息是一旦你习惯了达梦这种树状结构它的信息量反而比MySQL大得多。你能看到每个步骤的代价占比、估算行数、扫描方式、join方式甚至能对比估算值和实际值之间的差距这对定位性能问题非常有帮助。1.3 读懂执行计划前必须先知道的达梦基础知识在看执行计划之前有几个达梦的基本设定需要先心里有数不然会莫名踩坑大小写敏感问题。达梦默认对未加引号的标识符会转换为大写所以你在建表时写的user_name实际存储的是USER_NAME。如果从MySQL迁移过来原库的表名是user_name迁到达梦后没注意大小写规则查询时就会出现“表或视图不存在”这类报错连执行计划都到不了。模式Schema概念。达梦和Oracle类似用户和模式是绑定的一个用户对应一个同名模式。查表时如果不带模式前缀默认走当前登录用户自己的模式。看执行计划时你会看到模式名.表名这种完整限定名这对确认你查的是不是预期的那张表很有用。优化器开关参数。达梦有一批和优化器行为直接相关的参数比如ENABLE_HASH_JOIN、ENABLE_NEST_LOOP、ENABLE_INDEX_JOIN等控制优化器能不能用某种连接方式。有时候某条SQL突然走了一个奇怪的路径先查一下这些开关有没有被人改过。2. 达梦执行计划的抓取方式从EXPLAIN到真实执行统计2.1 最基础的EXPLAIN命令看优化器的“原计划”达梦抓执行计划最基础的方式就是EXPLAIN命令格式如下EXPLAIN SELECT O.ORDER_NO, U.USER_NAME FROM T_ORDER O INNER JOIN T_USER U ON O.USER_ID U.USER_ID WHERE O.STATUS 1;执行之后工具里会输出一棵树状结构的计划。以达梦自带的管理工具DM Manager或disql命令行为例输出大概长这样1 #NSET2: [0, 1, 22] 2 #NEST LOOP INNER JOIN2: [0, 1, 22] 3 #CSCN2: [0, 100, 6] 4 #CSEK2: [0, 1, 16]注意每行的方括号里有三个数字我的经验是依次对应“估算代价、估算行数、估算字节数”。具体数值跟你本机的统计信息和数据量有关不同版本格式可能略有差异但结构基本就是这种嵌套缩进的风格。EXPLAIN最大的价值是它告诉你优化器“打算”怎么干。它不会真正执行SQL所以速度很快适合在写SQL阶段就用来检查路径。但它有一个致命弱点——它只给你看估算值不给你看实际值。如果统计信息已经过期估算行数可能跟实际差几百倍这时候光看EXPLAIN会被带偏。2.2 从“想怎么干”到“实际怎么干”ET工具看真实执行要看到SQL真正执行时的路径、耗时和实际扫描行数达梦提供了一个非常实用的工具——ET。这是达梦性能分析里我很依赖的一个功能用法也不复杂开启ET参数。在dm.ini配置文件中把ET参数设为1或者用系统函数实时开启SP_SET_PARA_VALUE(1, ET, 1);执行你要分析的SQL让它真实跑一遍。调用ET命令查看最近一条SQL的执行统计信息。执行完ET后你能看到每个操作符的实际执行时间毫秒级、逻辑读次数、物理读次数、返回行数等关键指标。这套数据是SQL执行现场的真实录像比EXPLAIN的估算值可信得多。我个人的习惯是两者配合先用EXPLAIN快速确认大方向再用ET确认实际瓶颈在哪个节点。很多“看起来走了索引但还是很慢”的诡异问题就是靠ET的逐节点耗时数据才定位到问题的。2.3 不同工具里看执行计划的体验差异达梦官方自带的工具主要有两个DM Manager图形化管理工具和disql命令行工具。图形工具里选中SQL直接按快捷键就能看到计划树鼠标悬停还能看详细信息适合日常排查。disql适合脚本化操作适合在服务器上直接调试。关于第三方工具这里想多说一句。很多新同学会问能不能用Navicat或DBeaver看达梦执行计划。实测下来的结论是能连但看执行计划的体验参差不齐。Navicat连接达梦后能跑SQL但执行计划展示格式未必友好DBeaver配合达梦驱动也能用但有时候需要手动调整。我的建议是如果你要严肃地调优一条慢SQL尽量回到DM Manager或disql里看不要依赖第三方工具的兼容性。工具只是手段准确的数据才是目的。3. 达梦执行计划的关键操作符认出它们就读懂了一半3.1 数据访问方式全表扫描、索引扫描与回表执行计划里最底层的节点一定是对某个表的数据访问。达梦中常见的数据访问操作符有这几类CSCN2全表扫描。优化器觉得走索引不如全表扫一遍或者表上没有可用索引时会出现。大表上的CSCN2通常是性能问题的信号但也要结合过滤条件判断——如果过滤条件能过滤掉90%的数据全表扫描可能反而是最优选择。CSEK2索引范围扫描。这是走索引的典型操作通过索引定位到满足条件的行位置。相比全表扫描它能大幅减少需要访问的数据页。CSEK2 CBLKUP2索引范围扫描加回表操作。走索引定位到rowid后再根据rowid回到表里取其他列的值。这里有个性能关键点如果索引选择性差回表次数会非常吓人还不如全表扫描。很多SQL慢就慢在“索引走了但回表次数爆炸”。SSCN系统表扫描。扫描的是系统字典表一般是处理权限、元数据时出现业务SQL里不太常见。达梦操作符后面带的数字后缀比如CSCN2、CSEK2跟内部版本的实现有关你不需要死记硬背只需要知道它们代表同一类操作的演进版本即可。3.2 表连接方式嵌套循环与哈希连接当SQL里涉及多张表join时执行计划里会多出一个连接节点。达梦里最常见的两种连接方式是NEST LOOP INNER JOIN2嵌套循环连接。它的执行逻辑是取驱动表外层表的一行去内层表找匹配的行循环往复。这种连接适合小表驱动大表、内层表有索引的场景。如果驱动表选错比如用大表驱动小表循环次数会爆炸。HASH JOIN2哈希连接。把其中一张表的连接列做成哈希表另一张表的数据去哈希表里查找匹配。它适合两张大表连接、等值连接场景缺点是会有哈希表的构建代价也有内存消耗。此外还有排序合并连接SORT MERGE JOIN以及达梦对某些特殊场景的优化形式。在实际优化中我判断连接方式是否合理核心看三点两表的数据量级、连接列上有没有索引、驱动表的选择是否正确。执行计划上这两点一目了然。3.3 中间处理操作符排序、聚合与过滤除了数据访问和连接执行计划里还会出现一些中间处理节点比如排序SORT、聚合AGG、视图VIEW、过滤FILTER等。这些节点的代价往往容易被忽略但实际业务里恰恰是它们拖慢了SQL。最典型的是排序。SQL里有ORDER BY、GROUP BY、DISTINCT时优化器可能选择先把数据排序再做后续处理。如果排序的数据量超出内存限制就会落盘到临时表空间性能会急剧恶化。这种情况下执行计划里会出现SORT节点如果它的代价占比很高你就该琢磨一下能不能通过索引消除排序——比如让索引键的顺序契合ORDER BY的字段顺序优化器就可能直接用索引的有序性跳过排序。还有一种情况是FILTER节点它可能对应NOT IN子查询、EXISTS子查询等场景。如果子查询在每一行上都要执行一次代价会非常大这种问题往往需要改写SQL来根治。3.4 操作符速查表我整理的一份常见清单为了方便入门我把达梦执行计划里高频出现的操作符整理成了一张速查表新手可以直接对照参考。操作符含义性能提示CSCN2全表扫描大表高过滤条件时需警惕CSEK2索引范围扫描通常高效注意选择性CBLKUP2回表取数据回表次数过多时要小心SSCN系统表扫描非业务SQL常见NEST LOOP INNER JOIN2嵌套循环连接小表驱动大表时高效HASH JOIN2哈希连接大表等值连接常用SORT / SORT3排序操作数据量大时可能落盘AGG / AAGR2聚合操作关注group by的代价SPOOL中间结果暂存常见于复杂子查询NSET结果集输出一般是计划的顶层节点4. 从执行计划到SQL优化一个真实的调优全过程4.1 慢SQL背景一个30秒的报表查询为了把读计划的方法讲透我拿一个真实的调优场景来拆解结构做了简化但思路完全一致。有一个订单报表查询逻辑很简单SELECT O.ORDER_NO, O.AMOUNT, U.USER_NAME, U.PHONE FROM T_ORDER O INNER JOIN T_USER U ON O.USER_ID U.USER_ID WHERE O.STATUS 1 AND O.CREATE_TIME TO_DATE(2024-01-01, YYYY-MM-DD) ORDER BY O.CREATE_TIME DESC;T_ORDER表有500万行T_USER表有20万行。这条SQL初始跑出来的执行计划核心路径是先对T_ORDER做CSCN2全表扫描然后嵌套循环连接T_USER最后排序输出。整体耗时30秒左右。一眼看过去有两个问题T_ORDER表500万行为什么要全表扫描ORDER BY能不能走索引避免排序答案大概率是T_ORDER上没有一个能同时覆盖STATUS和CREATE_TIME的复合索引优化器算来算去觉得全表扫描更“便宜”。4.2 读计划、查统计、补索引三步走我当时排障的步骤是这样的第一步用EXPLAIN看计划确认是不是全表扫描估算行数多少。第二步查统计信息是否新鲜。达梦的统计信息不会自动更新如果表数据量变化很大但统计信息还是旧的优化器的估算就会很离谱。手动收集一下统计信息DBMS_STATS.GATHER_TABLE_STATS(你的用户, T_ORDER); DBMS_STATS.GATHER_TABLE_STATS(你的用户, T_USER);注意达梦的DBMS_STATS包和Oracle用法很像但收集时建议在业务低峰期执行否则会消耗一定的系统资源。第三步根据WHERE条件设计索引。这个查询的过滤条件是STATUS和CREATE_TIME所以本着最左前缀的原则创建复合索引CREATE INDEX IDX_ORDER_STATUS_TIME ON T_ORDER(STATUS, CREATE_TIME);创建后再看执行计划CSCN2变成了CSEK2但仍带着CBLKUP2回表操作。回表本身能接受因为过滤后返回的行数并不多。如果你对性能更苛刻可以把查询涉及的列都塞进索引里做覆盖索引进一步消除回表但要知道索引不是越多越好写入放大和存储成本都要权衡。4.3 前后对比执行计划怎么看效果优化后的执行计划路径变成了通过IDX_ORDER_STATUS_TIME做索引范围扫描定位到符合条件的T_ORDER行然后对T_USER走索引查找匹配最后借助索引的有序性直接输出排序节点被消除。SQL耗时从30秒降到了1.2秒左右。这里面最核心的转折点不是“加了个索引”这个动作本身而是你知道为什么要加这个索引、加了之后执行计划应该变成什么样子。如果你看不懂执行计划你只是蒙着头试索引可能试了七八个都找不到对的。而看懂执行计划的人能从CSCN2、CBLKUP2、SORT这些操作符的组合里直接推导出问题所在一击命中。4.4 用HINT干预优化器不仅要知道还要会收放有时候优化器就是不听话统计信息也新鲜索引也存在它偏不走最优路径。这时候可以用达梦支持的HINT来干预。达梦的HINT写法跟Oracle类似放在SELECT后面SELECT /* INDEX(O IDX_ORDER_STATUS_TIME) */ O.ORDER_NO, O.AMOUNT FROM T_ORDER O WHERE O.STATUS 1;常用HINT包括FULL(表别名)强制全表扫描、INDEX(表别名 索引名)强制走指定索引、LEADING(表别名)控制驱动表顺序等。但这里有个经验之谈HINT是最后的手段不是第一选择。因为SQL一旦写了HINT就可能在某些数据分布变化后锁死一个坏路径。我更推荐的做法是先用HINT验证“如果走某条路径性能能提升多少”确认方案有效后再把HINT去掉尝试通过调整统计信息或索引设计让优化器自己选对路。如果实在不行再保留HINT并做好注释说明。5. 执行计划分析中的常见误区和避坑指南5.1 从MySQL迁过来的人特别容易踩的几个坑这几年达梦国产化替代的项目非常多大批MySQL应用迁到DM8执行计划层面的问题也特别集中。我梳理几个高频坑首先很多MySQL的SQL习惯带反引号达梦默认不认识反引号。迁过来的SQL如果不改直接报语法错误根本走不到执行计划这一步。其次MySQL的LIMIT语法达梦支持但如果你写的是LIMIT 10 OFFSET 20这种深度分页执行计划里可能会出现大范围的扫描性能和使用习惯都要重新审视。第三个坑是隐式类型转换。MySQL和达梦都允许数字和字符串比较时自动转型但转型之后很可能导致索引失效。比如字段是VARCHAR类型你传入一个数字达梦可能会把字段转成数字再比较导致索引无法使用。排查时看到计划走了CSCN2但表上有索引、字段也匹配第一反应就去看类型。5.2 统计信息过期执行计划失真的头号元凶达梦的优化器依赖统计信息来估算行数和代价但统计信息不会自动更新。我遇到最夸张的一次表从200万行涨到了1800万行统计信息还停留在200万的快照上优化器估算出来的计划当然就是拿200万的视角在规划一条1800万数据的路能不慢吗。这就引出一个重要习惯在达梦环境里你要建立统计信息更新的机制。核心表在批量数据加载后及时收集周期性的大表数据变动也要有定时任务兜底。命令很简单DBMS_STATS.GATHER_TABLE_STATS(SCHEMA名, 表名, NULL, 100);这里的第四个参数是采样百分比100表示全量统计。小表可以全量超大表建议采样比如10或者30兼顾准确性和效率。5.3 判断执行计划好坏的一个实用框架很多同学会问拿到一个执行计划后怎么快速判断它到底好不好我分享一个自己的判断顺序第一看有没有异常的大表全表扫描。递归地扫描执行计划树找到所有CSCN2节点看对应的表数据量级如果百万级以上的表出现CSCN2且过滤条件多就要警戒了。第二估算行数与实际行数是否差距过大。用EXPLAIN看估算值用ET看实际值如果同一个节点两者差出几个数量级基本可以断定统计信息或者连接估算出了问题。第三看连接顺序是否合理。值不值得作为驱动表的表是不是在合适的位置。第四看有没有多余的排序、回表、中间结果集。这些节点往往意味着还有优化空间。这套框架不需要你背什么复杂的公式它就是一份经验性的体检清单。看得多了一眼扫过去就能定位个七七八八。5.4 排查问题速查表最后送上一张我在运维排障时常用的速查表按“症状→原因→解法”的路径整理希望能帮你在真实环境里少走弯路。现象可能原因处理办法有索引却不走索引计划显示CSCN2隐式类型转换 / 统计信息过期检查字段类型是否匹配更新统计信息估算行数远小于实际行数统计信息陈旧或未收集重新收集统计信息索引走了一点但回表次数巨大索引选择性差或覆盖列不足调整索引考虑覆盖索引排序节点代价极高ORDER BY/GROUP BY数据量过大试建匹配排序字段顺序的索引消除排序嵌套循环连接极慢驱动表选错或内层表无索引检查连接列索引调整驱动表相同SQL时快时慢统计信息波动 / 参数被修改对比两段时间的参数和统计信息写在最后的一点个人体会达梦执行计划这个东西说难也难说简单也简单。你说它难是因为它确实有一堆操作符、一堆估算规则、一堆参数配置你说它简单是因为无论多复杂的问题最后都归结到“数据是怎么被捞出来、又是怎么被连起来、中间有没有无效动作”这三件事上。我自己的经验是与其死记硬背操作符的含义不如多拿真实的慢SQL来反复对比“优化前”和“优化后”的计划差异慢慢就会形成肌肉记忆。最后分享一个执行计划之外的小技巧达梦有AOP自动优化计划功能可以对重复执行的SQL进行计划缓存和自动演进。生产环境里我通常建议先通过常规手段把SQL优化到位再配合AOP让系统自动稳定住计划双管齐下效果最好。优化数据库这条路没有捷径抓到执行计划你就已经赢了一半。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑