12C 之分区表
思想:
在计算机领域有两个非常著名的结论: 1 分而治之 2 加一层
1 什么是分区表
物理上分区表是将一个大表按照一定规则分解为几个小表,这些小表称为表分区,表分区物理的独立。
逻辑上分区表对应用透明,数据访问哪个分区由SQL语句的特点和存储引擎来决定,用户感受不到表被分区。
2 用分区表的目的。
(1) 提高数据可用性 :分区数据损坏,不影响其他分区
(2) 减少DBA的管理负担:表重组 以及回收碎片 分区操作比大表操作负担小
(3)提高查询性能(OLAP): 通过分区裁剪功能,读取更少的数据
(4)减少高并发环境下OLTP系统上段的争用: 通过分区打散数据 (表段数据,索引段数据)
1 2 4 是在OLTP环境下分区的优势所在,而对于查询性能的提升则几乎没有影响,因为OLTP系统都是很短的小事务,基本都是通过索引返回数据。分区剪裁对他不会发挥作用。虽然索引也可以分区,但是扫描小索引获得的性能提升可以忽略,更多是高可用,和减少管理负担的考虑。
3 分区优势实验测试
1 提高可用性
创建表空间
create tablespace p1
datafile '/u01/app/oracle/oradata/prod/p1.dbf' size 10m
autoextend on next 1m;
create tablespace p2
datafile '/u01/app/oracle/oradata/prod/p2.dbf' size 10m
autoextend on next 1m;
创建分区表
create table emp_hash
(empno number,
ename varchar2(20)
)
partition by hash(empno)
(partition part_1 tablespace p1,
partition part_2 tablespace p2
)
/
create table emp_hash
(empno number,
ename varchar2(20)
)
partition by hash(empno)
(partition part_1 ,
partition part_2
)
/
插入数据,数据自动分区
insert into emp_hash select empno,ename from scott.emp;
commit
查询分区数据,验证表空间离线后,分区技术对分区表访问的高可用性
select * from emp_hash partition(part_1);
select * from emp_hash partition(part_2);
alter tablespace p2 offline
总结: 分区表的高可用:访问高可用 和 恢复高可用
2 减少管理压力:相比于大对象的管理 小对象更容易管理,体现在速度更快、占用更少资源
比如重建索引(rebuild),重新组织表(move)
如果索引分区,使得索引重建速度更快,占用更少存储资源。对于索引重建过程中的系统故障,也只会影响部分分区索引的这部分数据,使得重建工作失败的影响最小。
create index
比如解决行迁移问题,需要move表,此时按照每个分区做move操作使得操作更快,减少占用的存储资源,move操作的过程只会影响一个分区表。
例子,创建两个表空间,两个表,一个非分区表,一个分区表,分别放在大小相同的表空间中。
创建表空间
create tablespace big1
datafile '/u01/app/oracle/oradata/orcl/big1.dbf' size 8m
创建表
create table big_t1 tablespace big1 as select object_id,object_name,object_type,created from dba_objects;
查询该表占用的空间
select segment_name,bytes/1024/1024 from dba_segments where segment_name='BIG_T1';
SEGMENT_NAME BYTES/1024/1024
----------------------------------------------------------------
BIG_T1 5
如果此时将这个表move,会需要另外5M的存储空间,否则会失败。
下面我们创建分区表。我们创建4个分区。
先创建一个表空间,用于存储4个分区表
create tablespace big2
datafile '/u01/app/oracle/oradata/orcl/big2.dbf' size 45m
/
CREATE TABLE BIG_T3(
OBJECT_ID ,
OBJECT_NAME ,
OBJECT_TYPE,
CREATED )
partition by hash(object_id)
(
partition part_1 tablespace big3,
partition part_2 tablespace big3,
partition part_3 tablespace big3,
partition part_4 tablespace big3
)
as select OBJECT_ID,OBJECT_NAME,OBJECT_TYPE,CREATED from dba_objects;
/
3 OLTP环境下分区的查询性能分析
分析全表扫描的过程,和索引找数过程。
通过创建唯一索引,看性能提升,分析提升原因(减少了物理,逻辑读)
create unique index IDX_BIGT1_ID on big_t1(object_id);
set autotr on;
select * from big_t1 where object_id=70000;
分区表创建全局唯一索引
性能与非分区表的唯一索引查找性能一样。
create unique index IDX_BIGT2_ID on big_t2(object_id);
set autotr on;
select * from big_t2 where object_id=70000;
数据仓库系统/决策支持系统而言
分区技术很好的管理工具,也可以加快处理速度。
4 分区技术在OLTP系统中对消除争用的作用
通过哈希分区,打散发生争用的段(表段、索引段)
分区概述:
三种核心分区表
1 范围分区
例子
create table range_t
( id number,
name varchar2(20),
hiredate date)
partition by range (hiredate)
(
partition part_1 values less than (to_date('2018-01-01','yyyy-mm-dd')),
partition part_2 values less than (to_date('2019-01-01','yyyy-mm-dd')),
partition part_3 values less than (maxvalue))
/
插入数据,验证
SQL>insert into range_t values(1,'Mark',to_date('2017-03-01','yyyy-mm-dd'));
SQL> insert into range_t values(2,'Tom',to_date('2018-03-02','yyyy-mm-dd'));
SQL> insert into range_t values(3,'Larry',to_date('2019-02-05','yyyy-mm-dd'));
分区查询
SQL> select * from range_t partition(part_1);
ID NAME HIREDATE
---------- -------------------- ---------
1 Mark 01-MAR-17
SQL> select * from range_t partition(part_2);
ID NAME HIREDATE
---------- -------------------- ---------
2 Tom 02-MAR-18
SQL> select * from range_t partition(part_3);
ID NAME HIREDATE
---------- -------------------- ---------
3 Larry 05-FEB-19
可以看到数据都到各自分区去了。
生产中一般讲时间字段作为范围分区的分区键,可以根据时间将数据分区存储,易于管理,提高可用性。
2 列表分区
例子
create table list_t
(city varchar2(30),
data varchar2(30))
partition by list(city)
(partition part_1 values('A','B','C'),
partition part_2 values('D','E','F'))
/
insert into list_t values('A','level1');
insert into list_t values('B','level2');
insert into list_t values('F','level6');
使用场景: 需要按照列表类型数据分区管理
增加分区
alter table list_t
add partition
part_3 values(default)
/
3 哈希分区
例子
create table hash_t
(id number,
name varchar2(23))
partition by hash(id)
(partition part_1 tablespace p1,
partition part_2 tablespace p2)
/
插入数据:
SQL> insert into hash_t select empno,ename from scott.emp;
验证数据是否应被哈希到各自分区
SQL> select * from hash_t partition (part_1);
ID NAME
---------- -----------------------
7369 SMITH
7499 ALLEN
7654 MARTIN
7698 BLAKE
7782 CLARK
7839 KING
7876 ADAMS
7934 MILLER
8 rows selected.
SQL> select * From hash_t partition (part_2);
ID NAME
---------- -----------------------
7521 WARD
7566 JONES
7788 SCOTT
7844 TURNER
7900 JAMES
7902 FORD
6 rows selected.
在我们读数据时刻,Oracle会自动实现分区裁剪
注意:hash分区的分区个数为2的幂值
如何增加一个hash分区?
alter table hash_t add partition part_3;
使用场景: 打散数据,消除热块争用。
收集分区表的统计信息
单个分区收集
SQL> exec dbms_stats.gather_table_stats(user,'HASH_T','PART_1');
整个分区表收集
SQL> exec dbms_stats.gather_table_stats(user,'HASH_T');
创建间隔分区
create table interval_t
( data varchar2(30),
ts timestamp)
partition by range(ts)
interval (numtoyminterval(1 , 'month'))
store in (users,example)
(partition p0 values less than (to_date('2018-02-01', 'yyyy-mm-dd'))
)
/
插入数据
SQL> insert into interval_t values('test',to_date('2018-05-23','yyyy-mm-dd'));
SQL> insert into interval_t values('test',to_date('2018-06-02','yyyy-mm-dd'));
SQL> insert into interval_t values('test',to_date('2018-01-06','yyyy-mm-dd'));
SQL> insert into interval_t values('test',to_date('2018-03-01','yyyy-mm-dd'));
SQL> insert into interval_t values('test',to_date('2018-04-01','yyyy-mm-dd'));
SQL> insert into interval_t values('test',to_date('2018-08-01','yyyy-mm-dd'));
查询自动分区
SQL> select table_name,partition_name,tablespace_name,high_value from user_tab_partitions where table_name='INTERVAL_T';
TABLE_NAME PARTITION_NAME TABLESPACE_NAME HIGH_VALUE
-------------------- -------------------- -------------------- -------------------------------
INTERVAL_T P0 SYSTEM TIMESTAMP' 2018-02-01 00:00:00'
INTERVAL_T SYS_P421 USERS TIMESTAMP' 2018-06-01 00:00:00'
INTERVAL_T SYS_P422 EXAMPLE TIMESTAMP' 2018-07-01 00:00:00'
INTERVAL_T SYS_P441 EXAMPLE TIMESTAMP' 2018-09-01 00:00:00'
INTERVAL_T SYS_P442 EXAMPLE TIMESTAMP' 2018-05-01 00:00:00'
INTERVAL_T SYS_P443 USERS TIMESTAMP' 2018-04-01 00:00:00'
符合分区,常用的分区模式
列表分区,范围分区以及哈希分区的组合,可以有9种组
1 范围-哈希分区
create table composite_examp
(id number,
name varchar2(20),
hiredate date)
partition by range(hiredate)
subpartition by hash(id) subpartitions 2
(
partition part_1 values less than (to_date('2017-01-01','yyyy-mm-dd'))
(subpartition part_1_sub_1,
subpartition part_1_sub_2),
partition part_2 values less than (to_date('2018-01-01','yyyy-mm-dd'))
(subpartition part_2_sub_1,
subpartition part_2_sub_2)
)
/
2 创建范围-列表分区表
create table composite_range_list
(id number,
city varchar2(20),
created date)
partition by range(created)
subpartition by list(city)
(
partition part_1
values less than (to_date('2018-01-01','yyyy-mm-dd'))
(subpartition part_1_sub_1 values('A','B'),
subpartition part_1_sub_2 values('C','D')
),
partition part_2
values less than (to_date('2019-01-01','yyyy-mm-dd'))
(subpartition part_2_sub_1 values('E','F'),
subpartition part_2_sub_2 values('G','H'),
subpartition part_2_sub_3 values('I','J'))
)
/
查询分区和自分区的数量
select table_name,subpartition_count,partition_name,blocks from user_tab_partitions where table_name='COMPOSITE_RANGE_LIST’;
获得分区表的定义
SQL> select dbms_metadata.get_ddl('TABLE','COMPOSITE_RANGE_LIST') from dual;
分区表中关于行移动问题:
当更新分区表时,如果分区键被更新,导致该行数据要分布的其他分区,此时需要启动行移动功能(enable row movement),否则会失败。
这个特性会导致行的rowID发生变更,此时索引会自动维护。其他如alter table T move ,alter table t shrink space 也可能带来rowID的变更。
索引分区:
概述
1 本地分区索引适用于数据仓库系统:本地分区索引提供了高可用性,隔离故障到某个具体分区。索引分区维护灵活,如移动分区,只需要重建或者维护卖游戏账号地图本地索引分区,不影响其他分区索引,而全局索引则需要全部重建。本地索引简化了基于时间点的表分区恢复,而全局索引需要重建。
2 全局索引更适用于OLTP系统:因为OLTP系统都是短的小事务,通过索引访问数据,索引的分区往往不会提高查询性能,索引分区不当还会使得查询性能下降,扫描一个索引段,与扫描多个索引段的开销一般要小。者需要了解索引的结构。
本地分区索引和全局索引
1 本地分区索引: 这里的本地指每个表分区都有一个索引分区,这个索引分区只对这个表分区中的数据进行索引、分区表<- - - - - > 分区索引 具有一一对应关系。
1.1 本地前缀分区索引: 索引的前几列为分区键。 本地非前缀分区索引:索引不将分区键作为分区索引的前几列。
分析分区裁剪:当查询使用索引时,是否使用表的分区裁剪取决于查询谓词。
例子:这个表按照分区键a实现范围分区
create table partitioned_t
(a int,
b int,
data char(20)
)
partition by range (a)
(
partition part_1 values less than(2) tablespace p1,
partition part_2 values less than(3) tablespace p2
)
/
下面创建两个索引,一个本地前缀分区索引,一个是本地非前缀分区索引
SQL> create index local_prefixed on partitioned_t (a,b) local;
SQL> create index local_noprefixed on partitioned_t(b) local;
我们插入数据,此时索引是自动维护的。
insert into partitioned_t
select mod(rownum-1,2) + 1,rownum,'xxx
' from dual connect by level<=70000;
commit;
收集统计信息
exec dbms_stats.gather_table_stats(user,'PARTITIONED_T',cascade=>true);
下面模拟表分区和索引分区故障,使得第二个索引分区无法访问,我们看本地前缀分区索引和本地非前缀分区索引在高可用方面的不同。
SQL> alter tablespace p2 offline;
下面查询,注意谓词的区别
SQL>select * from partitioned_t where b=1
ERROR:
ORA-00376: file 2 cannot be read at this time
ORA-01110: data file 2: '/u01/app/oracle/oradata/orcl/p2.dbf'
分析:此时我们通过执行计划知道,Oracle希望通过索引访问,但是此时该索引是本地非前缀分区索引,会扫描全部索引分区,但是由于第二个索引分区介质故障无法访问,导致该查询失败,但是同样是获得一条数据,我们看使用本地前缀分区索引的高可用效果。(这个例子有个问题,就是即使非本地前缀索引,依然会使用到分区剪裁功能,此时就忽略掉p2空间的不可用问题)。
SQL> select data from partitioned_t where a=1 and b=1;
DATA
--------------------
xxx
Execution Plan
----------------------------------------------------------
Plan hash value: 3776131401
-----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 29 | 2 (0) | 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE | | 1 | 29 | 2 (0) | 00:00:01 | 1 | 1 |
| 2 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED | PARTITIONED_T | 1 | 29 | 2 (0) | 00:00:01 | 1 | 1 |
|* 3 | INDEX RANGE SCAN | LOCAL_PREFIXED | 1 | | 1 (0) | 00:00:01 | 1 | 1 |
总结:前缀分区索引被使用时,可以实现分区裁剪,提供高可用性,而非前缀分区索引被使用时,需要扫描所有索引分区,不能保证高可用性,并且由于要扫描多个索引段,性能可能略有下降。
通过实验得出同样结论
后者的逻辑读,和cost都增加。因为后者要扫描更多的索引分区。
下面验证Oracle是否对非前缀索引使用分区裁剪,为了优化器选择使用非前缀索引,我们先删除前缀索引
drop index local_prefixed;
select * from partitioned_t where a = 1 and b = 1;
-------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 29 | 2 (0) | 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE | | 1 | 29 | 2 (0) | 00:00:01 | 1 | 1 |
|* 2 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED | PARTITIONED_T | 1 | 29 | 2 (0) | 00:00:01 | 1 | 1 |
|* 3 | INDEX RANGE SCAN | LOCAL_NOPREFIXED | 1 | | 1 (0) | 00:00:01 | 1 | 1 |
-------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("A"=1)
3 - access("B"=1)
本地索引和唯一性约束问题
结论:本地分区索引不能保证唯一性
1 如果允许则失去分区表可用性和扩展性,插入和更新每一行数据,都要扫描所有分区,分区越多,可用性越差,扩展性越差。
2 如果可以保证,则会导致表的插入和更新要串行化,因为分区一插入的数据,必须保证此时不能再分区二插入,严重降低了数据的并发能力。
在OLTP系统中,唯一性约束由Oracle自己保证,不能通过本地分区索引保证唯一性。
重建分区表的本地索引,默认索引与表在同一个表分区,每个分区表的索引名称默认part_n,重建分区索引,需要指定分区名称,
SQL> select index_name,partition_name,status,leaf_blocks from user_ind_partitions where index_name=' LOCAL_NOPREFIXED';
no rows selected
SQL> select index_name,partition_name,status,leaf_blocks from user_ind_partitions where index_name='LOCAL_NOPREFIXED';
INDEX_NAME PARTITION_NAME STATUS LEAF_BLOCKS
------------------------------ ------------------------------ -------- -----------
LOCAL_NOPREFIXED PART_1 USABLE 70
LOCAL_NOPREFIXED PART_2 USABLE 70
SQL> alter index LOCAL_NOPREFIXED rebuild partition PART_1 online;
Index altered.