注册
DM8 从 CSCN2 到 SSEK2 + BLKUP2:50 万行表的 B+ 树二级索引前后对比
培训园地/ 文章详情 /

DM8 从 CSCN2 到 SSEK2 + BLKUP2:50 万行表的 B+ 树二级索引前后对比

waytoofar 2026/08/19 169 1 0

从聚集索引扫描到二级索引定位与回表验证

摘要: 本文在 DM8 独立实验实例中构造 50 万行测试数据,对 USER_CODE 未建立二级索引和建立普通 B+ 树二级索引两种状态进行对比。实验通过索引字典、执行计划和重复计时三类证据,验证等值查询由 CSCN2 变为 SSEK2 + BLKUP2,范围 COUNT(*) 的访问路径由 CSCN2 变为 SSEK2 且未出现 BLKUP2。文章重点说明三类操作符的区别、索引生效的判断方法及测试结果的适用边界。

关键词: DM8、B+ 树、二级索引、执行计划、CSCN2、SSEK2、BLKUP2、SQL 优化

1. 问题与判断

USER_CODE 上创建普通 B+ 树二级索引后,为什么等值查询的执行计划出现 SSEK2 + BLKUP2,而范围 COUNT(*) 的访问路径使用 SSEK2、没有 BLKUP2

判断索引是否真正发挥作用,至少需要保留以下三类证据:

  • 对象证据: 目标索引已经创建,并能在索引字典中查到;
  • 计划证据: 相同 SQL 的执行计划发生符合预期的变化;
  • 计时证据: 在相同环境和相同口径下重复执行,耗时变化与计划变化能够相互印证。

只有 CREATE INDEX 执行成功,不能证明具体 SQL 已经使用该索引。

2. 实验环境与测试数据

2.1 实验环境

项目 本次实测值
操作系统与数据库 银河麒麟 V10;DM8 V8
实验实例 数据库 BTREEDB;实例 BTREE01;本地端口 5240
测试表 SYSDBA.T_BPLUS_TEST;500,000 行
目标列 USER_CODE;仅改变该列是否存在二级索引
等值条件 USER_CODE='USER_0400000',命中 1 行
范围条件 连续 7 位补零编码;USER_0400000USER_0404999 命中 5,000 行(1%)
计时口径 按实验报告口径:每组首轮预热,后 3 次取中位数;记录 disql 端到端“已用时间”

本次使用 SYSDBA 是隔离实验环境的实际对象属主,不作为生产对象管理建议。使用其他用户复现时,应把过程调用中的模式名替换为实际对象属主。

2.2 创建并装载测试表

CREATE TABLE T_BPLUS_TEST ( ID INT, USER_CODE VARCHAR(20), USER_NAME VARCHAR(50), DEPT_ID INT ); INSERT INTO T_BPLUS_TEST SELECT LEVEL, 'USER_' || LPAD(CAST(LEVEL AS VARCHAR(20)), 7, '0'), 'NAME_' || CAST(LEVEL AS VARCHAR(20)), MOD(LEVEL, 100) FROM DUAL CONNECT BY LEVEL <= 500000; COMMIT; CALL SP_TAB_INDEX_STAT_INIT('SYSDBA', 'T_BPLUS_TEST');

SP_TAB_INDEX_STAT_INIT 只收集指定表上所有索引的统计信息,不包含表统计或列统计。首次调用只覆盖当时已有的索引;创建目标二级索引后还会再次调用,使新索引统计信息参与代价估算。

装载后核对总行数、目标键范围和索引状态:

SELECT COUNT(*) AS TOTAL_ROWS, MIN(USER_CODE) AS MIN_CODE, MAX(USER_CODE) AS MAX_CODE FROM T_BPLUS_TEST; SELECT INDEX_NAME, TABLE_NAME, UNIQUENESS FROM USER_INDEXES WHERE TABLE_NAME = 'T_BPLUS_TEST';

本次实验中可见自动生成的聚集索引 INDEX33555466,尚不存在 IDX_BPLUS_USER_CODE。“无索引”准确指 USER_CODE 没有可用的二级索引,不是整张普通表完全没有 B+ 树结构。

01_50万行数据与索引前状态.png

图 1:T_BPLUS_TEST 共 500,000 行,目标二级索引尚未创建

3. 索引前:执行计划选择 CSCN2

对等值查询和范围统计查询执行 EXPLAIN。计时 SQL 与 EXPLAIN SQL 保持一致:

EXPLAIN SELECT USER_NAME, DEPT_ID FROM T_BPLUS_TEST WHERE USER_CODE = 'USER_0400000'; EXPLAIN SELECT COUNT(*) FROM T_BPLUS_TEST WHERE USER_CODE BETWEEN 'USER_0400000' AND 'USER_0404999';

两份计划均出现 CSCN2。达梦官方操作符说明将 CSCN2 定义为“聚集索引扫描”。本次计划选择扫描系统聚集索引中的较大范围,再由 SLCT2 完成 USER_CODE 条件过滤。

这里不能把 CSCN2 简单解释为“表没有任何索引”,因为 DM8 普通表本身仍由聚集索引组织。

02_索引前CSCN2与重复计时.png

图 2:无 USER_CODE 二级索引时,计划提取到 CSCN2

4. 创建二级索引并收集索引统计信息

CREATE INDEX IDX_BPLUS_USER_CODE ON T_BPLUS_TEST(USER_CODE); CALL SP_TAB_INDEX_STAT_INIT('SYSDBA', 'T_BPLUS_TEST'); SELECT INDEX_NAME, TABLE_NAME, UNIQUENESS FROM USER_INDEXES WHERE TABLE_NAME = 'T_BPLUS_TEST';

新建索引后重新收集该表所有索引的统计信息,再通过索引字典确认 IDX_BPLUS_USER_CODE 已存在。统计信息会参与代价估算;如果仍使用旧统计信息,优化器可能无法准确比较新的访问路径。

03_创建二级索引并核对.png

图 3:IDX_BPLUS_USER_CODE 创建成功并完成字典核对

5. 索引后:为什么一个有 BLKUP2,一个没有

使用完全相同的两条 SQL 再次执行 EXPLAIN

5.1 等值查询:SSEK2 + BLKUP2

等值查询的 scan_range 起止键相同,SSEK2USER_CODE 执行等值定位。查询还需要返回 USER_NAMEDEPT_ID,而这两个字段不在该二级索引中,因此 BLKUP2 依据二级索引返回的定位信息访问聚集索引记录,补取所需列。

5.2 范围统计:AAGR2 + SSEK2

范围查询只返回 COUNT(*)。本次计划以 SSEK2 扫描二级索引范围内的索引项,再由 AAGR2 聚合计数,且未出现 BLKUP2;该查询不需要读取 USER_NAMEDEPT_ID 等索引外列。

这说明“是否回表”还与返回列和索引覆盖情况有关,不能只看 WHERE 条件。

04_索引后SSEK2_BLKUP2与重复计时.png

图 4:索引后等值查询出现 SSEK2 与 BLKUP2,范围 COUNT(*) 未出现 BLKUP2

查询 USER_CODE 二级索引 创建二级索引后
等值查询,返回 USER_NAMEDEPT_ID CSCN2:扫描聚集索引并过滤 SSEK2 等值定位 + BLKUP2 补取列
范围查询,返回 COUNT(*) AAGR2 + CSCN2 AAGR2 + SSEK2;未出现 BLKUP2

6. 重复计时结果

按实验报告既定口径,截图中每组 4 次业务耗时的首轮作为预热,随后 3 次取中位数;截图本身未给轮次增加 warm-up/run 标签。EXPLAIN 本身的耗时和首轮预热不纳入结果。

这里记录的是同一 disql 会话显示的端到端“已用时间”,不是数据库 CPU 时间。

查询 无索引 3 轮(ms) 无索引中位数 有索引 3 轮(ms) 有索引中位数 耗时比
等值查询 40.665 / 42.695 / 42.283 42.283 0.776 / 0.639 / 0.459 0.639 约 66.17 倍
范围 COUNT(*) 47.910 / 43.874 / 43.444 43.874 0.310 / 0.386 / 0.209 0.310 约 141.53 倍

本次结果: 等值查询中位数由 42.283 ms 降至 0.639 ms,耗时约为原来的 1/66;范围 COUNT(*) 由 43.874 ms 降至 0.310 ms,耗时约为原来的 1/142。该结果只适用于本次虚拟机、50 万行数据和当前缓存状态,不能直接作为生产容量或性能承诺。

7. 建了索引却没走时的排查顺序

  1. 检查统计信息。 新建索引后可使用 SP_TAB_INDEX_STAT_INIT 收集该表所有索引的统计信息,再查看 EXPLAIN。该过程不等于收集表统计或列统计。
  2. 检查选择率。 本次等值查询命中 1/500000,范围查询命中 5000/500000(1%),都属于返回少量记录的场景。如果查询返回表中大部分数据,优化器选择 CSCN2 可能更合理。
  3. 检查编码与范围条件。 本次范围恰好命中 5,000 行,依赖 USER_CODE 连续且按 7 位补零;VARCHARBETWEEN 按字符次序比较,不能泛化到任意编码。
  4. 检查谓词和类型。 函数包裹、隐式类型转换、复合索引前导列缺失等情况,都可能使现有索引不能按预期使用。应以实际 scan_range、索引名和操作符为准。
  5. 检查返回列。 当优化器选择二级索引且返回列未被该索引覆盖时,SELECT * 往往需要 BLKUP2;优化器也可能直接选择 CSCN2。复合或覆盖索引还要同时考虑空间和 DML 维护成本。

8. 风险与实验清理

生产环境不要直接复制本实验的数据量和索引方案。创建索引会占用空间,执行 INSERTUPDATEDELETE 时也要维护索引;大表建索引还可能带来明显 I/O 和锁等待。应在业务低峰完成空间评估,并准备回退语句后再实施。

确认不再保留实验对象后执行:

DROP INDEX IDX_BPLUS_USER_CODE; DROP TABLE T_BPLUS_TEST;

清理提醒: 上述命令不具备幂等性,重跑前应先在索引字典中核对同名对象。删除表本身也会清理其索引,这里先显式删除目标索引用于展示清理顺序。若要在真实业务表中删除或重建索引,必须保存原建索引语句、覆盖完整业务周期验证依赖,并按变更流程执行。

9. 复盘

本次实验区分了三个容易混淆的概念:

  • CSCN2 表示优化器在扫描聚集索引;
  • SSEK2 表示优化器通过二级索引扫描或定位;
  • BLKUP2 表示二级索引中的信息不足以返回所需列,还需要访问聚集索引记录。

索引是否“有效”,不能只看对象是否存在,而要检查执行计划、选择率、统计信息和重复计时能否形成同一结论。

结论: 在本次 50 万行实验中,USER_CODE 二级索引将等值查询从 CSCN2 改为 SSEK2 + BLKUP2,将范围 COUNT(*) 的访问路径从 CSCN2 改为 SSEK2,并显著降低了本机重复执行时间;同时也验证了“命中索引”不等于“一定不回表”。

10. 参考资料

  1. 达梦技术文档:《管理索引》
  2. 达梦技术文档:《查询优化》
  3. 达梦技术文档:《附录 4:执行计划操作符》
评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服