外观
Oracle 分区表与分区索引
分区表与分区索引是 Oracle 数据库中用于管理大型表和索引的重要技术。它们可以提高查询性能、简化管理和维护、提高可用性,是处理海量数据的有效手段。
分区概述
分区的定义
分区是将大型表或索引分解为更小、更易管理的片段的过程。每个片段称为一个分区,分区之间是逻辑上相关的,但物理上是独立的。
分区的作用
- 提高查询性能:只需要访问相关分区,不需要扫描整个表
- 简化管理和维护:可以单独管理和维护每个分区
- 提高可用性:一个分区损坏不会影响其他分区
- 提高并行性能:可以并行处理多个分区
- 优化存储:可以将不同分区存储在不同的表空间和磁盘上
分区的类型
| 分区类型 | 特点 | 适用场景 |
|---|---|---|
| 范围分区(Range Partitioning) | 基于列的范围值进行分区 | 日期、时间、数值范围等 |
| 列表分区(List Partitioning) | 基于列的离散值进行分区 | 地区、状态、类型等 |
| 哈希分区(Hash Partitioning) | 基于哈希函数的值进行分区 | 随机分布数据,均衡负载 |
| 复合分区(Composite Partitioning) | 结合两种或多种分区类型 | 复杂业务场景 |
| 间隔分区(Interval Partitioning) | 自动根据范围创建分区 | 日期、时间等连续数据 |
| 引用分区(Reference Partitioning) | 基于父表的分区键进行分区 | 主从表关系 |
| 系统分区(System Partitioning) | 由用户指定分区 | 自定义分区策略 |
分区表管理
范围分区(Range Partitioning)
特点
- 基于列的范围值进行分区
- 每个分区包含一个连续的范围值
- 适合日期、时间、数值范围等
创建方法
sql
-- 按日期范围分区
CREATE TABLE sales (
sale_id NUMBER(10) PRIMARY KEY,
sale_date DATE NOT NULL,
product_id NUMBER(6) NOT NULL,
quantity NUMBER(8) NOT NULL,
amount NUMBER(12,2) NOT NULL
)
PARTITION BY RANGE (sale_date) (
PARTITION sales_q1_2024 VALUES LESS THAN (TO_DATE('2024-04-01', 'YYYY-MM-DD')) TABLESPACE sales_q1,
PARTITION sales_q2_2024 VALUES LESS THAN (TO_DATE('2024-07-01', 'YYYY-MM-DD')) TABLESPACE sales_q2,
PARTITION sales_q3_2024 VALUES LESS THAN (TO_DATE('2024-10-01', 'YYYY-MM-DD')) TABLESPACE sales_q3,
PARTITION sales_q4_2024 VALUES LESS THAN (TO_DATE('2025-01-01', 'YYYY-MM-DD')) TABLESPACE sales_q4,
PARTITION sales_future VALUES LESS THAN (MAXVALUE) TABLESPACE sales_future
);
-- 按数值范围分区
CREATE TABLE employees (
employee_id NUMBER(6) PRIMARY KEY,
first_name VARCHAR2(20),
last_name VARCHAR2(25) NOT NULL,
salary NUMBER(8,2) NOT NULL
)
PARTITION BY RANGE (salary) (
PARTITION p1 VALUES LESS THAN (5000) TABLESPACE emp_p1,
PARTITION p2 VALUES LESS THAN (10000) TABLESPACE emp_p2,
PARTITION p3 VALUES LESS THAN (15000) TABLESPACE emp_p3,
PARTITION p4 VALUES LESS THAN (MAXVALUE) TABLESPACE emp_p4
);列表分区(List Partitioning)
特点
- 基于列的离散值进行分区
- 每个分区包含一个或多个离散值
- 适合地区、状态、类型等
创建方法
sql
-- 按地区列表分区
CREATE TABLE customers (
customer_id NUMBER(10) PRIMARY KEY,
customer_name VARCHAR2(100) NOT NULL,
region VARCHAR2(20) NOT NULL,
status VARCHAR2(10) NOT NULL
)
PARTITION BY LIST (region) (
PARTITION p_north VALUES ('NORTH', 'NORTHEAST') TABLESPACE cust_north,
PARTITION p_south VALUES ('SOUTH', 'SOUTHEAST') TABLESPACE cust_south,
PARTITION p_west VALUES ('WEST') TABLESPACE cust_west,
PARTITION p_central VALUES ('CENTRAL') TABLESPACE cust_central,
PARTITION p_other VALUES (DEFAULT) TABLESPACE cust_other
);
-- 按状态列表分区
CREATE TABLE orders (
order_id NUMBER(10) PRIMARY KEY,
order_date DATE NOT NULL,
customer_id NUMBER(10) NOT NULL,
status VARCHAR2(15) NOT NULL
)
PARTITION BY LIST (status) (
PARTITION p_pending VALUES ('PENDING') TABLESPACE ord_pending,
PARTITION p_processing VALUES ('PROCESSING') TABLESPACE ord_processing,
PARTITION p_shipped VALUES ('SHIPPED') TABLESPACE ord_shipped,
PARTITION p_delivered VALUES ('DELIVERED') TABLESPACE ord_delivered,
PARTITION p_cancelled VALUES ('CANCELLED') TABLESPACE ord_cancelled
);哈希分区(Hash Partitioning)
特点
- 基于哈希函数的值进行分区
- 数据均匀分布在各个分区
- 适合随机分布数据,均衡负载
创建方法
sql
-- 按 customer_id 哈希分区
CREATE TABLE order_items (
order_item_id NUMBER(12) PRIMARY KEY,
order_id NUMBER(10) NOT NULL,
product_id NUMBER(6) NOT NULL,
quantity NUMBER(8) NOT NULL,
unit_price NUMBER(10,2) NOT NULL
)
PARTITION BY HASH (customer_id) PARTITIONS 8
STORE IN (ord_items_p1, ord_items_p2, ord_items_p3, ord_items_p4, ord_items_p5, ord_items_p6, ord_items_p7, ord_items_p8);
-- 自定义哈希分区表空间
CREATE TABLE products (
product_id NUMBER(6) PRIMARY KEY,
product_name VARCHAR2(100) NOT NULL,
category_id NUMBER(4) NOT NULL,
price NUMBER(10,2) NOT NULL
)
PARTITION BY HASH (product_id) (
PARTITION p1 TABLESPACE prod_p1,
PARTITION p2 TABLESPACE prod_p2,
PARTITION p3 TABLESPACE prod_p3,
PARTITION p4 TABLESPACE prod_p4
);复合分区(Composite Partitioning)
特点
- 结合两种或多种分区类型
- 适合复杂业务场景
- 支持范围-列表、范围-哈希、列表-哈希等组合
创建方法
sql
-- 范围-列表复合分区
CREATE TABLE sales_composite (
sale_id NUMBER(10) PRIMARY KEY,
sale_date DATE NOT NULL,
region VARCHAR2(20) NOT NULL,
product_id NUMBER(6) NOT NULL,
quantity NUMBER(8) NOT NULL,
amount NUMBER(12,2) NOT NULL
)
PARTITION BY RANGE (sale_date)
SUBPARTITION BY LIST (region) (
PARTITION p_q1_2024 VALUES LESS THAN (TO_DATE('2024-04-01', 'YYYY-MM-DD')) (
SUBPARTITION p_q1_2024_north VALUES ('NORTH', 'NORTHEAST'),
SUBPARTITION p_q1_2024_south VALUES ('SOUTH', 'SOUTHEAST'),
SUBPARTITION p_q1_2024_west VALUES ('WEST'),
SUBPARTITION p_q1_2024_central VALUES ('CENTRAL')
),
PARTITION p_q2_2024 VALUES LESS THAN (TO_DATE('2024-07-01', 'YYYY-MM-DD')) (
SUBPARTITION p_q2_2024_north VALUES ('NORTH', 'NORTHEAST'),
SUBPARTITION p_q2_2024_south VALUES ('SOUTH', 'SOUTHEAST'),
SUBPARTITION p_q2_2024_west VALUES ('WEST'),
SUBPARTITION p_q2_2024_central VALUES ('CENTRAL')
)
);
-- 范围-哈希复合分区
CREATE TABLE orders_composite (
order_id NUMBER(10) PRIMARY KEY,
order_date DATE NOT NULL,
customer_id NUMBER(10) NOT NULL,
amount NUMBER(12,2) NOT NULL
)
PARTITION BY RANGE (order_date)
SUBPARTITION BY HASH (customer_id) SUBPARTITIONS 4 (
PARTITION p_2024 VALUES LESS THAN (TO_DATE('2025-01-01', 'YYYY-MM-DD')),
PARTITION p_2025 VALUES LESS THAN (TO_DATE('2026-01-01', 'YYYY-MM-DD'))
);间隔分区(Interval Partitioning)
特点
- 自动根据范围创建分区
- 适合日期、时间等连续数据
- Oracle 11g 及以上版本支持
创建方法
sql
-- 按月份自动创建间隔分区
CREATE TABLE sales_interval (
sale_id NUMBER(10) PRIMARY KEY,
sale_date DATE NOT NULL,
product_id NUMBER(6) NOT NULL,
quantity NUMBER(8) NOT NULL,
amount NUMBER(12,2) NOT NULL
)
PARTITION BY RANGE (sale_date)
INTERVAL (NUMTOYMINTERVAL(1, 'MONTH')) (
PARTITION p_initial VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD'))
);
-- 按天自动创建间隔分区
CREATE TABLE logs_interval (
log_id NUMBER(12) PRIMARY KEY,
log_date DATE NOT NULL,
log_level VARCHAR2(10) NOT NULL,
message CLOB NOT NULL
)
PARTITION BY RANGE (log_date)
INTERVAL (NUMTODSINTERVAL(1, 'DAY')) (
PARTITION p_initial VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD'))
);引用分区(Reference Partitioning)
特点
- 基于父表的分区键进行分区
- 主从表关系,从表的分区与父表保持一致
- Oracle 11g 及以上版本支持
创建方法
sql
-- 创建父表(范围分区)
CREATE TABLE orders_ref (
order_id NUMBER(10) PRIMARY KEY,
order_date DATE NOT NULL,
customer_id NUMBER(10) NOT NULL,
status VARCHAR2(15) NOT NULL
)
PARTITION BY RANGE (order_date) (
PARTITION p_2024 VALUES LESS THAN (TO_DATE('2025-01-01', 'YYYY-MM-DD')),
PARTITION p_2025 VALUES LESS THAN (TO_DATE('2026-01-01', 'YYYY-MM-DD'))
);
-- 创建从表(引用分区)
CREATE TABLE order_items_ref (
order_item_id NUMBER(12) PRIMARY KEY,
order_id NUMBER(10) NOT NULL,
product_id NUMBER(6) NOT NULL,
quantity NUMBER(8) NOT NULL,
unit_price NUMBER(10,2) NOT NULL,
CONSTRAINT fk_order_items_ref FOREIGN KEY (order_id) REFERENCES orders_ref(order_id)
)
PARTITION BY REFERENCE (fk_order_items_ref);分区索引管理
局部分区索引(Local Partitioned Index)
特点
- 索引分区与表分区一一对应
- 每个索引分区只包含对应表分区的数据
- 维护简单,自动随表分区变化
- 适合与表分区相同的查询模式
创建方法
sql
-- 创建局部分区索引
CREATE INDEX sales_product_id_local_idx ON sales(product_id) LOCAL;
-- 创建唯一局部分区索引(必须包含分区键)
CREATE UNIQUE INDEX sales_sale_id_local_idx ON sales(sale_id, sale_date) LOCAL;全局分区索引(Global Partitioned Index)
特点
- 索引分区与表分区不一一对应
- 可以使用与表不同的分区键
- 维护复杂,表分区变化可能导致索引失效
- 适合全局查询
创建方法
sql
-- 创建全局分区索引
CREATE INDEX sales_customer_id_global_idx ON sales(customer_id)
GLOBAL PARTITION BY RANGE (customer_id) (
PARTITION p1 VALUES LESS THAN (1000),
PARTITION p2 VALUES LESS THAN (2000),
PARTITION p3 VALUES LESS THAN (3000),
PARTITION p4 VALUES LESS THAN (MAXVALUE)
);
-- 创建全局唯一索引
CREATE UNIQUE INDEX sales_order_id_global_idx ON sales(order_id)
GLOBAL PARTITION BY RANGE (order_id) (
PARTITION p1 VALUES LESS THAN (10000),
PARTITION p2 VALUES LESS THAN (20000),
PARTITION p3 VALUES LESS THAN (MAXVALUE)
);全局非分区索引(Global Nonpartitioned Index)
特点
- 整个索引作为一个整体,不分区
- 适合需要全局访问的场景
创建方法
sql
-- 创建全局非分区索引
CREATE INDEX sales_product_id_global_idx ON sales(product_id) GLOBAL;
-- 创建全局唯一非分区索引
CREATE UNIQUE INDEX sales_sale_id_global_idx ON sales(sale_id) GLOBAL;分区维护
分区的添加、删除、合并和拆分
sql
-- 添加分区
ALTER TABLE sales ADD PARTITION sales_2025 VALUES LESS THAN (TO_DATE('2026-01-01', 'YYYY-MM-DD')) TABLESPACE sales_2025;
-- 删除分区(会删除分区中的数据)
ALTER TABLE sales DROP PARTITION sales_2023;
-- 删除分区但保留数据
ALTER TABLE sales TRUNCATE PARTITION sales_2023;
-- 合并分区
ALTER TABLE sales MERGE PARTITIONS sales_q1_2024, sales_q2_2024 INTO PARTITION sales_h1_2024;
-- 拆分分区
ALTER TABLE sales SPLIT PARTITION sales_h1_2024 AT (TO_DATE('2024-04-01', 'YYYY-MM-DD')) INTO (PARTITION sales_q1_2024, PARTITION sales_q2_2024);
-- 交换分区(将分区与表交换)
CREATE TABLE sales_q1_2024_temp AS SELECT * FROM sales WHERE sale_date < TO_DATE('2024-04-01', 'YYYY-MM-DD');
ALTER TABLE sales EXCHANGE PARTITION sales_q1_2024 WITH TABLE sales_q1_2024_temp;
-- 移动分区(更改分区的表空间)
ALTER TABLE sales MOVE PARTITION sales_q1_2024 TABLESPACE new_sales_tbs;
-- 重命名分区
ALTER TABLE sales RENAME PARTITION sales_q1_2024 TO sales_q1_2024_old;分区索引的维护
sql
-- 重建局部分区索引
ALTER INDEX sales_product_id_local_idx REBUILD PARTITION sales_q1_2024;
-- 重建所有局部分区索引
ALTER INDEX sales_product_id_local_idx REBUILD PARTITION ALL;
-- 重建全局分区索引
ALTER INDEX sales_customer_id_global_idx REBUILD PARTITION p1;
-- 重建所有全局分区索引
ALTER INDEX sales_customer_id_global_idx REBUILD PARTITION ALL;
-- 修复失效的全局分区索引
ALTER INDEX sales_customer_id_global_idx REBUILD;
-- 查看索引分区状态
SELECT index_name, partition_name, status FROM dba_ind_partitions WHERE index_name = 'SALES_PRODUCT_ID_LOCAL_IDX';分区表与索引的监控
分区表监控
sql
-- 查看分区表信息
SELECT table_name, partitioning_type, subpartitioning_type, partition_count FROM dba_part_tables;
-- 查看分区信息
SELECT table_name, partition_name, high_value, tablespace_name, num_rows FROM dba_tab_partitions WHERE table_name = 'SALES';
-- 查看子分区信息
SELECT table_name, partition_name, subpartition_name, tablespace_name, num_rows FROM dba_tab_subpartitions WHERE table_name = 'SALES_COMPOSITE';
-- 查看分区表的统计信息
SELECT table_name, partition_name, last_analyzed, num_rows, blocks FROM dba_tab_statistics WHERE table_name = 'SALES';分区索引监控
sql
-- 查看分区索引信息
SELECT index_name, index_type, partitioning_type, subpartitioning_type, partition_count FROM dba_part_indexes;
-- 查看索引分区信息
SELECT index_name, partition_name, status, tablespace_name FROM dba_ind_partitions WHERE index_name = 'SALES_PRODUCT_ID_LOCAL_IDX';
-- 查看索引子分区信息
SELECT index_name, partition_name, subpartition_name, status, tablespace_name FROM dba_ind_subpartitions WHERE index_name = 'SALES_COMPOSITE_IDX';
-- 查看分区索引的统计信息
SELECT index_name, partition_name, last_analyzed, leaf_blocks, distinct_keys FROM dba_ind_statistics WHERE index_name = 'SALES_PRODUCT_ID_LOCAL_IDX';最佳实践
分区表最佳实践
- 选择合适的分区类型:根据业务需求选择合适的分区类型
- 合理设计分区键:选择查询频率高、基数适中的列作为分区键
- 控制分区数量:避免创建过多分区,建议每个分区大小在 1GB-10GB 之间
- 使用自动分区:对于日期、时间等连续数据,使用间隔分区
- 合理设置分区表空间:将不同分区存储在不同的表空间和磁盘上
- 定期收集统计信息:确保优化器生成最佳执行计划
- 使用本地索引:优先使用本地索引,维护简单,性能良好
- 避免全局索引失效:在删除或合并分区时,考虑使用 UPDATE GLOBAL INDEXES 子句
分区索引最佳实践
- 优先使用本地索引:维护简单,性能良好
- 合理使用全局索引:只在需要全局访问时使用
- 避免过度索引:每个索引都会增加 DML 操作的开销
- 定期重建索引:对于碎片化严重的索引,定期重建
- 使用压缩索引:减少索引的存储空间
- 监控索引状态:定期检查索引的状态,及时修复失效的索引
查询优化最佳实践
- 使用分区修剪:在查询中包含分区键,让 Oracle 只访问相关分区
- 避免跨分区查询:尽量减少跨多个分区的查询
- 使用并行查询:对于大型查询,使用并行查询可以提高性能
- 使用分区连接:当连接两个分区表时,使用相同的分区键可以提高性能
- 使用分区wise join:Oracle 11g 及以上版本支持分区wise join
版本差异
| 版本 | 特性 |
|---|---|
| Oracle 11g | 引入间隔分区、引用分区、虚拟列分区 |
| Oracle 12c | 引入系统分区、增强了间隔分区功能 |
| Oracle 18c | 引入混合分区、增强了分区维护功能 |
| Oracle 19c | 引入自动列表分区、增强了分区索引功能 |
| Oracle 23c | 增强了 JSON 数据的分区支持、优化了分区查询性能 |
常见问题(FAQ)
Q1: 如何选择合适的分区类型?
A1: 根据业务需求选择:
- 日期、时间等连续数据:范围分区或间隔分区
- 地区、状态等离散数据:列表分区
- 随机分布数据:哈希分区
- 复杂业务场景:复合分区
- 主从表关系:引用分区
Q2: 如何避免全局索引失效?
A2: 在删除或合并分区时,使用 UPDATE GLOBAL INDEXES 子句:
sql
ALTER TABLE sales DROP PARTITION sales_2023 UPDATE GLOBAL INDEXES;
ALTER TABLE sales MERGE PARTITIONS sales_q1_2024, sales_q2_2024 INTO PARTITION sales_h1_2024 UPDATE GLOBAL INDEXES;Q3: 如何查看分区表的统计信息?
A3: 使用以下查询:
sql
SELECT table_name, partition_name, last_analyzed, num_rows, blocks FROM dba_tab_statistics WHERE table_name = 'SALES';Q4: 如何收集分区表的统计信息?
A4: 使用 DBMS_STATS 包:
sql
-- 收集特定分区的统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME => 'HR', TABNAME => 'SALES', PARTNAME => 'SALES_Q1_2024', CASCADE => TRUE);
-- 收集所有分区的统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME => 'HR', TABNAME => 'SALES', CASCADE => TRUE);Q5: 如何监控分区表的使用情况?
A5: 使用以下查询:
sql
-- 查看分区的访问情况
SELECT table_name, partition_name, num_rows, blocks FROM dba_tab_partitions WHERE table_name = 'SALES';
-- 查看分区的 I/O 情况
SELECT * FROM v$partition_statistics WHERE object_name = 'SALES';Q6: 如何将普通表转换为分区表?
A6: 可以使用以下方法:
- 创建分区表,然后插入数据
- 使用 DBMS_REDEFINITION 包在线转换
- 使用交换分区功能
示例(使用 DBMS_REDEFINITION):
sql
-- 创建分区表
CREATE TABLE sales_partitioned (
sale_id NUMBER(10) PRIMARY KEY,
sale_date DATE NOT NULL,
product_id NUMBER(6) NOT NULL,
quantity NUMBER(8) NOT NULL,
amount NUMBER(12,2) NOT NULL
)
PARTITION BY RANGE (sale_date) (
PARTITION sales_2023 VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD')),
PARTITION sales_2024 VALUES LESS THAN (TO_DATE('2025-01-01', 'YYYY-MM-DD'))
);
-- 使用 DBMS_REDEFINITION 在线转换
EXEC DBMS_REDEFINITION.START_REDEF_TABLE('HR', 'SALES', 'SALES_PARTITIONED');
EXEC DBMS_REDEFINITION.FINISH_REDEF_TABLE('HR', 'SALES', 'SALES_PARTITIONED');相关命令速查
| 命令 | 用途 |
|---|---|
CREATE TABLE ... PARTITION BY | 创建分区表 |
ALTER TABLE ADD PARTITION | 添加分区 |
ALTER TABLE DROP PARTITION | 删除分区 |
ALTER TABLE TRUNCATE PARTITION | 清空分区数据 |
ALTER TABLE MERGE PARTITIONS | 合并分区 |
ALTER TABLE SPLIT PARTITION | 拆分分区 |
ALTER TABLE EXCHANGE PARTITION | 交换分区 |
ALTER TABLE MOVE PARTITION | 移动分区 |
CREATE INDEX ... LOCAL | 创建局部分区索引 |
CREATE INDEX ... GLOBAL | 创建全局分区索引 |
ALTER INDEX REBUILD PARTITION | 重建索引分区 |
DBMS_STATS.GATHER_TABLE_STATS | 收集分区表统计信息 |
SELECT * FROM dba_part_tables | 查看分区表信息 |
SELECT * FROM dba_tab_partitions | 查看分区信息 |
SELECT * FROM dba_part_indexes | 查看分区索引信息 |
SELECT * FROM dba_ind_partitions | 查看索引分区信息 |
总结
分区表与分区索引是 Oracle 数据库中用于管理大型表和索引的重要技术。它们可以提高查询性能、简化管理和维护、提高可用性,是处理海量数据的有效手段。DBA 应该根据实际业务需求选择合适的分区类型和索引类型,合理设计分区键,定期进行监控和维护,以确保分区表和分区索引的高效运行。通过遵循最佳实践,DBA 可以充分发挥分区技术的优势,提高数据库的性能和可靠性。
