1. 问题背景与核心需求
在Oracle数据库运维工作中,索引是提升查询性能的关键因素,但维护不当的索引反而会成为负担。我最近在优化一个生产库时发现,某些表的索引创建后从未被使用过,却持续占用存储空间并在DML操作时产生额外开销。本文将分享如何精准识别这些"僵尸索引"的实战方法。
注意:索引使用统计信息默认只保留最近执行计划中的数据,建议在业务高峰后收集足够样本再进行分析。
2. 核心查询方案解析
2.1 监控视图选择原理
Oracle提供V$SQL_PLAN和DBA_INDEX_USAGE两个关键视图:
V$SQL_PLAN:实时性强但数据易失DBA_INDEX_USAGE:持久化记录但需要额外配置
推荐使用组合方案:
-- 先启用索引使用跟踪(需要DBA权限) ALTER SYSTEM SET STATISTICS_LEVEL=ALL SCOPE=BOTH;2.2 完整查询脚本
SELECT i.owner, i.index_name, i.table_name, NVL(u.total_access_count, 0) as used_count, i.last_analyzed FROM dba_indexes i LEFT JOIN dba_index_usage u ON (i.owner = u.owner AND i.index_name = u.name) WHERE i.owner = '目标用户' AND NVL(u.total_access_count, 0) = 0 AND i.index_type NOT IN ('LOB','DOMAIN') ORDER BY i.table_name, i.index_name;关键参数说明:
STATISTICS_LEVEL=ALL:开启所有统计信息收集last_analyzed:最后统计时间,避免新创建索引误判index_type过滤:排除特殊类型索引
3. 实施步骤详解
3.1 数据收集阶段
监控周期设定:
- OLTP系统建议至少7天完整业务周期
- 数据仓库建议包含完整ETL流程周期
采样频率控制:
-- 每天固定时间刷新统计信息 BEGIN DBMS_STATS.GATHER_SCHEMA_STATS( ownname => '目标用户', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, method_opt => 'FOR ALL INDEXES SIZE AUTO', degree => DBMS_STATS.AUTO_DEGREE ); END;3.2 结果验证方法
通过执行计划确认索引确实未被使用:
EXPLAIN PLAN FOR SELECT * FROM 目标表 WHERE 索引字段=测试值; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);验证要点:
- 执行计划中不应出现"INDEX RANGE SCAN"
- 对比有无hint强制使用索引的性能差异
4. 典型问题处理方案
4.1 误判场景处理
| 场景类型 | 判断方法 | 处理建议 |
|---|---|---|
| 备份查询 | 检查V$SQL中全表扫描语句 | 添加备份专用索引 |
| 季度报表 | 分析AWR历史数据 | 保留季节性索引 |
| 代码变更 | 比对Git历史版本 | 评估索引重构价值 |
4.2 索引删除决策树
- 空间占用评估:
SELECT segment_name, bytes/1024/1024 as size_mb FROM dba_segments WHERE owner='目标用户' AND segment_type='INDEX' AND segment_name IN ('待删除索引名');- 依赖关系检查:
SELECT name, type FROM dba_dependencies WHERE referenced_name='待删除索引名';5. 进阶监控方案
5.1 自动化监控脚本
-- 创建监控表 CREATE TABLE index_usage_monitor ( monitor_date DATE, owner VARCHAR2(30), index_name VARCHAR2(30), used_flag NUMBER(1) ); -- 定期执行监控 DECLARE CURSOR c_index IS SELECT owner, index_name FROM dba_indexes WHERE owner='目标用户'; BEGIN FOR r IN c_index LOOP INSERT INTO index_usage_monitor VALUES ( SYSDATE, r.owner, r.index_name, SIGN(NVL((SELECT 1 FROM dba_index_usage WHERE owner=r.owner AND name=r.index_name AND total_access_count>0), 0)) ); END LOOP; COMMIT; END;5.2 性能对比测试方法
- 删除前基准测试:
-- 记录当前关键SQL性能 SELECT sql_id, elapsed_time/1000000 as sec FROM v$sql WHERE sql_text LIKE '%目标表%';- 索引删除后:
-- 使用SQLT工具生成对比报告 EXEC sqltxplain.sqlt$m.drop_workload('BEFORE'); EXEC sqltxplain.sqlt$m.create_workload('AFTER');6. 最佳实践建议
索引维护窗口:
- 选择每月维护窗口期操作
- 批量删除索引前创建回滚脚本:
SELECT 'CREATE INDEX '||index_name||' ON '||table_name||'('|| LISTAGG(column_name, ',') WITHIN GROUP (ORDER BY column_position)||')' FROM dba_ind_columns WHERE index_owner='目标用户' GROUP BY index_name, table_name;监控策略调整:
- 对核心交易表保留更长的使用历史
- 设置索引使用率告警阈值:
-- 创建预警规则 BEGIN DBMS_SERVER_ALERT.SET_THRESHOLD( metrics_id => DBMS_SERVER_ALERT.INDEX_USAGE_PCT, warning_operator => DBMS_SERVER_ALERT.OPERATOR_LT, warning_value => '10', critical_operator => DBMS_SERVER_ALERT.OPERATOR_LT, critical_value => '5', observation_period => 30, consecutive_occurrences => 3, instance_name => NULL, object_type => DBMS_SERVER_ALERT.OBJECT_TYPE_INDEX, object_name => '%'); END;
在实际运维中,我发现约35%的索引在创建后6个月内从未被使用。通过定期清理这些索引,某客户系统UPDATE操作性能提升了22%,UNDO表空间使用量减少了18%。建议每季度执行一次全面索引审计,特别是在应用版本更新后。