Oracle 索引维护操作总结

Oracle 索引维护操作总结

一、索引的可见性(Visibility)

1.1 概念解析

索引的可见性(VISIBLE / INVISIBLE)是 Oracle 11g 引入的特性。一个不可见索引(Invisible Index) 仍然会被 DML 操作(INSERT、UPDATE、DELETE)维护,但优化器默认不会在查询中使用它。

核心用途:

  1. 在删除索引前进行"软删除"测试------将索引设为 INVISIBLE,观察业务是否出现性能下降,确认无影响后再真正删除。
  2. 创建索引,优化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)。

执行流程:

  1. 获取表的排他锁
  2. 全表扫描,构建索引
  3. 释放锁,索引生效

特点:速度较快(无需维护日志表),但会完全阻塞业务写入。

3.2 加 ONLINE(在线创建)
sql 复制代码
CREATE INDEX idx_name ON table_name(column_name) ONLINE;

锁机制:分三个阶段,仅在开始和结束阶段短暂加锁。

执行流程:

  1. 准备阶段:施加短暂的 SSX 级 TM 锁(毫秒级),阻止其他 DDL,允许 DML
  2. 扫描构建阶段 :以共享模式(S 锁)扫描表数据,构建索引结构。此阶段允许并发 DML ,所有变更通过临时日志表 SYS_JOURNAL_xxx 记录
  3. 最终切换阶段:施加短暂的 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。

执行流程:

  1. 获取表级排他锁
  2. 以 Index Fast Full Scan 方式读取原索引数据
  3. 排序后构建新索引
  4. 替换旧索引,释放锁

特点:速度较快(直接读索引块,数据量通常小于全表),但完全阻塞业务。

4.2 加 ONLINE(在线重建)
sql 复制代码
ALTER INDEX idx_name REBUILD ONLINE;

数据源 :扫描基表数据(Table Access Full),从表中重新提取索引列数据来构建新索引。

锁机制:与 CREATE INDEX ONLINE 类似,分三个阶段,仅在首尾短暂加锁。

执行流程:

  1. 准备阶段:短暂 SSX 锁,阻止 DDL
  2. 扫描构建阶段 :全表扫描,构建新索引。允许并发 DML,变更通过 SYS_JOURNAL_xxx 记录
  3. 最终切换阶段:短暂 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 ... --- 确认无用后删除
相关推荐
for_ever_love__1 小时前
MySQL Online DDL 完全指南:INSTANT/INPLACE/COPY 三种算法、MDL 锁事故与 gh-ost 实战
数据库·mysql·ddl·online·gh-ost·锁表·加字段
for_ever_love__1 小时前
MySQL 子查询、派生表与 CTE 优化:半连接、物化和递归实战
数据库·mysql·优化·as·子查询·with·cte
DBA_G2 小时前
GBase 8c分布式数据库客户端工具 gsql
数据库·分布式
用户5611610232732 小时前
量化笔记程序代码分析,逐笔数据、五档快照和分钟行情代码分析
数据库
房开民2 小时前
通俗总结 Octree(八叉树)
java·服务器·数据库
星云低代码开发平台2 小时前
AI 连接器运行健康度:指标、异常恢复和联调清单
大数据·运维·网络·数据库·人工智能·mcp
鱼宵2 小时前
LangChain4j 生产化改造:记忆落 Redis、向量落 ES,重启再也不丢
数据库·redis·elasticsearch·langchain4j
星云API技术笔记3 小时前
企业微信外部群机器人实战:多人同时咨询时,如何准确匹配消息与回复对象
数据库
ClouGence3 小时前
开源数据库管理工具 CloudDM 4.4.0 发布,新增 Kafka、SAP HANA 数据源
数据库·kafka