从聚集索引扫描到二级索引定位与回表验证
摘要: 本文在 DM8 独立实验实例中构造 50 万行测试数据,对
USER_CODE未建立二级索引和建立普通 B+ 树二级索引两种状态进行对比。实验通过索引字典、执行计划和重复计时三类证据,验证等值查询由CSCN2变为SSEK2 + BLKUP2,范围COUNT(*)的访问路径由CSCN2变为SSEK2且未出现BLKUP2。文章重点说明三类操作符的区别、索引生效的判断方法及测试结果的适用边界。
关键词: DM8、B+ 树、二级索引、执行计划、CSCN2、SSEK2、BLKUP2、SQL 优化
在 USER_CODE 上创建普通 B+ 树二级索引后,为什么等值查询的执行计划出现 SSEK2 + BLKUP2,而范围 COUNT(*) 的访问路径使用 SSEK2、没有 BLKUP2?
判断索引是否真正发挥作用,至少需要保留以下三类证据:
只有 CREATE INDEX 执行成功,不能证明具体 SQL 已经使用该索引。
| 项目 | 本次实测值 |
|---|---|
| 操作系统与数据库 | 银河麒麟 V10;DM8 V8 |
| 实验实例 | 数据库 BTREEDB;实例 BTREE01;本地端口 5240 |
| 测试表 | SYSDBA.T_BPLUS_TEST;500,000 行 |
| 目标列 | USER_CODE;仅改变该列是否存在二级索引 |
| 等值条件 | USER_CODE='USER_0400000',命中 1 行 |
| 范围条件 | 连续 7 位补零编码;USER_0400000—USER_0404999 命中 5,000 行(1%) |
| 计时口径 | 按实验报告口径:每组首轮预热,后 3 次取中位数;记录 disql 端到端“已用时间” |
本次使用 SYSDBA 是隔离实验环境的实际对象属主,不作为生产对象管理建议。使用其他用户复现时,应把过程调用中的模式名替换为实际对象属主。
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+ 树结构。
图 1:T_BPLUS_TEST 共 500,000 行,目标二级索引尚未创建
对等值查询和范围统计查询执行 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 普通表本身仍由聚集索引组织。
图 2:无 USER_CODE 二级索引时,计划提取到 CSCN2
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 已存在。统计信息会参与代价估算;如果仍使用旧统计信息,优化器可能无法准确比较新的访问路径。
图 3:IDX_BPLUS_USER_CODE 创建成功并完成字典核对
使用完全相同的两条 SQL 再次执行 EXPLAIN。
等值查询的 scan_range 起止键相同,SSEK2 按 USER_CODE 执行等值定位。查询还需要返回 USER_NAME、DEPT_ID,而这两个字段不在该二级索引中,因此 BLKUP2 依据二级索引返回的定位信息访问聚集索引记录,补取所需列。
范围查询只返回 COUNT(*)。本次计划以 SSEK2 扫描二级索引范围内的索引项,再由 AAGR2 聚合计数,且未出现 BLKUP2;该查询不需要读取 USER_NAME、DEPT_ID 等索引外列。
这说明“是否回表”还与返回列和索引覆盖情况有关,不能只看 WHERE 条件。
图 4:索引后等值查询出现 SSEK2 与 BLKUP2,范围 COUNT(*) 未出现 BLKUP2
| 查询 | 无 USER_CODE 二级索引 |
创建二级索引后 |
|---|---|---|
等值查询,返回 USER_NAME、DEPT_ID |
CSCN2:扫描聚集索引并过滤 |
SSEK2 等值定位 + BLKUP2 补取列 |
范围查询,返回 COUNT(*) |
AAGR2 + CSCN2 |
AAGR2 + SSEK2;未出现 BLKUP2 |
按实验报告既定口径,截图中每组 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 万行数据和当前缓存状态,不能直接作为生产容量或性能承诺。
SP_TAB_INDEX_STAT_INIT 收集该表所有索引的统计信息,再查看 EXPLAIN。该过程不等于收集表统计或列统计。CSCN2 可能更合理。USER_CODE 连续且按 7 位补零;VARCHAR 的 BETWEEN 按字符次序比较,不能泛化到任意编码。scan_range、索引名和操作符为准。SELECT * 往往需要 BLKUP2;优化器也可能直接选择 CSCN2。复合或覆盖索引还要同时考虑空间和 DML 维护成本。生产环境不要直接复制本实验的数据量和索引方案。创建索引会占用空间,执行 INSERT、UPDATE、DELETE 时也要维护索引;大表建索引还可能带来明显 I/O 和锁等待。应在业务低峰完成空间评估,并准备回退语句后再实施。
确认不再保留实验对象后执行:
DROP INDEX IDX_BPLUS_USER_CODE;
DROP TABLE T_BPLUS_TEST;
清理提醒: 上述命令不具备幂等性,重跑前应先在索引字典中核对同名对象。删除表本身也会清理其索引,这里先显式删除目标索引用于展示清理顺序。若要在真实业务表中删除或重建索引,必须保存原建索引语句、覆盖完整业务周期验证依赖,并按变更流程执行。
本次实验区分了三个容易混淆的概念:
CSCN2 表示优化器在扫描聚集索引;SSEK2 表示优化器通过二级索引扫描或定位;BLKUP2 表示二级索引中的信息不足以返回所需列,还需要访问聚集索引记录。索引是否“有效”,不能只看对象是否存在,而要检查执行计划、选择率、统计信息和重复计时能否形成同一结论。
结论: 在本次 50 万行实验中,
USER_CODE二级索引将等值查询从CSCN2改为SSEK2 + BLKUP2,将范围COUNT(*)的访问路径从CSCN2改为SSEK2,并显著降低了本机重复执行时间;同时也验证了“命中索引”不等于“一定不回表”。
文章
阅读量
获赞
