71、Oracle索引与分区:B-tree索引、位图索引、表分区、索引分区
各位同学,今天我们来聊聊Oracle里两个非常重要的概念——索引和分区。说实话,这两个东西单独拎出来都不算难,但把它们放在一起用,很多新手就容易懵了。我在项目中见过不少因为索引分区没设计好,导致查询从毫秒级直接掉到分钟级的惨案。嗯,咱们今天就把它彻底讲透。
一、B-tree索引:最常用的索引结构
B-tree索引,说白了就是Oracle默认的索引类型。你创建一个普通索引,默认就是B-tree。它的结构像一棵倒着长的树,根节点、分支节点、叶子节点。叶子节点里存的是索引列的值和对应的ROWID。
我个人习惯把B-tree索引想象成一本字典的目录。你想查一个字,先翻目录,找到页码,再翻到正文。B-tree索引也是这个道理——先通过索引找到ROWID,再通过ROWID去表里拿数据。
适用场景:
- 高基数列(比如身份证号、订单号)
- 等值查询和范围查询
- 排序操作(ORDER BY)
举个例子,我在一个电商项目里,订单表有5000万行数据。按订单号查询,没索引的时候要全表扫描,大概40秒。加了个B-tree索引后,直接降到0.01秒。你想想看,这差距有多大。
-- 创建B-tree索引(默认就是B-tree)
CREATE INDEX idx_order_id ON orders(order_id);
-- 查看索引类型
SELECT index_name, index_type
FROM user_indexes
WHERE table_name = 'ORDERS';
小技巧:B-tree索引对NULL值不敏感。如果查询条件里经常用到IS NULL,可以考虑加一个函数索引,比如CREATE INDEX idx_null ON table(NVL(col, 0))。
二、位图索引:低基数列的利器
位图索引和B-tree完全不同。它用位图(bitmap)来表示每一行数据是否包含某个值。每个不同的值对应一个位图,位图里每一位代表一行数据。
为什么会用到位图索引?你想想看,如果一列只有几个值,比如性别(男/女)、状态(待支付/已支付/已取消),用B-tree索引其实效率不高。因为每个值对应的行数太多,索引扫描反而比全表扫描还慢。
我记得有一次帮一个金融客户优化报表查询。他们的交易状态列只有5个值,但表有2亿行。原来用的B-tree索引,查询某个状态的数据要跑3分钟。我改成位图索引后,同样的查询只要2秒。客户当场就愣住了。
-- 创建位图索引
CREATE BITMAP INDEX idx_status ON transactions(status);
-- 位图索引特别适合AND/OR组合查询
SELECT COUNT(*)
FROM transactions
WHERE status = '已支付'
AND channel = '线上';
注意:位图索引不适合高并发DML操作的表。因为一个位图索引条目可能锁住多行数据,并发更新时容易死锁。我曾经在一个OLTP系统里用过位图索引,结果生产环境频繁报锁等待,后来赶紧换掉了。
三、表分区:把大表拆成小表
表分区,说白了就是把一张大表物理上拆成多个小表,但逻辑上还是一张表。每个分区可以独立管理,比如单独备份、单独压缩、单独放在不同的表空间。
Oracle支持几种分区方式:
| 分区类型 | 说明 | 适用场景 |
|---|---|---|
| 范围分区 | 按列值的范围划分 | 时间字段(如按月份分区) |
| 列表分区 | 按列值的离散值划分 | 地区、状态等枚举值 |
| 哈希分区 | 按哈希算法均匀分布 | 数据分布均匀,无明确分区键 |
| 复合分区 | 先按一种方式分区,再按另一种方式子分区 | 大型数据仓库 |
我个人最常用的是范围分区,特别是按时间分区。比如日志表,按月份分区,查询最近一个月的数据时,Oracle会自动只扫描对应的分区,其他分区碰都不碰。这就是所谓的「分区裁剪」。
-- 按月份范围分区
CREATE TABLE logs (
log_id NUMBER,
log_date DATE,
message VARCHAR2(4000)
)
PARTITION BY RANGE (log_date) (
PARTITION p_2024_01 VALUES LESS THAN (TO_DATE('2024-02-01', 'YYYY-MM-DD')),
PARTITION p_2024_02 VALUES LESS THAN (TO_DATE('2024-03-01', 'YYYY-MM-DD')),
PARTITION p_2024_03 VALUES LESS THAN (TO_DATE('2024-04-01', 'YYYY-MM-DD')),
PARTITION p_max VALUES LESS THAN (MAXVALUE)
);
分区的好处:
- 查询性能提升(只扫描相关分区)
- 管理方便(可以单独TRUNCATE某个分区)
- 数据生命周期管理(旧分区可以归档或删除)
四、索引分区:让索引也分区
表分区了,索引怎么办?如果不做索引分区,索引还是全局的,那分区带来的性能优势就大打折扣了。索引分区有两种方式:
1. 局部索引(Local Index)
每个分区对应一个独立的索引。说白了就是「分区内的索引」。局部索引和表分区一一对应,每个分区只维护自己的索引。
-- 创建局部索引
CREATE INDEX idx_logs_date_local ON logs(log_date) LOCAL;
2. 全局索引(Global Index)
索引不分区,或者按另一种方式分区。全局索引覆盖所有分区数据,适合跨分区查询。
-- 创建全局索引
CREATE INDEX idx_logs_id_global ON logs(log_id) GLOBAL;
我在项目中遇到过一个问题:有个按时间分区的表,但业务经常按用户ID查询。如果只建局部索引,每次查询都要扫描所有分区,性能很差。后来我建了一个全局索引在用户ID上,查询速度直接起飞。
经验之谈:
- 如果查询条件经常包含分区键,用局部索引就够了
- 如果查询条件不包含分区键,考虑全局索引
- 全局索引在分区维护操作(如TRUNCATE分区)后需要重建,局部索引不受影响
五、知识体系结构图
下面这张图帮你理清索引和分区的核心关系:
六、实战建议与避坑指南
最后,我总结几条实战经验,希望能帮你少走弯路:
- 索引不是越多越好。每个索引都会增加DML操作的开销。我曾经在一个表上建了8个索引,结果插入数据慢得像蜗牛。后来删掉冗余索引,插入性能提升了10倍。
- 分区键的选择很关键。尽量选查询条件里经常出现的字段。如果分区键和查询条件不匹配,分区就失去了意义。
- 定期维护索引。B-tree索引在大量DML操作后会产生碎片,定期重建或整理索引能保持性能。我一般会在业务低峰期跑一个索引重建脚本。
- 位图索引慎用。除非你确定表是只读的或者极少更新,否则别在生产环境的OLTP系统里用位图索引。我在一个项目里吃过这个亏,后来全改成B-tree了。
曾经踩过的坑:有一次我帮客户做分区表迁移,忘了重建全局索引。结果数据导进去后,跨分区查询直接走全表扫描,性能惨不忍睹。从那以后,每次做分区维护操作,我都会检查索引状态:SELECT index_name, status FROM user_indexes WHERE status != 'VALID';
好了,关于Oracle索引和分区的内容就讲到这里。这些知识在实际工作中非常实用,特别是处理大表时,用好索引和分区,性能能提升好几个数量级。希望今天的分享对你有帮助。
公众号:蓝海资料掘金营,微信deep3321