Oracle 索引维护操作总结
一、索引的可见性(Visibility)
1.1 概念解析
索引的可见性(VISIBLE / INVISIBLE)是 Oracle 11g 引入的特性。一个不可见索引(Invisible Index) 仍然会被 DML 操作(INSERT、UPDATE、DELETE)维护,但优化器默认不会在查询中使用它。
核心用途:
- 在删除索引前进行"软删除"测试------将索引设为 INVISIBLE,观察业务是否出现性能下降,确认无影响后再真正删除。
- 创建索引,优化SQL语句之前,先创建为INVISIBLE,在会话级别进行调试,验证是否有效果
1.2 案例
(1)创建不可见索引
-- 创建测试表
CREATE TABLE test_visibility AS
SELECT LEVEL AS id, 'Name_' || LEVEL AS name
FROM DUAL CONNECT BY LEVEL <= 10000;
-- 创建不可见索引
CREATE INDEX idx_test_visibility ON test_visibility(id) INVISIBLE;
-- 收集统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TEST_VISIBILITY');
(2)查看索引可见性状态
-- 查询索引的可见性
set linesize 200 pagesize 999
col index_name format a20
SELECT INDEX_NAME, VISIBILITY
FROM USER_INDEXES
WHERE TABLE_NAME = 'TEST_VISIBILITY';
-- 输出示例:
-- INDEX_NAME VISIBILITY
-- IDX_TEST_VISIBILITY INVISIBLE
(3)验证不可见索引不会被使用
-- 执行查询,观察执行计划
EXPLAIN PLAN FOR SELECT * FROM test_visibility WHERE id = 9999;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- 预期结果:全表扫描(TABLE ACCESS FULL),索引未被使用
Plan hash value: 2609069169
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 14 | 10 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| TEST_VISIBILITY | 1 | 14 | 10 (0)| 00:00:01 |
-------------------------------------------------------------------------------------
(4)通过参数强制使用不可见索引
-- 临时让优化器考虑不可见索引(会话级别)
ALTER SESSION SET optimizer_use_invisible_indexes = TRUE;
ALTER SESSION SET OPTIMIZER_USE_INVISIBLE_INDEXES = TRUE;
-- 再次执行查询,现在会使用索引
EXPLAIN PLAN FOR SELECT * FROM test_visibility WHERE id = 9998;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- 预期结果:INDEX RANGE SCAN
Plan hash value: 2646675871
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 14 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TEST_VISIBILITY | 1 | 14 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX_TEST_VISIBILITY | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
(5)切换索引可见性
-- 将不可见索引改为可见
ALTER INDEX idx_test_visibility VISIBLE;
-- 再次查询状态
SELECT INDEX_NAME, VISIBILITY FROM USER_INDEXES WHERE TABLE_NAME = 'TEST_VISIBILITY';
-- VISIBILITY 变为 VISIBLE
-- 改回不可见
ALTER INDEX idx_test_visibility INVISIBLE;
二、索引的可用性(Usability)
2.1 概念解析
索引的可用性(USABLE / UNUSABLE)决定了索引是否可供优化器使用 以及是否被 DML 维护。
| 属性 | 优化器是否使用 | DML 是否维护 | 是否占用存储空间 |
|---|---|---|---|
| USABLE | 是 | 是 | 是 |
| UNUSABLE | 否 | 否 | 否(一般不分配段) |
UNUSABLE 状态的核心用途:
- 大批量数据加载:先置为 UNUSABLE,加载完成后重建,大幅提升加载性能
- 分区表维护:某些分区操作(如 DROP/TRUNCATE PARTITION)会导致全局索引失效,需重建
- 节省存储空间:UNUSABLE 的索引不占用存储空间
注意 :
UNUSABLE与INVISIBLE的关键区别在于------INVISIBLE仍被 DML 维护,而UNUSABLE完全不被维护。
2.2 案例
(1)创建 UNUSABLE 状态的索引
-- 创建测试表
CREATE TABLE test_unusable AS
SELECT LEVEL AS id, 'Data_' || LEVEL AS data
FROM DUAL CONNECT BY LEVEL <= 100000;
-- 创建 UNUSABLE 状态的索引
CREATE INDEX idx_test_unusable ON test_unusable(id) UNUSABLE;
-- 查看索引状态
SELECT INDEX_NAME, STATUS FROM USER_INDEXES WHERE TABLE_NAME = 'TEST_UNUSABLE';
-- STATUS: UNUSABLE
(2)验证 UNUSABLE 索引不占用空间
-- 查询段信息
SELECT SEGMENT_NAME, BYTES/1024 AS KB
FROM USER_SEGMENTS
WHERE SEGMENT_NAME = 'IDX_TEST_UNUSABLE';
-- 预期:无记录返回(UNUSABLE 索引不分配段)
(3)在大批量加载前将索引置为 UNUSABLE
-- 将索引置为不可用
ALTER INDEX idx_test_unusable UNUSABLE;
-- 执行大批量数据加载(此时索引不被维护,加载速度极快)
INSERT INTO test_unusable SELECT LEVEL + 100000, 'New_' || LEVEL
FROM DUAL CONNECT BY LEVEL <= 500000;
COMMIT;
-- 加载完成后重建索引
ALTER INDEX idx_test_unusable REBUILD;
-- 验证索引状态
SELECT INDEX_NAME, STATUS FROM USER_INDEXES WHERE TABLE_NAME = 'TEST_UNUSABLE';
-- STATUS: VALID
(4)分区表场景:创建部分分区 UNUSABLE 的本地索引
-- 创建分区表
CREATE TABLE sales_part (
sales_id NUMBER,
sales_date DATE,
amount NUMBER
)
PARTITION BY RANGE (sales_date) (
PARTITION p1 VALUES LESS THAN (DATE '2024-01-01'),
PARTITION p2 VALUES LESS THAN (DATE '2025-01-01'),
PARTITION p3 VALUES LESS THAN (DATE '2026-01-01')
);
-- 创建本地索引,指定 p1 分区为 UNUSABLE[reference:22]
CREATE INDEX idx_sales_part ON sales_part(sales_date)
LOCAL (
PARTITION p1 UNUSABLE,
PARTITION p2,
PARTITION p3
);
-- 查看各分区状态
col partition_name format a15
SELECT INDEX_NAME, PARTITION_NAME, STATUS
FROM USER_IND_PARTITIONS
WHERE INDEX_NAME = 'IDX_SALES_PART';
-- 输出结果
INDEX_NAME PARTITION_NAME STATUS
-------------------- --------------- --------
IDX_SALES_PART P1 UNUSABLE
IDX_SALES_PART P2 USABLE
IDX_SALES_PART P3 USABLE
三、CREATE INDEX创建索引(ONLINE 与ONLINE 的区别)
3.1 不加 ONLINE(离线创建)
sql
CREATE INDEX idx_name ON table_name(column_name);
锁机制 :在创建索引的整个过程中,对表施加 TM 表级排他锁(X 锁),阻塞所有 DML 操作(INSERT/UPDATE/DELETE)。
执行流程:
- 获取表的排他锁
- 全表扫描,构建索引
- 释放锁,索引生效
特点:速度较快(无需维护日志表),但会完全阻塞业务写入。
3.2 加 ONLINE(在线创建)
sql
CREATE INDEX idx_name ON table_name(column_name) ONLINE;
锁机制:分三个阶段,仅在开始和结束阶段短暂加锁。
执行流程:
- 准备阶段:施加短暂的 SSX 级 TM 锁(毫秒级),阻止其他 DDL,允许 DML
- 扫描构建阶段 :以共享模式(S 锁)扫描表数据,构建索引结构。此阶段允许并发 DML ,所有变更通过临时日志表
SYS_JOURNAL_xxx记录 - 最终切换阶段:施加短暂的 X 锁(毫秒级),将日志表中的增量变更合并到新索引,完成切换
特点:速度略慢(需要维护日志表),但不阻塞业务写入。
3.3 对比总结
| 维度 | 不加 ONLINE | 加 ONLINE |
|---|---|---|
| 锁级别 | 表级排他锁(全程) | 短暂 SSX/X 锁(首尾) |
| DML 是否阻塞 | 完全阻塞 | 允许并发 |
| DDL 是否阻塞 | 阻塞 | 阻塞 |
| 额外空间 | 无 | 需要日志表空间 |
| 创建速度 | 较快 | 略慢 |
| NOLOGGING模式 | 支持,最小化 Redo 日志生成 | 支持,最小化 Redo 日志生成 |
| PARALLEL模式 | 支持,多个进程同时扫描表/索引并构建索引结构 | 支持,多个进程同时扫描表/索引并构建索引结构 |
| 适用场景 | 维护窗口/小表 | 7×24 生产环境 |
3.4 create index创建索引,生产环境推荐写法
生产环境,对于大表的索引创建,建议如下:
-- 设置会话级参数优化排序性能
ALTER SESSION SET workarea_size_policy = MANUAL;
ALTER SESSION SET sort_area_size = 10737418200; -- 10GB,加快索引创建时排序的内存使用大小
ALTER SESSION SET sort_area_retained_size = 10737418200;
ALTER SESSION SET db_file_multiblock_read_count = 128;
-- online+nologging+parallel 三合一方式,加快索引的创建,并且影响最小化!
CREATE INDEX idx_orders_customer ON orders(customer_id) PARALLEL 8 NOLOGGING ONLINE;
-- 创建完成后必须恢复默认并行度和日志模式
ALTER INDEX idx_orders_customer NOPARALLEL LOGGING;
备注:或者先设置足够大的pga_aggregate_target,索引创建完毕后(加快索引创建时排序的内存使用大小),再修改回来
alter system set pga_aggregate_target=80G;
3.5 案例
sql
-- 创建测试表
CREATE TABLE orders (
order_id NUMBER PRIMARY KEY,
customer_id NUMBER,
order_date DATE,
amount NUMBER
);
-- 插入10万条测试数据
INSERT INTO orders
SELECT LEVEL, MOD(LEVEL, 5000) + 1,
SYSDATE - MOD(LEVEL, 365),
ROUND(DBMS_RANDOM.VALUE(100, 10000), 2)
FROM DUAL
CONNECT BY LEVEL <= 100000;
COMMIT;
-- 收集统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'ORDERS', CASCADE => TRUE);
-- ========== 案例1:不加 ONLINE 创建索引 ==========
-- 在会话1中执行(会阻塞DML)
CREATE INDEX idx_orders_cust_offline ON orders(customer_id);
-- 在会话2中尝试DML(会被阻塞,直到索引创建完成)
-- INSERT INTO orders VALUES (100001, 1, SYSDATE, 500); -- 挂起等待
-- 验证索引状态
SELECT index_name, status, visibility
FROM user_indexes
WHERE table_name = 'ORDERS';
-- ========== 案例2:加 ONLINE 创建索引 ==========
-- 在会话1中执行(不阻塞DML)
CREATE INDEX idx_orders_cust_online ON orders(customer_id) ONLINE;
-- 在会话2中同时执行DML(正常完成,不被阻塞)
INSERT INTO orders VALUES (100001, 1, SYSDATE, 500);
COMMIT;
-- 验证:观察临时日志表
SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME LIKE 'SYS_JOURNAL%';
-- 创建完成后,日志表自动删除
-- 验证索引状态
SELECT index_name, status, visibility, num_rows
FROM user_indexes
WHERE table_name = 'ORDERS';
四、ALTER INDEX REBUILD重建索引(ONLINE 与ONLINE 的区别)
不要盲目定期重建索引:Oracle B-Tree 索引具有自我平衡能力,仅在碎片率超过 20% 或索引高度异常时才考虑重建。
4.1 不加 ONLINE(离线重建)
sql
ALTER INDEX idx_name REBUILD;
数据源 :直接扫描原索引块(Index Fast Full Scan),读取现有索引数据来构建新索引。
锁机制:全程对表施加排他锁,阻塞所有 DML 和 DDL。
执行流程:
- 获取表级排他锁
- 以 Index Fast Full Scan 方式读取原索引数据
- 排序后构建新索引
- 替换旧索引,释放锁
特点:速度较快(直接读索引块,数据量通常小于全表),但完全阻塞业务。
4.2 加 ONLINE(在线重建)
sql
ALTER INDEX idx_name REBUILD ONLINE;
数据源 :扫描基表数据(Table Access Full),从表中重新提取索引列数据来构建新索引。
锁机制:与 CREATE INDEX ONLINE 类似,分三个阶段,仅在首尾短暂加锁。
执行流程:
- 准备阶段:短暂 SSX 锁,阻止 DDL
- 扫描构建阶段 :全表扫描,构建新索引。允许并发 DML,变更通过
SYS_JOURNAL_xxx记录 - 最终切换阶段:短暂 X 锁,合并增量变更,替换旧索引
特点:速度略慢(需全表扫描 + 维护日志表),但不阻塞业务写入。
4.3 对比总结
| 维度 | 不加 ONLINE | 加 ONLINE |
|---|---|---|
| 数据源 | 原索引块(Index Fast Full Scan) | 基表数据(Table Access Full) |
| 锁级别 | 表级排他锁(全程) | 短暂 SSX/X 锁(首尾) |
| DML 是否阻塞 | 完全阻塞 | 允许并发 |
| 额外空间 | 约 1 倍索引大小 | 约 2 倍索引大小(新旧并存+日志表) |
| 重建速度 | 较快 | 略慢 |
| 适用场景 | 维护窗口 | 7×24 生产环境 |
**特别注意:**如果索引存在逻辑问题(如逻辑损坏),不应该 使用普通的离线 REBUILD(不带 ONLINE),因为它依赖原索引数据来重建新索引,可能会将问题带入新索引,必须用 REBUILD ONLINE 或 DROP + CREATE。两者扫描数据的来源完全不同,如下:
普通 REBUILD 的工作方式:
原索引块 ──Index Fast Full Scan──→ 排序 ──→ 新索引结构
REBUILD ONLINE 的工作方式:
基表数据 ──Table Access Full──→ 提取索引列+ROWID ──→ 排序 ──→ 新索引结构
4.4 alter index rebuild重建索引,生产环境推荐写法
生产环境,对于大表的索引创建,建议如下:
-- 设置会话级参数优化排序性能
ALTER SESSION SET workarea_size_policy = MANUAL;
ALTER SESSION SET sort_area_size = 10737418200; -- 10GB,加快索引创建时排序的内存使用大小
ALTER SESSION SET sort_area_retained_size = 10737418200;
ALTER SESSION SET db_file_multiblock_read_count = 128;
-- online+nologging+parallel 三合一方式,加快索引的重建,并且影响最小化!
ALTER INDEX idx_orders_customer REBUILD PARALLEL 8 NOLOGGING ONLINE;
-- 创建完成后必须恢复默认并行度和日志模式
ALTER INDEX idx_orders_customer NOPARALLEL LOGGING;
备注:或者先设置足够大的pga_aggregate_target,索引创建完毕后(加快索引创建时排序的内存使用大小),再修改回来
alter system set pga_aggregate_target=80G;
4.5 案例
sql
-- 准备:先制造索引碎片
-- 删除一半数据,制造碎片
DELETE FROM orders WHERE MOD(order_id, 2) = 0;
COMMIT;
-- 查看碎片率
ANALYZE INDEX idx_orders_cust_online VALIDATE STRUCTURE;
SELECT name, lf_rows, del_lf_rows,
ROUND((del_lf_rows / lf_rows) * 100, 2) AS frag_pct
FROM index_stats;
NAME LF_ROWS DEL_LF_ROWS FRAG_PCT
------------------------- ---------- ----------- ----------
IDX_ORDERS_CUST_ONLINE 100001 50001 50 --碎片率50%
-- ========== 案例1:不加 ONLINE 重建 ==========
-- 在会话1中执行
ALTER INDEX idx_orders_cust_offline REBUILD;
-- 在会话2中尝试DML(会被阻塞)
-- UPDATE orders SET amount = 999 WHERE order_id = 1; -- 挂起等待
-- ========== 案例2:加 ONLINE 重建 ==========
-- 在会话1中执行
ALTER INDEX idx_orders_cust_online REBUILD ONLINE;
-- 在会话2中同时执行DML(正常完成)
UPDATE orders SET amount = 999 WHERE order_id = 1;
COMMIT;
-- 重建完成后验证碎片率
ANALYZE INDEX idx_orders_cust_online VALIDATE STRUCTURE;
SELECT name, lf_rows, del_lf_rows,
ROUND((del_lf_rows / lf_rows) * 100, 2) AS frag_pct
FROM index_stats;
NAME LF_ROWS DEL_LF_ROWS FRAG_PCT
------------------------- ---------- ----------- ----------
IDX_ORDERS_CUST_ONLINE 50000 0 0 ----碎片率降为0
五、其他索引维护操作
5.1 COALESCE:合并叶块碎片
ALTER INDEX ... COALESCE 将相邻叶块中的索引条目合并,释放空闲叶块。与 REBUILD 不同,COALESCE 不重建索引树,仅整理叶块层面的碎片,速度更快、开销更小。
sql
-- 合并索引叶块碎片
ALTER INDEX idx_orders_cust_online COALESCE;
对比 REBUILD:
| 维度 | COALESCE | REBUILD |
|---|---|---|
| 操作范围 | 仅叶块 | 整棵索引树 |
| 速度 | 快 | 慢 |
| 能否改变索引高度 | 不能 | 能 |
| 能否改变存储参数 | 不能 | 能 |
| 能否跨表空间迁移 | 不能 | 能 |
5.2 MONITORING USAGE:监控索引使用情况
用于判断某个索引是否真正被查询使用,帮助识别无用索引。
sql
-- 开启索引使用监控
ALTER INDEX idx_orders_cust_online MONITORING USAGE;
-- 执行一些查询
SELECT * FROM orders WHERE customer_id = 100;
SELECT * FROM orders WHERE order_id = 5000;
-- 查看监控结果
SELECT index_name, table_name, monitoring, used, start_monitoring
FROM v$object_usage
WHERE index_name = 'IDX_ORDERS_CUST_ONLINE';
-- 关闭监控
ALTER INDEX idx_orders_cust_online NOMONITORING USAGE;
-- 再次查看结果(关闭后仍可查询最后一次的监控结果)
SELECT index_name, used, start_monitoring, end_monitoring
FROM v$object_usage
WHERE index_name = 'IDX_ORDERS_CUST_ONLINE';
INDEX_NAME USE START_MONITORING END_MONITORING
-------------------- --- ------------------- -------------------
IDX_ORDERS_CUST_ONLI NO 08/17/2026 16:35:14 08/17/2026 16:35:35
NE
5.3 RENAME:重命名索引
sql
-- 重命名索引
ALTER INDEX idx_orders_cust_online RENAME TO idx_orders_customer;
-- 验证
SELECT index_name FROM user_indexes WHERE table_name = 'ORDERS';
5.4 SHRINK SPACE:收缩索引段
与 COALESCE 类似,但可以释放段的高水位线以下的空闲空间,真正缩小索引段的物理大小。
sql
-- 收缩索引段
ALTER INDEX idx_orders_customer SHRINK SPACE;
-- 查看收缩前后的段大小
SELECT segment_name, bytes / 1024 AS size_kb
FROM user_segments
WHERE segment_name = 'IDX_ORDERS_CUSTOMER';
5.5 索引压缩
索引键压缩 (Index Key Compression):前缀压缩
-- 压缩前2列作为前缀
CREATE INDEX idx_emp_dept_sal ON employees(department_id, job_id, salary) COMPRESS 2;
-- 重建现有索引并启用压缩
ALTER INDEX idx_emp_dept_sal REBUILD COMPRESS 2;
高级索引压缩 (Advanced Index Compression)
-- 使用高级压缩(默认HIGH级别)
CREATE INDEX idx_emp_dept_sal ON employees(department_id, job_id, salary) COMPRESS ADVANCED;
-- 显式指定LOW级别
CREATE INDEX idx_emp_dept_sal ON employees(department_id, job_id, salary) COMPRESS ADVANCED LOW;
-- 重建现有索引并启用高级压缩
ALTER INDEX idx_emp_dept_sal REBUILD COMPRESS ADVANCED HIGH;
5.6 索引列与空值
#### 方法一:复合索引(添加常量列)
-- 创建包含常量的复合索引
CREATE INDEX idx_people_idnum ON xx_people(id_number, 1);
或者
-- 创建包含常量的复合索引
CREATE INDEX idx_people_idnum ON xx_people(1,id_number);
#### 方法二:使用虚拟列(Virtual Column)的方式
-- 使用虚拟列(Oracle 11g+)
ALTER TABLE t ADD (null_marker GENERATED ALWAYS AS (1) VIRTUAL);
CREATE INDEX idx_t ON t(null_marker, column_name);
5.7 DROP INDEX:删除索引
sql
-- 直接删除
DROP INDEX idx_orders_customer;
-- 验证
SELECT index_name FROM user_indexes WHERE table_name = 'ORDERS';
六、维护操作总结
| 操作 | 语法 | 是否阻塞 DML | 典型场景 |
|---|---|---|---|
| 创建索引(离线) | CREATE INDEX ... |
阻塞 | 维护窗口/小表 |
| 创建索引(在线) | CREATE INDEX ... ONLINE |
不阻塞 | 生产环境大表 |
| 重建索引(离线) | ALTER INDEX ... REBUILD |
阻塞 | 维护窗口 |
| 重建索引(在线) | ALTER INDEX ... REBUILD ONLINE |
不阻塞 | 生产环境 |
| 合并叶块 | ALTER INDEX ... COALESCE |
阻塞 | 轻度碎片整理 |
| 收缩段空间 | ALTER INDEX ... SHRINK SPACE |
阻塞 | 释放段空间 |
| 设为不可见 | ALTER INDEX ... INVISIBLE |
--- | 删除前测试 |
| 设为不可用 | ALTER INDEX ... UNUSABLE |
--- | 批量加载前禁用 |
| 监控使用 | ALTER INDEX ... MONITORING USAGE |
--- | 识别无用索引 |
| 重命名 | ALTER INDEX ... RENAME TO |
--- | 命名规范化 |
| 删除 | DROP INDEX ... |
--- | 确认无用后删除 |