Oracle 索引完整指南

Oracle 索引完整指南

一、索引类型分类

复制代码
Oracle 索引
│
├── 按结构分类
│   ├── B-Tree 索引(默认,最常用)
│   ├── Bitmap 索引(位图)
│   ├── Reverse Key 索引(反向键)
│   └── Hash 索引(仅 Cluster 中)
│
├── 按列数分类
│   ├── 单列索引
│   └── 复合索引(组合索引)
│
├── 按特殊用途分类
│   ├── Function-Based Index(函数索引)
│   ├── Unique Index(唯一索引)
│   ├── Domain Index(域索引,全文等)
│   ├── Invisible Index(不可见索引,11g+)
│   └── Virtual Index(虚拟索引,调优用)
│
└── 按物理存储分类
    ├── Local Index(局部分区索引)
    ├── Global Index(全局索引)
    └── Global Partitioned Index(全局分区索引)

二、各类索引详解与示例

1. B-Tree 索引(默认)

最通用的索引类型,适合高基数列(distinct 值多)。

sql 复制代码
-- 创建普通B-Tree索引
CREATE INDEX IDX_EMP_NAME ON EMPLOYEES(EMP_NAME);

-- 唯一索引
CREATE UNIQUE INDEX IDX_EMP_NO ON EMPLOYEES(EMP_NO);

-- 复合索引(最左前缀原则)
CREATE INDEX IDX_EMP_DEPT_HIRE ON EMPLOYEES(DEPT_ID, HIRE_DATE);

-- 在线创建(不锁表,推荐生产环境)
CREATE INDEX IDX_EMP_NAME ON EMPLOYEES(EMP_NAME) ONLINE;

-- 并行创建(大表加速)
CREATE INDEX IDX_EMP_NAME ON EMPLOYEES(EMP_NAME) PARALLEL 4 NOLOGGING;
-- 创建完成后改回串行+日志
ALTER INDEX IDX_EMP_NAME NOPARALLEL LOGGING;

适用场景:OLTP 系统,列值唯一性高(如主键、身份证号、邮箱)。


2. Bitmap 索引(位图索引)

为每个 distinct 值维护一个位图,适合低基数列

sql 复制代码
-- 创建位图索引
CREATE BITMAP INDEX IDX_EMP_GENDER ON EMPLOYEES(GENDER);

-- 位图连接索引(事实表与维度表的JOIN场景)
CREATE BITMAP INDEX IDX_BMJ_DEPT
ON SALES(D.DEPT_NAME)
FROM SALES S, DEPT D
WHERE S.DEPT_ID = D.DEPT_ID;

适用场景:数据仓库(DW)、性别/状态/省份等枚举列。

禁忌

  • ❌ OLTP 高并发更新(位图锁粒度大,会引发严重锁竞争)
  • ❌ 列值唯一性高的字段

3. 函数索引(Function-Based Index)

表达式或函数结果建索引。

sql 复制代码
-- 大小写不敏感查询
CREATE INDEX IDX_EMP_UPNAME ON EMPLOYEES(UPPER(EMP_NAME));

-- 这样的查询会走索引:
SELECT * FROM EMPLOYEES WHERE UPPER(EMP_NAME) = 'ZHANG SAN';

-- 日期截取
CREATE INDEX IDX_ORD_MONTH ON ORDERS(TRUNC(ORDER_DATE, 'MM'));

-- NVL 函数索引
CREATE INDEX IDX_EMP_MGR ON EMPLOYEES(NVL(MGR_ID, -1));

-- 必须确保查询优化器能用:
ALTER SESSION SET QUERY_REWRITE_ENABLED = TRUE;

适用场景:查询条件中固定使用某种函数转换。


4. 反向键索引(Reverse Key)

将索引列字节反转后存储,分散热点。

sql 复制代码
-- 创建反向键索引
CREATE INDEX IDX_ORD_ID_REV ON ORDERS(ORDER_ID) REVERSE;

-- 普通索引转反向
ALTER INDEX IDX_ORD_ID REBUILD REVERSE;

适用场景

  • 自增序列主键(避免索引右侧热块)
  • RAC 环境减少 GC 缓存等待

禁忌 :❌ 不能用于范围查询(BETWEEN<> 都失效)


5. 不可见索引(Invisible Index)⭐

11g 引入,优化器忽略它,但仍维护数据,调优神器。

sql 复制代码
-- 创建不可见索引(验证用)
CREATE INDEX IDX_EMP_TEST ON EMPLOYEES(EMP_NAME) INVISIBLE;

-- 让现有索引"下线"(不删除,仅不被使用)
ALTER INDEX IDX_EMP_NAME INVISIBLE;

-- 测试用某个索引(仅当前会话生效)
ALTER SESSION SET OPTIMIZER_USE_INVISIBLE_INDEXES = TRUE;

-- 验证 OK 后启用
ALTER INDEX IDX_EMP_NAME VISIBLE;

适用场景

  • 怀疑某索引无用,下线观察后再删除
  • 测试新索引而不影响生产 SQL

6. 复合索引(关键:列顺序)
sql 复制代码
-- 顺序很重要!
CREATE INDEX IDX_EMP_COMP ON EMPLOYEES(DEPT_ID, HIRE_DATE, SALARY);

-- ✅ 走索引(最左前缀)
SELECT * FROM EMPLOYEES WHERE DEPT_ID = 10;
SELECT * FROM EMPLOYEES WHERE DEPT_ID = 10 AND HIRE_DATE > SYSDATE-365;

-- ⚠️ 11g+ Index Skip Scan,前导列基数低时勉强可用
SELECT * FROM EMPLOYEES WHERE HIRE_DATE > SYSDATE-365;

-- ❌ 完全用不上索引
SELECT * FROM EMPLOYEES WHERE SALARY > 10000;

列顺序口诀

  • 等值条件在前,范围条件在后
  • 基数高(区分度高)的列在前
  • 常用过滤列在前

7. 分区索引
sql 复制代码
-- Local 局部索引(每个分区一个索引段,推荐)
CREATE INDEX IDX_SALES_LOCAL ON SALES(SALE_DATE) LOCAL;

-- Global 全局索引(一个索引覆盖所有分区)
CREATE INDEX IDX_SALES_GLOBAL ON SALES(CUSTOMER_ID) GLOBAL;

-- Global 分区索引
CREATE INDEX IDX_SALES_GP ON SALES(CUSTOMER_ID)
GLOBAL PARTITION BY RANGE(CUSTOMER_ID) (
    PARTITION P1 VALUES LESS THAN (10000),
    PARTITION P2 VALUES LESS THAN (20000),
    PARTITION P3 VALUES LESS THAN (MAXVALUE)
);

Local vs Global 选择

特性 Local 索引 Global 索引
索引列 通常包含分区键 不含分区键
分区维护 DROP 分区时索引自动清理 必须手动维护(UPDATE GLOBAL INDEXES)
查询性能 分区裁剪友好 跨分区查询更快
运维成本

三、索引常用 DDL 操作

sql 复制代码
-- ============== 创建相关 ==============
-- 指定表空间
CREATE INDEX IDX_NAME ON T1(C1) TABLESPACE TBS_IDX;

-- 在线、并行、不写日志(大表加速)
CREATE INDEX IDX_NAME ON T1(C1)
ONLINE PARALLEL 8 NOLOGGING;

-- 改回正常状态
ALTER INDEX IDX_NAME NOPARALLEL LOGGING;

-- ============== 维护相关 ==============
-- 重建(消除碎片)
ALTER INDEX IDX_NAME REBUILD;
ALTER INDEX IDX_NAME REBUILD ONLINE PARALLEL 4;

-- 移动表空间
ALTER INDEX IDX_NAME REBUILD TABLESPACE TBS_NEW;

-- 合并叶子块(轻量整理)
ALTER INDEX IDX_NAME COALESCE;

-- 收集统计信息
EXEC DBMS_STATS.GATHER_INDEX_STATS('SCOTT','IDX_NAME');

-- 启用/禁用
ALTER INDEX IDX_NAME UNUSABLE;     -- 失效
ALTER INDEX IDX_NAME REBUILD;       -- 重新可用

-- 改名
ALTER INDEX IDX_OLD RENAME TO IDX_NEW;

-- 删除
DROP INDEX IDX_NAME;
DROP INDEX IDX_NAME ONLINE;          -- 在线删除

四、查询索引信息的常用 SQL

sql 复制代码
-- 1. 查看某表所有索引
SELECT INDEX_NAME, INDEX_TYPE, UNIQUENESS,
       STATUS, VISIBILITY, TABLESPACE_NAME
FROM USER_INDEXES
WHERE TABLE_NAME = 'EMPLOYEES';

-- 2. 查看索引列
SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION, DESCEND
FROM USER_IND_COLUMNS
WHERE TABLE_NAME = 'EMPLOYEES'
ORDER BY INDEX_NAME, COLUMN_POSITION;

-- 3. 函数索引的表达式
SELECT INDEX_NAME, COLUMN_EXPRESSION
FROM USER_IND_EXPRESSIONS
WHERE TABLE_NAME = 'EMPLOYEES';

-- 4. 索引大小
SELECT SEGMENT_NAME, BYTES/1024/1024 AS MB
FROM USER_SEGMENTS
WHERE SEGMENT_TYPE = 'INDEX';

-- 5. 索引使用情况监控
ALTER INDEX IDX_NAME MONITORING USAGE;
-- 等待一段时间后查询:
SELECT * FROM V$OBJECT_USAGE WHERE INDEX_NAME = 'IDX_NAME';
ALTER INDEX IDX_NAME NOMONITORING USAGE;

-- 12c+ 用新视图(更准确)
SELECT * FROM DBA_INDEX_USAGE
WHERE NAME = 'IDX_NAME';

五、索引使用判断(执行计划)

sql 复制代码
-- 查看 SQL 是否走索引
EXPLAIN PLAN FOR
SELECT * FROM EMPLOYEES WHERE EMP_NAME = 'ZHANG SAN';

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

-- 关键词解读:
--  TABLE ACCESS FULL          → 全表扫描(未走索引)
--  INDEX RANGE SCAN            → 索引范围扫描 ✅
--  INDEX UNIQUE SCAN           → 唯一索引扫描 ✅
--  INDEX FULL SCAN             → 索引全扫描
--  INDEX FAST FULL SCAN        → 索引快速全扫描
--  INDEX SKIP SCAN             → 跳跃扫描
--  TABLE ACCESS BY INDEX ROWID → 通过索引回表 ✅

六、索引失效的常见原因

sql 复制代码
-- ❌ 1. 列上使用函数(除非有函数索引)
SELECT * FROM EMP WHERE UPPER(NAME) = 'TOM';   -- 失效
-- ✅ 解决:CREATE INDEX IDX_UPNAME ON EMP(UPPER(NAME));

-- ❌ 2. 隐式类型转换
-- ID 是 NUMBER 类型
SELECT * FROM EMP WHERE ID = '100';            -- 隐式转换可能失效
-- ✅ 解决:传入与列同类型的值

-- ❌ 3. NOT NULL / IS NULL(B-Tree 不索引 NULL)
SELECT * FROM EMP WHERE NAME IS NULL;          -- 失效
-- ✅ 解决:复合索引或函数索引 NVL(NAME, 'NA')

-- ❌ 4. 前模糊查询
SELECT * FROM EMP WHERE NAME LIKE '%TOM';      -- 失效
-- ✅ 解决:反转列+反向LIKE,或全文索引

-- ❌ 5. != 或 <> 通常不走索引
SELECT * FROM EMP WHERE STATUS <> 'ACTIVE';
-- ✅ 解决:改写为 IN/NOT IN,或拆分查询

-- ❌ 6. OR 连接的多列且只有部分有索引
SELECT * FROM EMP WHERE NAME = 'TOM' OR AGE = 30;
-- ✅ 解决:UNION ALL 拆分,或两列都建索引

-- ❌ 7. 优化器认为成本更高(数据量小、统计信息过期)
-- ✅ 解决:EXEC DBMS_STATS.GATHER_TABLE_STATS(...);

七、索引最佳实践

✅ 推荐做法
实践 说明
主键/外键必建索引 外键不建会导致父表DML锁表
WHERE/JOIN 高频列建索引 但需评估写入成本
ONLINE 创建 不阻塞 DML(生产环境必备)
大表用 PARALLEL + NOLOGGING 加速创建,完成后改回
定期 GATHER_INDEX_STATS 保持优化器决策准确
使用 INVISIBLE 灰度下线 删除前先观察 1~2 周
❌ 避免的做法
反模式 危害
一个表 10+ 个索引 DML 性能严重下降
在低基数列建 B-Tree 优化器倾向走全表
在高频更新列建 Bitmap 锁竞争 + 索引膨胀
索引列顺序不合理 索引利用率低
从不重建/分析索引 碎片严重,统计信息失真

八、索引维护脚本(生产实用)

sql 复制代码
-- 找出可能需要重建的索引(>20% 碎片)
SELECT INDEX_NAME,
       BLOCKS,
       DEL_LF_ROWS_LEN,
       LF_ROWS_LEN,
       ROUND(DEL_LF_ROWS_LEN/NULLIF(LF_ROWS_LEN,0)*100, 2) AS DEL_PCT
FROM (
    SELECT * FROM INDEX_STATS  -- 需先 ANALYZE INDEX ... VALIDATE STRUCTURE
);

-- 找出未使用的索引(开启监控后查询)
SELECT u.NAME, u.OWNER, u.TOTAL_ACCESS_COUNT, u.LAST_USED
FROM DBA_INDEX_USAGE u
WHERE u.TOTAL_ACCESS_COUNT = 0
  AND u.OWNER NOT IN ('SYS','SYSTEM');

-- 找出冗余索引(前缀重复)
SELECT a.TABLE_NAME, a.INDEX_NAME AS REDUNDANT,
       b.INDEX_NAME AS DOMINANT
FROM USER_IND_COLUMNS a, USER_IND_COLUMNS b
WHERE a.TABLE_NAME = b.TABLE_NAME
  AND a.INDEX_NAME != b.INDEX_NAME
  AND a.COLUMN_POSITION = 1
  AND b.COLUMN_POSITION = 1
  AND a.COLUMN_NAME = b.COLUMN_NAME
  AND (SELECT COUNT(*) FROM USER_IND_COLUMNS WHERE INDEX_NAME = a.INDEX_NAME)
    < (SELECT COUNT(*) FROM USER_IND_COLUMNS WHERE INDEX_NAME = b.INDEX_NAME);

一句话总结

索引是空间换时间,但绝非越多越好 :每个索引都会增加 DML 开销和存储成本。建索引前先用 EXPLAIN PLAN 验证是否被使用,建完后用 DBA_INDEX_USAGE 监控是否真的在工作,无用的索引应该果断 INVISIBLE 后再删除。

如果你有具体的业务场景或 SQL 性能问题,可以贴出来,我帮你分析适合什么类型的索引。

相关推荐
程序猿乐锅2 小时前
【Tilas|第三篇】多表SQL语句
数据库·经验分享·笔记·学习·mysql
Navicat中国3 小时前
使用 Navicat 导入向导导入 Excel 数据时,系统提示导入成功,表中也能看到数据,但行数统计显示为 0,这是什么原因?
数据库·excel·导入
gmaajt3 小时前
Golang怎么做国际化多语言_Golang i18n教程【核心】
jvm·数据库·python
折哥的程序人生 · 物流技术专研3 小时前
从“卡死”到“秒过”:WMS销售数据跨库回填的极限优化之旅
数据库·机器学习·oracle
李可以量化3 小时前
DeepSeek 量化交易实战:用标准化提示词模板实现 AI 辅助交易决策
大数据·数据库·人工智能
maqr_1103 小时前
CSS如何利用Sass定义全局阴影方案_通过变量实现统一CSS风格
jvm·数据库·python
m0_613856293 小时前
uni-app怎么做类似于美团的商家评价星级 uni-app五星评分组件制作【实战】
jvm·数据库·python
Irene19914 小时前
大数据开发语境下,SQL 模式名,映射关系 - - 概念理解
大数据·数据库·sql
顾随4 小时前
(二)kettle--输入与输出
javascript·数据库·kettle