《Oracle SQL优化基础》之分区

浏览: 3704

首先我们从一个图书馆引出话题(又是那个图书馆,是不是该取个名字了)

我们之前索引那一篇里的图书馆有100w本书,那个学校来借了一次80w本书,把图书馆管理员累的不行。

后来那他申请按照书内容的类型进行排序,分层摆放。

后面有学校再来接的时候说我要烹饪类 的书,他就可以直接找到,无需“全表扫描”,也不用翻目录了,这也就是我们今天所讲的分区。

分区表的概念

分区是Oracle8i引入的一种应对VLDB(Very Large Database)的机制,可以理解为一个大表被分成了好几个小表,一个分区就是一个表,可以进行单独的DDL、DML操作,比如truncate、alter、select等。

以下是oracle各个版本对应的分区功能,是Oracle作为大型数据库最标志性的功能:

 Clipboard Image.png

我们基于11g版本讲解分区:

当前版本分区大致分下面几类:

范围分区、列表分区、哈希分区、系统分区、外键分区等等,还有上述分区的组合分区。

Oracle当前版本中支持的分区数量:

Tables can be partitioned into up to 1024K-1 separate partitions.

 

创建分区表语法如下(范围分区举例):

create table ltz_partition_table
  (
  ltz_id number ,
  sysid number(8) not null,
  name varchar2(300),
  reg_date date not null
  )
  partition by range (ltz_id)
  (
   partition P001 values less than(30000000) tablespace USERS,
   partition P002 values less than(60000000) tablespace USERS,
   partition P003 values less than(maxvalue) tablespace USERS
   );

 

分区类型

 Clipboard Image.png

范围分区:

 Clipboard Image.png

列表分区:

 Clipboard Image.png

哈希分区:

 Clipboard Image.png

更多的分区的语法请查阅Oracle官方文档,在次不过多介绍:

http://docs.oracle.com/cd/E11882_01/server.112/e41084/statements_7002.htm#i2129707

 

 

什么时候使用分区表

数据仓库中的表动辄几百G,甚至上T、P级别,为了解决这种大表的性能问题,Oracle提供了分区表的概念,一般来说当表的数据量达到百万级的时候就需要创建分区了(根据机器性能可具体分析)。

具体要分多少区,按照什么分区,可以根据实际业务来看。

 

而Oracle认为当一个表超过2G的时候就要建分区了。

1Tables greater than 2GB should always be considered forpartitioning.

2Tables containing historical data, in which new data is addedinto the newest partition. A typical example is a historical table where onlythe current month's data is updatable and the other 11 months are read only.

 

 

举例说明:

在我的数据集市层有一张全量的加速表(汇总表),大约千万级数据量,且以每月100w的数据量持续增长,每月增量数据中共有8种场景,报表每次只能查询1种场景,按月查询,其他维度条件为非必选项。

 

表结构如下

 Clipboard Image.png

很明显这时候我们就要建立分区了,否则报表工具将会很难在短时间内查询出用户想要的数据,根据表结构和实际业务逻辑:

1.        每月100万的数据量,所以我们可以按照第一个statistic_date字段进行按月分区,但是第一个字段是8位,也就是精确到日的,这时候我们就可以对字段的前六位进行截取,并作为分区字段,进行列表分区。或者直接建立范围分区;

2.        “每条记录8种场景,每次查询1种场景”根据这个业务需求再把SCENE_ID作为子分区,这时候每次查询的数据就只有100w/8 = 12.5w

这样分区效率就高很多。

 

如果我按照PROD_ID进行分区,而报表查询的时候并不是每次都按照产品查询的话,就会引起不必要的分区全表扫描。

 

以上只是个简单的例子,目的是为了让大家明白在实际项目中是如何根据业务用户习惯去应用表分区的。

 

当然,我们也可能会遇到一开始数据量很小,后来业务量急速增长,由于前期的疏忽导致我们队表的设计失误,没有设计成分区表,这时候怎么办呢?

Oracle为此提供了分区的其他功能去实现,也就是我下面要讲的内容:分区交换、间隔分区。

 

分区交换

想解决上面的问题需要用到分区的两个特性:分区交换和分区拆分。

 

所谓分区交换就是对数据字典中分区/表的定义进行修改,没有数据的修改或复制,属于DDL的范畴,无IO效率最高。

适用于将分区/子分区的数据交换、表数据交换等,非分区表中的大数据交换到分区表中的一个分区的操作。

这里的交换只是数据交换,而不是把一个表变成另一个表,且尽量在闲时进行操作。

 

而分区拆分比较好理解,就是把一个分区的数据拆分到多个分区中去。

 

举个栗子:

先建立一个非分区测试表(72733条数据)

create table ltz_no_partition as select * from dba_objects;

 

再建立分区测试表(表结构一样,无数据)

-- Create table
create table LTZ_PARTITION
(
  owner          VARCHAR2(30),
  object_name    VARCHAR2(128),
  subobject_name VARCHAR2(30),
  object_id      NUMBER,
  data_object_id NUMBER,
  object_type    VARCHAR2(19),
  created        DATE,
 last_ddl_time  DATE,
  timestamp      VARCHAR2(19),
  status         VARCHAR2(7),
  temporary      VARCHAR2(1),
  generated      VARCHAR2(1),
  secondary      VARCHAR2(1),
  namespace      NUMBER,
  edition_name   VARCHAR2(30)
)
tablespace USERS
partition by range(object_id)(
partition P000 values less than (maxvalue))

 

--分区交换
alter table ltz_partition exchange partition P000 with table ltz_no_partition;
--分区拆分
alter table ltz_partition split partition P000 at (123)--0 to 122
into (partition P001, partition P000);
alter table ltz_partition split partition P000 at (1000)-- 123 to 999
into (partition P002, partition P000);
alter table ltz_partition split partition P000 at (2000)-- 1000 to 1999
into (partition P003, partition P000);
alter table ltz_partition split partition P000 at (3000)-- 2000 to 2999
into (partition P004, partition P000);
alter table ltz_partition split partition P000 at (4000)-- 3000 to 3999
into (partition P005, partition P000);
alter table ltz_partition split partition P000 at (5000)--4000 to 4999
into (partition P006, partition P000);

 

几乎是瞬间完成,秒杀。

执行完成后可以查一下表数据

ltz_no_partition已经是0条数据

ltz_partition则是72733条数据,默认或者没有拆分的数据都在P000分区中。

间隔分区(Interval Partitioning )

我们平时还会遇到下面的情况,一个事实表按照时间字段,每个月一个分区。

比如201501、201502、201503...

在上线脚本中我们会从最早数据开始,一直到未来1到2年一次性全部建立分区,比如:

 

create table LTZ_PARTITION_02
(
  period_id      number,
  owner          VARCHAR2(30),
  object_name    VARCHAR2(128),
  subobject_name VARCHAR2(30),
  object_id      NUMBER,
  data_object_id NUMBER,
  object_type    VARCHAR2(19),
  created        DATE,
  last_ddl_time  DATE,
  timestamp     VARCHAR2(19),
  status         VARCHAR2(7),
  temporary     VARCHAR2(1),
  generated     VARCHAR2(1),
  secondary      VARCHAR2(1),
  namespace      NUMBER,
  edition_name   VARCHAR2(30)
)
tablespace USERS
partition by list(period_id)(
partition P001 values (201501),
partition P002 values (201502),
partition P003 values (201503),
partition P004 values (201504),
partition P005 values (201505)
)

 

这里暂时建立5个分区,1月份上线。

但是上线后半年突然ETL报错了。最后发现到了6月份没有分区了,于是第二次上线一次性加了24个分区:

alter table ltz_partition_02 add partition P006 values(201506) tablespace users;
alter table ltz_partition_02 add partition P007 values(201507) tablespace users;
alter table ltz_partition_02 add partition P008 values(201508) tablespace users;
alter table ltz_partition_02 add partition P009 values(201509) tablespace users;
...
alter table ltz_partition_02 add partition P024 values(201612) tablespace users;

 

但是2年后依旧会发生一样的错误,所以我们解决此问题一般有两种方式:

1. 范围分区,范围分区中有一个MAXVALUE选项,如果新增数据不属于当前已有分区,又大于最大分区范围,则会自动列入MAXVALUE中:

-- Create table
create table LTZ_PARTITION_03
(
  owner          VARCHAR2(30),
  object_name    VARCHAR2(128),
  subobject_name VARCHAR2(30),
  object_id      NUMBER,
  data_object_id NUMBER,
  object_type    VARCHAR2(19),
  created        DATE,
  last_ddl_time  DATE,
  timestamp     VARCHAR2(19),
  status         VARCHAR2(7),
  temporary     VARCHAR2(1),
  generated     VARCHAR2(1),
  secondary      VARCHAR2(1),
  namespace      NUMBER,
  edition_name   VARCHAR2(30)
)
tablespace USERS
partition by range(object_id)(
partition PMAX values less than (maxvalue))

但这种方式并不是最好的,如果大量数据存储在PMAX分区中,分区则起不到分区的作用。

下面介绍11g版本引入的新特性:间隔分区。

 

2. 间隔分区(IntervalPartitioning)

间隔分区可以实现Range分区的按年,月,日来自动生成分区

-- Create table
create table LTZ_PARTITION_04
(
  statistics_date date,
  owner          VARCHAR2(30),
  object_name    VARCHAR2(128),
  subobject_name VARCHAR2(30),
  object_id      NUMBER,
  data_object_id NUMBER,
  object_type    VARCHAR2(19),
  created        DATE,
  last_ddl_time  DATE,
  timestamp     VARCHAR2(19),
  status         VARCHAR2(7),
  temporary     VARCHAR2(1),
  generated     VARCHAR2(1),
  secondary      VARCHAR2(1),
  namespace      NUMBER,
  edition_name   VARCHAR2(30)
)
tablespace USERS
partition by range(statistics_date)
INTERVAL(NUMTOYMINTERVAL(1,'MONTH')) 

  PARTITION P001 VALUES LESS THAN(TO_DATE('2015-02-01','YYYY-MM-DD')) 
);

 

小于2015年2月1日的让入P001分区,随后每插入一个大于2月的数据,则会自动创建一个分区,大于3月则会再次创建分区。

这是Oracle11g的一个非常强大且实用的技术,用的好可以省去很多维护性工作,也少了很多不必要的生产问题。

分区索引

先来看一段Oracle的说明:

1.       If the table partitioning column is a subset of the indexkeys, use a local index. If this is the case, you are finished. If this is notthe case, continue to guideline 2.

2.       If the index is unique, use a global index. If this is thecase, you are finished. If this is not the case, continue to guideline 3.

3.       If your priority is manageability, use a local index. Ifthis is the case, you are finished. If this is not the case, continue toguideline 4.

4.       If the application is an OLTP one and users need quickresponse times, use a global index. If the application is a DSS one and usersare more interested in throughput, use a local index.

分区索引就是

 

分区索引分两类:

全局索引和本地索引。

创建索引时默认为全局索引,本地索引要增加LOCAL关键字

CREATE INDEX INX_TAB_PARTITION_LOCAL ON ltz_partition_table(ltz_id) LOCAL;

 

要特别注意的一点:

如果在分区表中建立全局索引,当alter table partition的时候(对分区进行合并、拆分、截断等操作),全局索引会失效,再次插数会报索引无效的错误。因为alter table partition的时候会发生行迁移,所以如果有对表分区的类似Move操作的时候:

1.        使用本地索引;

2.        重建索引

3.        在alter table partition xxx后增加update indexes子句。

 

 

常用命令及注意的地方

添加、合并、修改分区的基础命令在此简单罗列部分常用的,如果不记得命令可以随时百度,没有哪个程序员是可以记住所有语法的。

 

查询分区

select * from ltz_partition_tablepartition(P001)

 

添加分区

alter table ltz_partition_table add partition P001 values('123') tablespace users;

 

合并分区

alter table ltz_partition_table mergepartitions P001,P002 into partition P004;

 

拆分分区

alter table ltz_partition_table splitpartition P004 at('123') into (partition P001tablespace users, partition P002 tablespace users);

列表分区使用values关键字,范围分区使用at关键字

 

移动分区

alter table ltz_partition_tablemove partition P001 tablespace system;

MOVE会自动维护局部分区索引,oracle不会自动维护全局索引,所以需要我们重新rebuild分区索引。

 

Truncate 分区

alter table ltz_partition_table truncatepartition(P001);

truncatedelete的效率我就不详细说了哈,大家应该都懂的

 

Drop分区

alter table ltz_partition_table drop partition P001;

一个分区表最少要有一个分区,所以无法把全部分区全部drop

 

 

更多请百度。

推荐 12
本文由 老头子 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

13 个评论

好厉害的样子,虽然看不太懂
很详细,谢谢。
如果只有一块磁盘的话,分区有效果吗?
麻烦老大,下一篇出点执行计划、hint、统计信息等相关的文章,谢谢。
这里的分区不是磁盘分区,是表分区,这个概念别混了
这三个东西放一篇讲只能讲的很粗,估计讲不明白
好/好/好,非常好,希望博主坚持啊!
学习,支持。
先赞一个 虽然还没来得及细看 慢慢看
研究研究,加油老头子!好样的!
老头子,好文呀,拜读了。。。
间隔分区的分区名称不能自定义,维护和使用起来不太方便吧?
还好吧,通过数据字典就可以查到分区名,而且有一定规律可循

要回复文章请先登录注册