DB2中的索引承担着数据定位和唯一性约束的双重职责,但索引并非创建之后就一劳永逸。查询性能突然下降,往往不是SQL写错了,而是索引在物理层面或统计层面已经失效。无论是统计信息失真导致优化器放弃索引,还是B+树因大量随机写入而深度增加,最终表现都是相同的:原本走索引的执行计划变成了全表扫描。理解索引失效的完整链条,比单纯重建一次索引更有价值。接下来从失效原因、诊断方法、重建策略和预防机制四个层面展开。

一、索引失效的常见原因分类
索引失效并不总是意味着索引对象损坏。在大多数日常运维场景中,优化器没有选择索引,才是更常见的失效表现。DB2的优化器基于成本估算决定是否使用索引,而成本估算的核心输入就是统计信息。如果一张表经过大量插入、删除或更新,但RUNSTATS没有及时执行,那么SYSCAT.TABLES中的基数、页数以及SYSCAT.COLUMNS中的列分布都会严重偏离真实数据。优化器可能因此高估索引扫描的代价,转而选择全表扫描。例如一张订单表原本只有几十万行,某次批量导入后增长到几千万行,如果统计信息还停留在导入之前,优化器甚至可能认为该表仍然很小,此时不仅索引失效,连连接顺序和关联算法都可能出现错误。
第二种常见原因是索引的物理结构恶化。DB2的索引通常采用B+树结构,频繁的随机插入和删除会导致页分裂,使索引树的高度增加,叶子节点变得稀疏。这样一来,每次索引扫描需要访问的索引页变多,逻辑读成本上升。当索引碎片率超过一定阈值时,即使统计信息准确,优化器也可能因为估算出的随机I/O成本过高而放弃索引。另外,表数据页的碎片化同样会拖累索引扫描,因为索引叶节点中的RID指向的数据页变得分散,顺序扫描索引却无法带来顺序读取数据页的收益。
第三种原因是索引对象本身被标记为无效或需要重建。数据库异常宕机、存储子系统故障、文件系统错误等都可能损坏索引结构,此时DB2会在系统层面将索引标记为不可用。在这种状态下,即使SQL语句中指定了索引提示,DB2也无法使用该索引,只能进行全表扫描。还有一类容易被忽略的情况:在表执行REORG之后,如果没有同步重组索引,某些索引可能仍然保留旧的物理布局,与重组后的表数据不匹配,查询效率反而下降。此外,隐式类型转换、对索引列使用函数、前导列缺失、LIKE '%关键字'等写法,会造成索引无法被匹配,这类情况虽然不属于物理失效,但同样会导致执行计划不走索引,需要在诊断时加以区分。
二、如何判断索引是否失效
判断索引是否真正失效,不能只看索引是否存在。DB2提供了多个系统视图和表函数来观察索引的使用情况。其中SYSCAT.INDEXES保存了索引的定义信息,而LASTUSED字段可以显示索引最后一次被优化器采用的时间。如果一个索引长期没有被使用,就需要怀疑它是否已经失效,或者根本不再被任何查询需要。需要注意的是,不同版本的DB2对LASTUSED的更新时机有所差异,因此最好结合SYSIBMADM.INDEX_USAGE视图中的扫描次数进行交叉验证。
SELECT
SUBSTR(INDSCHEMA,1,20) AS INDSCHEMA,
SUBSTR(INDNAME,1,30) AS INDNAME,
SUBSTR(TABSCHEMA,1,20) AS TABSCHEMA,
SUBSTR(TABNAME,1,30) AS TABNAME,
LASTUSED,
INDEX_SCANS,
INDEX_ONLY_SCANS
FROM SYSIBMADM.INDEX_USAGE
ORDER BY LASTUSED ASC
FETCH FIRST 20 ROWS ONLY;
除了查询视图,还可以使用db2pd工具查看索引的物理状态和统计信息。例如执行db2pd -db sample -tcbstats all可以输出表空间和表的详细统计数据,其中包含索引的叶页数量、树高度等指标。对于怀疑存在物理损坏的索引,可以执行INSPECT命令来检查索引结构的完整性。更直接的方法是使用db2expln或db2exfmt分析某条SQL的访问计划,如果输出中出现了TBSCAN而预期应该是IXSCAN,再结合最优索引列判断,就能快速定位索引是否被优化器放弃。如果索引状态显示为需要重建,则必须通过重建操作才能恢复。
实际诊断时,建议先收集执行计划,再检查统计信息的更新时间。可以通过查询SYSCAT.TABLES的STATS_TIME字段来判断统计信息是否过期,如果该时间远早于大批量数据变更时间,那么失效原因大概率是统计信息问题。如果统计信息较新,但执行计划仍然不走索引,就需要进一步检查索引碎片率和表重组状态。对于大表,直接查看索引碎片率可能会消耗较多资源,可以在低峰期使用REORGCHK命令进行快速评估。总之,索引失效的诊断需要从执行计划、统计信息、物理结构三个维度依次排查,避免盲目重建。
三、索引重建与优化策略
索引重建并不是简单地删除再创建。在DB2中,根据失效原因的不同,可以选择REORG、RUNSTATS、REBUILD INDEX或CREATE INDEX等不同操作。如果诊断结果表明是统计信息过期,优先执行RUNSTATS即可,无需重建索引。对于碎片化严重的表和索引,需要先使用REORG TABLE整理表数据页,再使用REORG INDEXES ALL FOR TABLE整理索引页。如果索引已经被标记为无效,或者REORG之后仍然无法恢复,则可以使用REBUILD INDEX针对单个索引进行重建,或者直接删除后重新创建。对于大表和高并发环境,建议使用在线重组参数INPLACE ALLOW WRITE ACCESS来减少锁等待。
REORG TABLE db2inst1.orders INPLACE ALLOW WRITE ACCESS; REORG INDEXES ALL FOR TABLE db2inst1.orders ALLOW NO ACCESS; RUNSTATS ON TABLE db2inst1.orders WITH DISTRIBUTION AND DETAILED INDEXES ALL;
上面这段命令组合适合在维护窗口内执行,先重组表数据,再重组所有索引,最后更新统计信息。如果业务无法接受长时间锁表,可以将REORG TABLE改为REORG TABLE ... INPLACE ALLOW WRITE ACCESS,并把REORG INDEXES设置为ALLOW READ ACCESS,以降低对读写的影响。对于特别大的表,可以考虑使用ADMIN_MOVE_TABLE过程来在线迁移数据,它会自动完成重组和索引重建,但操作复杂度较高,需要提前评估日志空间和临时表空间。另一个常见策略是直接删除长期未使用的索引,这不仅能减轻写操作负担,还能减少优化器选择错误计划的概率。
判断哪些索引可以删除时,可以参考下面的查询,找出过去90天从未被使用的索引。不过要注意,唯一性索引即使不被查询使用,也可能承担约束功能,不能随意删除。对于普通索引,如果确认没有业务依赖,删除后可以立即释放存储空间并降低DML维护成本。
SELECT
INDSCHEMA,
INDNAME,
LASTUSED
FROM SYSCAT.INDEXES
WHERE TABSCHEMA = 'DB2INST1'
AND TABNAME = 'ORDERS'
AND LASTUSED IS NOT NULL
AND LASTUSED < CURRENT DATE - 90 DAYS;
在执行任何重建操作之前,务必在测试环境中验证执行计划是否如预期改变。有时候重建索引后,优化器仍然选择表扫描,这时需要进一步分析访问路径中的代价估算,而不是继续加大统计信息采样率或重复重建。对于核心业务表,建议在变更前备份索引定义和统计信息,以便出现问题时快速回退。重建索引虽然能够解决大部分失效问题,但如果SQL本身使用了函数或隐式转换,重建索引不会带来任何改善,必须同步修改SQL语句或创建表达式索引。
四、预防索引失效的运维建议
索引失效的根源往往是维护缺失。建立自动化的RUNSTATS任务是最基本也是最重要的一环。对于数据变化超过20%的大表,应该安排每日或每周的统计信息更新任务。对于变化极快的事实表,可以考虑在批量加载完成后立即执行RUNSTATS,而不是等到固定时间点。DB2的ADMIN_CMD存储过程可以很方便地嵌入脚本,实现定时调用。同时,定期使用REORGCHK检查表和索引的碎片情况,当碎片率超过一定阈值时触发在线重组,是避免索引物理失效的有效手段。
另一个容易被忽视的维护点是执行计划的稳定性。索引重建后,执行计划可能会因为统计信息变化而发生漂移,有时甚至从索引扫描变成全表扫描。可以通过DB2的执行计划基线或优化配置文件来固定关键SQL的访问路径,避免因为索引维护或统计信息更新导致性能波动。对于高频读写的小表,索引碎片通常不是主要问题,但统计信息的及时性仍然至关重要。对于大表,建议同时监控索引的LASTUSED、扫描次数和碎片率,将长期未使用且无约束意义的索引纳入清理清单。
最后,索引维护不能只依赖数据库管理员的人工判断。将索引使用情况、统计信息更新时间、重组状态等指标纳入监控平台,设置告警阈值,才能在索引失效初期就发现异常。索引不是越多越好,冗余索引会增加写操作的开销,消耗磁盘空间,还可能误导优化器。定期审查索引的必要性,结合业务查询模式调整索引设计,才能让DB2的索引始终处于健康状态。只有把重建、重组、统计更新和监控结合起来,才能从根本上减少索引失效带来的性能风险。

