Oracle 表分區算法
早在8.0.5版本中,Oracle就將範圍分區技術引入,如今分區功能已經愈來愈強大,包括支持擴展分區功能、Interval分區、外鍵分區、模擬列分區、以及分區建議器等。那麼,分區到底有什麼好處呢?咱們爲何要使用分區呢?在什麼環境下使用分區比較合適呢?數據庫
分區表應用在大表更合適,至少要大於100萬條的記錄才能夠考慮使用分區表安全
1)因爲Oracle數據庫能夠將分區指定爲不一樣的表空間,而不一樣的表空間是能夠指向不一樣的磁盤設備的,優化物理硬件資源,縮短了執行的時間;安全性也獲得了很大的提高。session
2)能夠無視其餘分區的數據,僅對本區的數據進行刪除操做。oracle
3)提升了特定的查詢速度。app
4)節約維護的成本,下降由於維護數據而對其餘系統數據產生的各類影響。ide
範圍分區將數據基於範圍映射到每個分區,這個範圍是你在建立分區時指定的分區鍵決定的。這種分區方式是最爲經常使用的,而且分區鍵常常採用日期。函數
如何選擇範圍分區的依據從而可以讓數據均勻分佈,是一個須要重點關注的問題。測試
當使用範圍分區時,請考慮如下幾個規則:優化
一、每個分區都必須有一個VALUES LESS THAN子句,它指定了一個不包括在該分區中的上限值。分區鍵的任何值等於或者大於這個上限值的記錄都會被加入到下一個高一些的分區中。
二、全部分區,除了第一個,都會有一個隱式的下限值,這個值就是此分區的前一個分區的上限值。
三、在最高的分區中,MAXVALUE被定義。MAXVALUE表明了一個不肯定的值。這個值高於其它分區中的任何分區鍵的值,也能夠理解爲高於任何分區中指定的VALUE LESS THAN的值,同時包括空值。
1)查看數據庫表空間
select * from dba_tablespaces
2)建立表空間
create tablespace zj1
datafile 'D:\app\zWX214990\oradata\orcl\zj.dbf' size 50M
下面,咱們經過腳原本建立一個基於月份的範圍分區表(假設插入的數據所有都是2013年的數據)
DROP TABLE PART_LOG_ZJ PURGE; --purge永久刪除選項
(補充):對於誤刪的表,只要沒有使用purge永久刪除選項,那麼從flash back區恢復回來但願挺大的。通常步驟:
select * from recyclebin
flashback table tb to before drop
CREATE TABLE PART_LOG_ZJ(
LOG_ID NUMBER(20) PRIMARY KEY,
LOG_DATE DATE,
LOG_DESC VARCHAR2(20)
)
PARTITION BY RANGE(LOG_DATE)
(
PARTITION PART_LOG_01 VALUES LESS THAN (TO_DATE('2013-01-01','YYYY-MM-DD')) TABLESPACE zj1,
PARTITION PART_LOG_02 VALUES LESS THAN (TO_DATE('2013-03-01','YYYY-MM-DD')) TABLESPACE zj1,
PARTITION PART_LOG_03 VALUES LESS THAN (TO_DATE('2013-05-01','YYYY-MM-DD')) TABLESPACE zj1,
PARTITION PART_LOG_04 VALUES LESS THAN (TO_DATE('2013-07-01','YYYY-MM-DD')) TABLESPACE zj1,
PARTITION PART_LOG_05 VALUES LESS THAN (TO_DATE('2013-09-01','YYYY-MM-DD')) TABLESPACE zj1,
PARTITION PART_LOG_06 VALUES LESS THAN (TO_DATE('2013-10-01','YYYY-MM-DD')) TABLESPACE zj1,
PARTITION PART_LOG_07 VALUES LESS THAN (MAXVALUE) TABLESPACE zj1
);
經過PARTITION BY RANGE關鍵字來指出進行分區的策略,其中PARTITION BY LOG(LOG_DATE)指的是使用LOG_DATE來做爲分區的字段,裏面根據取值的大小,命名了7個分區來存放數據,每一個分區還能夠指定不一樣的表空間,第七個分區使用MAXVALUE來避免有數值沒有被上面的範圍圈定,這個就相似於SWITCH語法中的DEFAULT,不一樣的,這個並非fall through的。
下面,咱們來生成10萬條數據來插入到表中,查看一下表中發生了什麼樣子的變化?
INSERT INTO PART_LOG_ZJ
(LOG_ID, LOG_DATE, LOG_DESC)
SELECT LEVEL,
TO_DATE('2013-01-01', 'YYYY-MM-DD') +
NUMTODSINTERVAL(CEIL(DBMS_RANDOM.VALUE(0, 365)), 'DAY'),
LEVEL || 'DESC'
FROM DUAL
CONNECT BY LEVEL <= 100000;
首先,咱們能夠查詢一下落在七、8月份的日誌記錄,就如同咱們去新華書店只是去查找IT類的書籍同樣,咱們知道咱們應當去那裏尋找名字爲PART_LOG_05分區的數據:
SELECT COUNT(1) FROM PART_LOG_ZJ PARTITION (PART_LOG_05);
顯示結果:
經過上面的結果能夠很明顯的看出,有17030條數據落在了七、8月份的分區中。咱們經過指定分區,將所有的注意力只放到此1.7W條左右的數據而不是所有的10W條數據上,能夠很明顯帶來效能的提高。
咱們如何才能知道正確的分區呢?
Oracle數據庫也提供了這個指引牌,那就是下面兩個數據字典:DBA_PART_TABLES和DBA_TAB_PARTITIONS
SELECT T.OWNER AS "全部者",
T.TABLE_NAME AS "表名",
T.PARTITIONING_TYPE AS "分區類型",
T.SUBPARTITIONING_TYPE AS "子分區類型",
T.PARTITION_COUNT AS "子分區數量",
T.PARTITIONING_KEY_COUNT AS "分區鍵中列的數量",
T.SUBPARTITIONING_KEY_COUNT AS "子分區鍵中列的數量",
T.STATUS AS "分區表狀態",
T.DEF_TABLESPACE_NAME AS "默認表空間"
FROM DBA_PART_TABLES T
WHERE T.TABLE_NAME IN ('PART_LOG_ZJ');
經過上面咱們能夠看到,表PART_LOG_CHENZW如今有7個分區,可是,咱們並不能知道這7個分區都是存放什麼樣子的數據的?咱們到什麼地方去找到咱們但願的七、8月的數據呢?
SELECT T.TABLE_OWNER AS "全部者",
T.TABLE_NAME AS "表名",
T.COMPOSITE AS "是否組合分區",
T.PARTITION_NAME AS "分區名",
T.SUBPARTITION_COUNT AS "子分區數",
T.HIGH_VALUE AS "分區上限",
T.HIGH_VALUE_LENGTH AS "分區上限長度",
T.PARTITION_POSITION AS "分區在表中位置",
T.TABLESPACE_NAME AS "所在表空間"
FROM DBA_TAB_PARTITIONS T
WHERE T.TABLE_NAME IN ('PART_LOG_ZJ')
從上面的結果能夠看到,若是咱們但願找到七、8月份的數據,就能夠根據分區上限定位到PART_LOG_05分區。
最後,咱們能夠經過查詢數據字典USER_SEGMENTS來查看分區表佔用的磁盤空間信息,以下:
SELECT T.SEGMENT_NAME AS "段名",
T.PARTITION_NAME AS "分區名",
T.SEGMENT_TYPE AS "分區類型",
T.BYTES / POWER(1024, 2) || 'M' AS "分區大小",
T.TABLESPACE_NAME AS "表空間"
FROM USER_SEGMENTS T
WHERE T.SEGMENT_NAME IN ('PART_LOG_ZJ');
一般狀況下,若是你的數據中的某一項是能夠被枚舉的,那麼,此列就能夠用做列表分區的分區字段。
CREATE TABLE PART_BOOK_ZJ(
BOOK_ID NUMBER(20) PRIMARY KEY,
BOOK_DATE DATE,
BOOK_TYPE NUMBER(2) NOT NULL,
BOOK_DESC VARCHAR2(20)
)
PARTITION BY LIST(BOOK_TYPE)
(
PARTITION PART_BOOK_01 VALUES(0) TABLESPACE zj1,
PARTITION PART_BOOK_02 VALUES(1) TABLESPACE zj1,
PARTITION PART_BOOK_03 VALUES(2) TABLESPACE zj1,
PARTITION PART_BOOK_04 VALUES(3) TABLESPACE zj1
);
下面的腳本用於生成相應的數據:
INSERT INTO PART_BOOK_ZJ
(BOOK_ID, BOOK_DATE, BOOK_TYPE,BOOK_DESC)
SELECT LEVEL,
TO_DATE('2013-01-01', 'YYYY-MM-DD') +
NUMTODSINTERVAL(CEIL(DBMS_RANDOM.VALUE(0, 365)), 'DAY'),
MOD(LEVEL,4),
LEVEL || 'DESC'
FROM DUAL
CONNECT BY LEVEL <= 100000;
注:操做同上
這類分區是在列值上使用散列算法,以肯定將行放入哪一個分區中。當列的值沒有合適的條件時,建議使用散列分區。
散列分區爲經過指定分區編號來均勻分佈數據的一種分區類型,由於經過在I/O設備上進行散列分區,使得這些分區大小一致。
DROP TABLE PART_BOOK_ZJ PURGE;
CREATE TABLE PART_BOOK_ZJ(
BOOK_ID NUMBER(20) PRIMARY KEY,
BOOK_DATE DATE,
BOOK_TYPE NUMBER(2) NOT NULL,
BOOK_DESC VARCHAR2(20)
)
PARTITION BY HASH(BOOK_ID)
(
PARTITION PART_BOOK_01 TABLESPACE zj1,
PARTITION PART_BOOK_02 TABLESPACE zj1,
PARTITION PART_BOOK_03 TABLESPACE zj1,
PARTITION PART_BOOK_04 TABLESPACE zj1
);
INSERT INTO PART_BOOK_ZJ
(BOOK_ID, BOOK_DATE, BOOK_TYPE,BOOK_DESC)
SELECT LEVEL,
TO_DATE('2013-01-01', 'YYYY-MM-DD') +
NUMTODSINTERVAL(CEIL(DBMS_RANDOM.VALUE(0, 365)), 'DAY'),
MOD(LEVEL,4),
LEVEL || 'DESC'
FROM DUAL
CONNECT BY LEVEL <= 100000;
hash分區最主要的機制是根據hash算法來計算具體某條紀錄應該插入到哪一個分區中,hash算法中最重要的是hash函數,Oracle中若是你要使用hash分區,只需指定分區的數量便可。建議分區的數量採用2的n次方,這樣可使得各個分區間數據分佈更加均勻。
這種分區是基於範圍分區和列表分區,表首先按某列進行範圍分區,而後再按某列進行列表分區,分區之中的分區被稱爲子分區。
/*組合分區*/
--刪除測試表
DROP TABLE PART_BOOK_ZJ PURGE;
--建立列表-範圍組合分區
CREATE TABLE PART_BOOK_ZJ(
BOOK_ID NUMBER(20) PRIMARY KEY,
BOOK_DATE DATE,
BOOK_TYPE NUMBER(2) NOT NULL,
BOOK_DESC VARCHAR2(20)
)
PARTITION BY LIST(BOOK_TYPE)
SUBPARTITION BY RANGE(BOOK_DATE)
SUBPARTITION TEMPLATE
(
SUBPARTITION PART_LOG_01 VALUES LESS THAN (TO_DATE('2013-01-01','YYYY-MM-DD')) TABLESPACE zj1,
SUBPARTITION PART_LOG_02 VALUES LESS THAN (TO_DATE('2013-03-01','YYYY-MM-DD')) TABLESPACE zj1,
SUBPARTITION PART_LOG_03 VALUES LESS THAN (TO_DATE('2013-05-01','YYYY-MM-DD')) TABLESPACE zj1,
SUBPARTITION PART_LOG_04 VALUES LESS THAN (TO_DATE('2013-07-01','YYYY-MM-DD')) TABLESPACE zj1,
SUBPARTITION PART_LOG_05 VALUES LESS THAN (TO_DATE('2013-09-01','YYYY-MM-DD')) TABLESPACE zj1,
SUBPARTITION PART_LOG_06 VALUES LESS THAN (TO_DATE('2013-10-01','YYYY-MM-DD')) TABLESPACE zj1,
SUBPARTITION PART_LOG_07 VALUES LESS THAN (MAXVALUE) TABLESPACE zj1
)
(
PARTITION PART_BOOK_01 VALUES(0) TABLESPACE zj1,
PARTITION PART_BOOK_02 VALUES(1) TABLESPACE zj1,
PARTITION PART_BOOK_03 VALUES(2) TABLESPACE zj1,
PARTITION PART_BOOK_04 VALUES(3) TABLESPACE zj1
);
--生成測試數據
INSERT INTO PART_BOOK_ZJ
(BOOK_ID, BOOK_DATE, BOOK_TYPE,BOOK_DESC)
SELECT LEVEL,
TO_DATE('2013-01-01', 'YYYY-MM-DD') +
NUMTODSINTERVAL(CEIL(DBMS_RANDOM.VALUE(0, 365)), 'DAY'),
MOD(LEVEL,4),
LEVEL || 'DESC'
FROM DUAL
CONNECT BY LEVEL <= 100000;
這種分區是基於範圍分區和散列分區,表首先按某列進行範圍分區,而後再按某列進行散列分區。
如下代碼給PART_BOOK_ZJ表添加了一個PART_BOOK_05分區
--添加列表分區
ALTER TABLE PART_BOOK_ZJ ADD PARTITION PART_BOOK_05 VALUES(4) TABLESPACE zj1;
--添加範圍分區
ALTER TABLE PART_LOG_ZJ ADD PARTITION P3 VALUES LESS THAN (TO_DATE('2003-06-01','YYYY-MM-DD')) TABLESPACE zj1;
注意:增長一個分區的時候,增長的分區的條件必須大於現有分區的最大值,不然系統將提示ORA-14074 partition bound must collate higher than that of the last partition 錯誤。
如下代碼刪除了PART_BOOK_ZJ表中名爲PART_BOOK_05的分區:
ALTER TABLE PART_BOOK_ZJ DROP PARTITION PART_BOOK_05;
注意:若是刪除的分區是表中惟一的分區,那麼此分區將不能被刪除,要想刪除此分區,必須刪除表。
截斷某個分區是指刪除某個分區中的數據,並不會刪除分區,也不會刪除其它分區中的數據。當表中即便只有一個分區時,也能夠截斷該分區。經過如下代碼截斷分區:
ALTER TABLE PART_LOG_ZJ TRUNCATE PARTITION PART_LOG_01;
SELECT COUNT(1) FROM PART_LOG_ZJ PARTITION (PART_LOG_01);
合併分區是將相鄰的分區合併成一個分區,結果分區將採用較高分區的界限,值得注意的是,不能將分區合併到界限較低的分區。如下代碼實現了PART_LOG_02, PART_LOG_03分區的合併:
ALTER TABLE PART_LOG_ZJ MERGE PARTITIONS PART_LOG_02, PART_LOG_03 INTO PARTITION PART_LOG_03;
注:1.PART_LOG_02, PART_LOG_03 合併到 PART_LOG_02
2. PART_LOG_03 ,PART_LOG_02 合併到 PART_LOG_03
拆分分區將一個分區拆分兩個新分區,拆分後原來分區再也不存在可是若是表存在PMAX分區那麼原來的分區仍是能夠存在的。注意不能對 HASH類型的分區進行拆分。
ALTER TABLE PART_LOG_ZJ split PARTITION PART_LOG_03
AT (TO_DATE('2013-03-01','YYYY-MM-DD')) INTO (PARTITION PART_LOG_02,PARTITION PART_LOG_03);
接合分區是將散列分區中的數據接合到其它分區中,當散列分區中的數據比較大時,能夠增長散列分區,而後進行接合,值得注意的是,接合分區只能用於散列分區中。經過如下代碼進行接合分區:
ALTER TABLE PART_BOOK_ZJ coalesce PARTITION;
如下代碼將PART_BOOK_01更改成PART_BOOK_1
ALTER TABLE PART_BOOK_ZJ RENAME PARTITION PART_BOOK_01 TO PART_BOOK_1
alter table PART_BOOK_ZJ move Partition PART_BOOK_02
tablespace USERS nologging
select sum(cn) from
(select count(*) cn from PART_BOOK_ZJ PARTITION (PART_BOOK_1)
union all
select count(*) cn from PART_BOOK_ZJ PARTITION (PART_BOOK_02));
統計PART_BOOK_一、PART_BOOK_02上總共多少條數據
SELECT * FROM user_tab_partitions WHERE TABLE_NAME='PART_BOOK_ZJ'
select object_name,object_type,tablespace_name,sum(value)
from v$segment_statistics
where statistic_name IN ('physical reads','physical write','logical reads')and object_type='INDEX'
group by object_name,object_type,tablespace_name
order by 4 desc
SELECT COUNT(1) FROM PART_LOG_ZJ PARTITION (PART_LOG_01);
--顯示數據庫全部分區表的信息:
select * from DBA_PART_TABLES
--顯示當前用戶可訪問的全部分區表信息:
select * from ALL_PART_TABLES
--顯示當前用戶全部分區表的信息:
select * from USER_PART_TABLES
--顯示錶分區信息 顯示數據庫全部分區表的詳細分區信息:
select * from DBA_TAB_PARTITIONS
--顯示當前用戶可訪問的全部分區表的詳細分區信息:
select * from ALL_TAB_PARTITIONS
--顯示當前用戶全部分區表的詳細分區信息:
select * from USER_TAB_PARTITIONS
--顯示子分區信息 顯示數據庫全部組合分區表的子分區信息:
select * from DBA_TAB_SUBPARTITIONS
--顯示當前用戶可訪問的全部組合分區表的子分區信息:
select * from ALL_TAB_SUBPARTITIONS
--顯示當前用戶全部組合分區表的子分區信息:
select * from USER_TAB_SUBPARTITIONS
--顯示分區列 顯示數據庫全部分區表的分區列信息:
select * from DBA_PART_KEY_COLUMNS
--顯示當前用戶可訪問的全部分區表的分區列信息:
select * from ALL_PART_KEY_COLUMNS
--顯示當前用戶全部分區表的分區列信息:
select * from USER_PART_KEY_COLUMNS
--顯示子分區列 顯示數據庫全部分區表的子分區列信息:
select * from DBA_SUBPART_KEY_COLUMNS
--顯示當前用戶可訪問的全部分區表的子分區列信息:
select * from ALL_SUBPART_KEY_COLUMNS
--顯示當前用戶全部分區表的子分區列信息:
select * from USER_SUBPART_KEY_COLUMNS
--怎樣查詢出oracle數據庫中全部的的分區表
select * from user_tables a where a.partitioned='YES'
--刪除一個表的數據是
truncate table table_name;
--刪除分區表一個分區的數據是
alter table table_name truncate partition p5;
注意,在維護分區的時候可能會對索引產生必定的影響,會引發分區表的全局索引無效,須要重建索引。
對分區表作了維護操做後,必須檢查相關索引,如檢查hisdeliverx表的非分區索引:
select owner,index_name,status from dba_indexes where table_name='HISDELIVERX';
如有索引的狀態爲unusable,則必須使用alter index index_name rebuild online;重建該索引。
通常建議在拆分分區、truncate 分區中的數據、刪除分區等功做時最好在語句後面加上update indexes子句,
這樣索引爲unusable狀態的可能性就會很低,以下語句:
ALTER TABLE table_name SPLIT PARTITION partition_name1 AT(20000) INTO (
partition partition_name2,partition partition_name3) update indexes;
alter table table_name truncate partition partition_name update indexes;
alter table table_name drop partition partition_name update indexes;
使用exchange partition方法
假設把hisdeliverx由非分區表改成分區表。
基本思路:hisdeliverx是(數據量上百萬條,列比較多)一個非分區表,此時建立一 個與hisdeliverx同結構的分區表t_hisdeliverx,交換和維護數據,刪除hisdeliverx表,將t_hisdeliverx更 名爲hisdeliverx。而後再重建hisdeliverx表上的索引。
--具體步驟:
--建立分區表(結構和非分區表hisdeliverx相同)
create table t_HISDELIVERX
(
INIT_DATE NUMBER(10) default to_number(to_char(sysdate,'yyyymmdd')) not null,
SERIAL_NO NUMBER(10) default 0 not null,
……
……
---交換數據(數據從非分區表到分區表)
SQL> alter table t_hisdeliverx exchange partition pmax with table hisdeliverx;
Table altered
SQL> drop table hisdeliverx;
---刪除非分區表
Table dropped
---將分區表重命名爲原非分區表名
SQL> alter table t_hisdeliverx rename to hisdeliverx;
Table altered
---檢查hideliverx表是否爲分區表
select table_owner,table_name,partition_name from dba_tab_partitions a where a.table_owner='HS_HIS' and
a.table_name='HISDELIVERX';
注意,在作exchange partition操做前先對該表進行備份
select * from dba_tablespaces
DROP TABLESPACE zj1 INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS;
select TABLE_NAME,PARTITIONING_TYPE,SUBPARTITIONING_TYPE,STATUS
from user_part_tables;
select TABLE_NAME,PARTITION_NAME,TABLESPACE_NAME
from user_tab_partitions
通常步驟:
1.從flash back裏查詢被刪的表
select * from recyclebin
2.執行表的恢復
flashback table tb to before drop
解決:這個表正在使用,lock
select * from v$session;
select * from v$locked_object;
exp zj/zj@orcl buffer=102400 tables=PART_BOOK_ZJ:PART_BOOK_02,file=D:\exp_dxsq_tables.dmp log=D:\exp_dxsq_tables.log