Oracle建议如果单个表超过2G就最好对其进行分区,一个分区数据行不要超过30w对于大表创建分区的好处是显而易见的本例是在oracle 11g下自带样例,sh 用户下 基于sales进行分区演示。该表数据 有91w条数据
select count(1) from sales t;
我们用sales 表数据来重新建表并进行分区,比较分区与不分区的效率,以及分区后分区局部索引与位图索引的效率比较。
sales_part_test
都按照time_id字段进行分区
create table sales_part_test
(
prod_id NUMBER not null,
cust_id NUMBER not null,
time_id DATE not null,
channel_id NUMBER not null,
promo_id NUMBER not null,
quantity_sold NUMBER(10,2) not null,
amount_sold NUMBER(10,2) not null
)
partition by range(time_id) subpartition by range (time_id) --指定主表分区和子分区分区方式都是:范围分区,并按照列time_id 进行范围划分
(
partition sales_part_1998 values less than (TO_DATE('1999-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'))
tablespace EXAMPLE
(
subpartition sales_part_1998_01 values less than ( TO_DATE('1998-02-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,--指定主分区sales_part_1998子分区 sales_part_1998_01 注意每个主分区的子分区名字不能一样
subpartition sales_part_1998_02 values less than ( TO_DATE('1998-03-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1998_03 values less than ( TO_DATE('1998-04-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1998_04 values less than ( TO_DATE('1998-05-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1998_05 values less than ( TO_DATE('1998-06-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1998_06 values less than ( TO_DATE('1998-07-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1998_07 values less than ( TO_DATE('1998-08-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1998_08 values less than ( TO_DATE('1998-09-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1998_09 values less than ( TO_DATE('1998-10-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1998_10 values less than ( TO_DATE('1998-11-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1998_11 values less than ( TO_DATE('1998-12-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1998_12 values less than ( TO_DATE('1999-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE
)
,
partition sales_part_1999 values less than (TO_DATE('2000-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'))
tablespace EXAMPLE
(
subpartition sales_part_1999_01 values less than ( TO_DATE('1999-02-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_02 values less than ( TO_DATE('1999-03-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_03 values less than ( TO_DATE('1999-04-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_04 values less than ( TO_DATE('1999-05-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_05 values less than ( TO_DATE('1999-06-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_06 values less than ( TO_DATE('1999-07-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_07 values less than ( TO_DATE('1999-08-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_08 values less than ( TO_DATE('1999-09-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_09 values less than ( TO_DATE('1999-10-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_10 values less than ( TO_DATE('1999-11-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_11 values less than ( TO_DATE('1999-12-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_1999_12 values less than ( TO_DATE('2000-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE
),
partition sales_part_2000 values less than (TO_DATE('2001-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'))
tablespace EXAMPLE
(
subpartition sales_part_2000_01 values less than ( TO_DATE('2000-02-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_02 values less than ( TO_DATE('2000-03-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_03 values less than ( TO_DATE('2000-04-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_04 values less than ( TO_DATE('2000-05-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_05 values less than ( TO_DATE('2000-06-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_06 values less than ( TO_DATE('2000-07-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_07 values less than ( TO_DATE('2000-08-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_08 values less than ( TO_DATE('2000-09-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_09 values less than ( TO_DATE('2000-10-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_10 values less than ( TO_DATE('2000-11-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_11 values less than ( TO_DATE('2000-12-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2000_12 values less than ( TO_DATE('2001-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE
),
partition sales_part_2001 values less than (TO_DATE('2002-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'))
tablespace EXAMPLE (
subpartition sales_part_2001_01 values less than ( TO_DATE('2001-02-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_02 values less than ( TO_DATE('2001-03-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_03 values less than ( TO_DATE('2001-04-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_04 values less than ( TO_DATE('2001-05-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_05 values less than ( TO_DATE('2001-06-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_06 values less than ( TO_DATE('2001-07-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_07 values less than ( TO_DATE('2001-08-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_08 values less than ( TO_DATE('2001-09-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_09 values less than ( TO_DATE('2001-10-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_10 values less than ( TO_DATE('2001-11-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_11 values less than ( TO_DATE('2001-12-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE,
subpartition sales_part_2001_12 values less than ( TO_DATE('2002-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) tablespace EXAMPLE
)
)
create index idx_sales_sales_part_test on sales_part_test (time_id)local;
insert into sales_part_test select t.* from sales t;
select t.* from sales_part_test t where t.time_id<=date'1998-05-01'
看到最先是通过我们创建的局部索引idx_sales_sales_part_test 采用了索引范围扫描方式访问了第一个主分区
sales_part_1998 的所有子分区,
获取到数据后再通过 PARTITION RANGE ITERATOR(分区范围迭代) 方式或获取主分区 sales_part_1998的1-5子分区。最后 看到 只访问了一个主分区 就是 sales_part_1998 同时看到分区表的逻辑读是38个
create table sales_nopart as select t.* from sales t;
create index idx_sales_nopart on sales_nopart (time_id) ;
可以看到 执行计划并没有选择索引扫描,而是选择了全部扫描,而且成本是分区表的1243倍,耗时是15倍左右
分区表的一致性读只有38个,物理读没有,而不是分区表的逻辑读即一致性读是8227个,物理读是4166个。性能高低立见高低。
把两个表都进行分析
exec dbms_stats.gather_table_stats('sh','SALES_PART_TEST');
exec dbms_stats.gather_table_stats('sh','SALES_NOPART');
来看如下语句的执行计划
select t.* from sales_part_test t where t.time_id=date'1998-05-01'
非分区表执行计划
select t.* from sales_nopart t where t.time_id=date'1998-05-01'
通过上面的结果,可以看到分区表和非分区表都走了索引范围扫描,但是非分区表逻辑读是58个 成本是42 依然比
分区表的 54个逻辑读和33的成本要多。
看到这里我们还不能满足,我们发现其实分区中time_id重复值太多了 ,我们可以尝试把b树索引改为创建位图索引
drop index idx_sales_sales_part_test;
create bitmap index idx_sales_sales_part_test on sales_part_test (time_id)local;
--注意位图索引必须创建为局部索引
--先分析表
exec dbms_stats.gather_table_stats('sh','SALES_PART_TEST');
select t.* from SALES_PART_TEST t where t.time_id=date'1998-05-01'
从结果中看到 创建位图索引后,oracle选择了位图索引进行唯一值扫描,并只扫描了一个第五个子分区,成本只有4,而且逻辑读只用了36个,这个才是最优选择?
但是………… 当我们执行以下语句时,执行计划还会比b树索引的成本低吗?
select t.* from SALES_PART_TEST t where t.time_id<=date'1998-05-01'
结果让我们大跌眼镜,此时,数据库最新并没有选择走索引,而是对主分区sales_part_1998 选择了全表扫描
,其他分区是扫描都是一样的,但是总的逻辑读是走B树索引38 个的10倍多。所以对于数据库并没有绝对的最优,都是相对的。到这里我们会有个疑问:为啥没走位图索引?
对于走位图索引 需要 谓语中 有位图索引列 且谓语是 “=”
需要区别的是对于B树索引, 对非唯一索引列上进行的任何查询 就会走 索引范围扫描 见 step 4 和step 7 结果都证实了这一点。
要获取列的列表可以通过其中一列的位图索引来获得 这里走位图索引范围扫描
我们通过把上面的sql 中* 改为 t.cust_id,t.time_id,t.prod_id 再来看执行计划
select t.cust_id,t.time_id,t.prod_id from SALES_PART_TEST t where t.time_id <=date'1998-05-01'
可以看到执行计划果然选择了位图索引范围扫描,尽管依然扫描了主分区sales_part_1998 的所有子分区
但是看到逻辑读只有122个 ,仅仅是B树索引范围扫描的4倍不到,跟全表扫描相比实在好了很多。
需要注意的位图索引需要慎重使用,位图索引适合数据仓库,位图索引特别不适经常DML操作的线上系统。
遇到问题就要刨根到底,一层一层的解开自己的疑惑,本文开始仅仅是想验证对于数据量大的表,分区比不分区查询
效率更高,但是到后面引申了分区表中建B树索引和位图索引的效率比较,同时也回顾了索引扫描的
条件,同时也学到了凡是都没有绝对的最好,需要辩证来看待问题。