Oracle split分區表引發ORA-01502錯誤

繼上次刪除分區表的分區遇到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分區。接下來再找一找資料,爭取對業務影響最小。

相關文章
相關標籤/搜索