繼上次刪除分區表的分區遇到ORA-01502錯誤後[詳細見連接:Oracle分區表刪除分區引起錯誤ORA-01502: 索引或這類索引的分區處於不可用狀態],最近在split分區的時候又遇到了這個問題。這裏記錄一下該問題是如何產生的,以及如何去解決。html
(一)目的sql
在生產中,咱們的大多數分區表都是按照時間分區的,最多見的是按周或按月分區,對於咱們DBA來講,對錶分區的建立與刪除都很是好管理,我在2018年10月會將全部表的分區建立到2019年12月,這樣2019年的數據就會進入各個對應月份的分區。less
可是也有小部分分區表是按照其它來分區,例如,事物交易編號等,咱們將10萬個交易信息存放在一個分區,對於業務,這樣建立分區是合理的,可是存在必定的隱患,天天的交易量是動態變化的,有可能3天使用完1個分區,也有可能1天就使用完一個分區,那麼分區何時使用完咱們是不得而知的。對於這種狀況,我會爲這類分區表添加max分區,從而保證當數據溢出了咱們建立的分區時,會進入到max分區裏面。分區表大體形式以下(須要說明的是,實際分區表的分區很是大,這裏是爲了模擬事故建立的小表):dom
圖1.表欄位信息post
圖2.表分區狀況測試
(二)事故原由spa
在上週,因爲交易量很是大,發現part_max分區已經開始進入數據了,而且進入的數據量還不小,有大概3個partition的數據。擔憂大量數據進入part_max分區引發業務查詢緩慢,因而決定實施split part_max分區,split執行的語句爲:3d
ALTER TABLE test01 SPLIT PARTITION part_max AT(1000) INTO(PARTITION part_1000,PARTITION part_max); ALTER TABLE test01 SPLIT PARTITION part_max AT(1100) INTO(PARTITION part_1100,PARTITION part_max); ALTER TABLE test01 SPLIT PARTITION part_max AT(1200) INTO(PARTITION part_1200,PARTITION part_max); ALTER TABLE test01 SPLIT PARTITION part_max AT(1300) INTO(PARTITION part_1300,PARTITION part_max);
經過以上操做,將part_max分區的數據分離到part_1000,part_1100,part_1200,part_1300裏面,從而減少part_max數據量。code
在執行操做後,過了幾分鐘,業務方面出現了2個問題:htm
問題1:與該表相關的查詢變得很是緩慢;
問題2:數據插入更新報出了大量的「ORA-01502」錯誤
(三)當時的解決方案
結合上次出現ORA-01502錯誤的經歷,立馬判定是索引出現問題了。查看索引,果真一部分新分區的局部分區索引失效了。立馬刪除索引,新建索引,將業務給啓動起來。
如今回想起來,解決問題的方式略有不妥。出問題的表size很是的大,有150多GB,建立一個局部分區索引大概須要2.5小時,還好是一部分非關鍵業務,不然都不知道如何處理。
(四)查找緣由&實驗驗證
回想了本身當天所作的操做,僅僅對這些表進行了split。那麼是否是split引發索引失效呢?咱們經過實驗驗證一下。
STEP1:建測試表。建立sales表,以transactionId(交易ID)來分區
create table sales ( transactionId number, goodsId number, goodsName varchar2(30), saleTimekey date, goodsdescrip varchar2(100) ) partition by range(transactionId) ( partition part_100 values less than(100), partition part_200 values less than(200), partition part_300 values less than(300), partition part_400 values less than(400), partition part_500 values less than(500), partition part_600 values less than(600), partition part_700 values less than(700), partition part_800 values less than(800), partition part_900 values less than(900), partition part_max values less than(maxvalue) );
STEP2:建立主鍵約束和局部分區索引。
--6.1 建立主鍵約束,主鍵約束會引入惟一性索引 alter table sales add constraint pk_sales_transactionId primary key(transactionId) using index local online tablespace users; --6.2 建立普通的局部分區索引 create index lijiaman.goodsId on sales(goodsId) local online tablespace users;
STEP3:建立一個自增加序列。該序列用來模擬交易ID的自增加狀況
create sequence sq_transactionId start with 1 increment by 1 maxvalue 100000000 nocache;
STEP4:建立一個procedure,用來模擬數據插入
--3.1 建立異常捕獲表 --該表用於捕獲數據插入異常時的異常信息 --drop table sale_exception; create table sale_exception ( timekey date, errcode varchar2(50), errmess varchar2(500) ); --3.2建立插入sales表的pl/sql程序 create or replace procedure p_sales is v_sqlcode number; v_sqlerrm varchar2(4000); begin insert into sales (transactionId, goodsId, goodsName, saleTimekey, goodsdescrip) values (sq_transactionId.Nextval, (select round(dbms_random.value(10000, 100000000)) from dual), (select dbms_random.string('a', 25) from dual), sysdate, (select dbms_random.string('a', 85) from dual)); commit; exception when others then rollback; v_sqlcode := sqlcode; v_sqlerrm := substr(sqlerrm,1,100); insert into sale_exception values(sysdate,v_sqlcode,v_sqlerrm); commit; end p_sales;
STEP5:建立job,定時向sales表插入數據。(屢次執行,能夠建立多個job向表裏插入數據,這裏我執行了10次,即由10個job每隔5s向sales表裏面插入數據)
declare job1 number; begin sys.dbms_job.submit(job => job1, what => 'p_sales;', next_date => sysdate, interval => 'sysdate + 5/(1440*60)'); --每隔5s向sales表插入一筆隨機數據 commit; end; /
STEP6:查看sales表的數據信息。查看sales表的數據及各個分區的數據
select count(*) from sales; select count(*) from sales partition(part_100); select count(*) from sales partition(part_200); select count(*) from sales partition(part_300); select count(*) from sales partition(part_400); select count(*) from sales partition(part_500); select count(*) from sales partition(part_600); select count(*) from sales partition(part_700); select count(*) from sales partition(part_800); select count(*) from sales partition(part_900); select count(*) from sales partition(part_max);
STEP7:確認索引的狀態
查看dba_indexes,發現index狀態爲N/A:
SQL> select owner,table_name,index_name,uniqueness,status from dba_indexes i 2 where i.owner = 'LIJIAMAN' and i.table_name = 'SALES'; OWNER TABLE_NAME INDEX_NAME UNIQUENESS STATUS ------------------------------ ------------------------------ ------------------------------ ---------- -------- LIJIAMAN SALES PK_SALES_TRANSACTIONID UNIQUE N/A LIJIAMAN SALES GOODSID NONUNIQUE N/A
分區索引狀態須要從dba_ind_partitions查看:
SQL> select index_owner,index_name,partition_name,status from dba_ind_partitions i 2 where index_owner = 'LIJIAMAN' and index_name in('PK_SALES_TRANSACTIONID','GOODSID'); INDEX_OWNER INDEX_NAME PARTITION_NAME STATUS ------------------------------ ------------------------------ ------------------------------ -------- LIJIAMAN GOODSID PART_100 USABLE LIJIAMAN GOODSID PART_200 USABLE LIJIAMAN GOODSID PART_300 USABLE LIJIAMAN GOODSID PART_400 USABLE LIJIAMAN GOODSID PART_500 USABLE LIJIAMAN GOODSID PART_600 USABLE LIJIAMAN GOODSID PART_700 USABLE LIJIAMAN GOODSID PART_800 USABLE LIJIAMAN GOODSID PART_900 USABLE LIJIAMAN GOODSID PART_MAX USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_100 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_200 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_300 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_400 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_500 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_600 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_700 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_800 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_900 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_MAX USABLE 20 rows selected
經過最後的STATUS列,能夠看到全部局部分區索引都是可用的。
STEP8:再次查看各分區的數據量
SQL> select count(*) from sales; --整個表有1244筆數據 COUNT(*) ---------- 1244 SQL> select count(*) from sales partition(part_max); --part_max分區有375筆數據 COUNT(*) ---------- 375
STEP9:執行split分區操做
在上一步,max分區已經有375筆數據了,若是按照100大小做爲一個分區,那麼數據能夠存放到4個分區裏面。執行split分區操做。
alter table sales split partition part_max at (1000) into (partition part_1000,partition part_max); alter table sales split partition part_max at (1100) into (partition part_1100,partition part_max); alter table sales split partition part_max at (1200) into (partition part_1200,partition part_max); alter table sales split partition part_max at (1300) into (partition part_1300,partition part_max); alter table sales split partition part_max at (1400) into (partition part_1400,partition part_max); alter table sales split partition part_max at (1500) into (partition part_1500,partition part_max);
STEP10:再次執行step7,查看分區索引的狀態
SQL> select owner,table_name,index_name,uniqueness,status from dba_indexes i 2 where i.owner = 'LIJIAMAN' and i.table_name = 'SALES'; OWNER TABLE_NAME INDEX_NAME UNIQUENESS STATUS ------------------------------ ------------------------------ ------------------------------ ---------- -------- LIJIAMAN SALES PK_SALES_TRANSACTIONID UNIQUE N/A LIJIAMAN SALES GOODSID NONUNIQUE N/A
查看各個索引分區的狀態:
14:44:42 SQL> select index_owner,index_name,partition_name,status from dba_ind_partitions i 2 where index_owner = 'LIJIAMAN' and index_name in('PK_SALES_TRANSACTIONID','GOODSID'); INDEX_OWNER INDEX_NAME PARTITION_NAME STATUS ------------------------------ ------------------------------ ------------------------------ -------- LIJIAMAN GOODSID PART_100 USABLE LIJIAMAN GOODSID PART_1000 UNUSABLE LIJIAMAN GOODSID PART_1100 UNUSABLE LIJIAMAN GOODSID PART_1200 UNUSABLE LIJIAMAN GOODSID PART_1300 UNUSABLE LIJIAMAN GOODSID PART_1400 UNUSABLE LIJIAMAN GOODSID PART_1500 USABLE LIJIAMAN GOODSID PART_200 USABLE LIJIAMAN GOODSID PART_300 USABLE LIJIAMAN GOODSID PART_400 USABLE LIJIAMAN GOODSID PART_500 USABLE LIJIAMAN GOODSID PART_600 USABLE LIJIAMAN GOODSID PART_700 USABLE LIJIAMAN GOODSID PART_800 USABLE LIJIAMAN GOODSID PART_900 USABLE LIJIAMAN GOODSID PART_MAX USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_100 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_1000 UNUSABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_1100 UNUSABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_1200 UNUSABLE INDEX_OWNER INDEX_NAME PARTITION_NAME STATUS ------------------------------ ------------------------------ ------------------------------ -------- LIJIAMAN PK_SALES_TRANSACTIONID PART_1300 UNUSABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_1400 UNUSABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_1500 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_200 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_300 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_400 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_500 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_600 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_700 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_800 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_900 USABLE LIJIAMAN PK_SALES_TRANSACTIONID PART_MAX USABLE 32 rows selected
從上面能夠看到,2個索引的某些分區變爲「UNUSABLE」狀態,這些狀態的索引都是新split出來的,可是並不包括所有,如part_1500分區的索引是可用的。上面的索引失效會引發2個問題:
問題1:在查詢失效索引相關的分區時,因爲索引不可用,查詢速度會很是慢;
問題2:因爲存在主鍵約束(帶有惟一性索性),在失效索引相關的分區上,數據DML時會引起ORA-01502錯誤。咱們能夠從異常捕獲表sales_exception查看異常信息:
這就明白了,爲何在split分區表後,生產系統中會出現以上2中狀況。
小結:什麼狀況下split會引發index失效?
在測試時,發如今作split後,新split出來的分區,有的相關分區索引失效,而有的分區索引則不會失效。至於爲何會出現這種狀況,我的認爲是和segment的分裂有關,part_max段在split後,一個表segment分裂爲多個,一樣,對應的索引segment也分裂爲多個。分裂後,若是一個index分區存放了全部分裂出來的數據,則索引分區與表分區依然能夠對應;若是一個index分區存放不下全部數據,則會致使存在數據的索引分區與表分區數據對應不上,索引失效;若是是新分離出來的分區沒有數據,則索引與表依然對應。
通過測試,發現規律:
1.part_max沒有數據時,split操做不會引發local index失效;
2.part_max有數據:
--split出來的第一個分區【能夠存放】part_max裏面的所有數據,split後part_max爲空,則split 【不會】 引發索引失效;
--split出來的第一個分區【不可以存放】part_max裏面的數據,可是後續的分區能夠存放下part_max的數據,split後part_max爲空,split 【會】 引發索引失效。失效的索引爲:新splits出來的有數據的分區,沒有數據的分區不會失效,part_max一樣不會失效;
--split出來的所有分區【不可以存放】part_max裏面的所有數據,split後part_max不爲空,split 【會】 引發索引失效。失效的索引爲:新split的所有索引和part_max;
圖3.split表分區索引失效梳理
(五)如何對應
方案一:重建不可用的索引
SQL> ALTER INDEX [schema.]index_name REBUILD PARTITION partition_name [ONLINE];
我在出問題時重建了整個表的索引,沒想到能夠重建單個分區的索引。
方法小結:
優勢:在部分分區的local index不可用後,使用該方法能夠快速重建,快速恢復業務;
缺點:用到這種方法,說明部分local index已經不可用,業務已經出現上面2個問題。
方案二:在split時添加update indexes選項
SQL> ALTER TABLE [schema.]table_name SPLIT PARTITION partition_name AT (part_values) INTO (PARTITION part_values, PARTITION part_max) update indexes;
對於這種方法,我的最關心的問題是:
1.會不會致使local index失效;
2.若是不會致使locl index失效,在進行split時,是否存在鎖,致使DML失敗。
通過測試(測試表有2個分區,咱們對其中一個分區進行split,該分區數據量有2GB,22800000行數據),發如今進行split時會產生TX鎖,split持續了90s。在這期間DML操做hang住。查看local index的狀態,未出現不可用的索引。
方法小結:
優勢:不會形成local index不可用;
缺點:在執行操做期間會形成鎖表,若是表分區較大,持續時間將會很長,在生產中難以接受。
目前來看,對於7*24小時的系統,沒有辦法完美解決分區數據分離的問題,只有隨時關注數據增加,儘可能不要讓數據進入part_max分區。接下來再找一找資料,爭取對業務影響最小。