(一)問題: 最近在做Oracle數據清理,在對分區表進行數據清理時,採用的方法是drop partition,刪除的過程中,沒有遇到任何問題,大概過了10分鐘,開發人員反饋部分分區表上的業務失敗。具體錯誤為: ORA-01502錯誤:索引或這類索引的分區處於不可用狀態(英文:ora-01502:i... ...
(一)問題:
最近在做Oracle數據清理,在對分區表進行數據清理時,採用的方法是drop partition,刪除的過程中,沒有遇到任何問題,大概過了10分鐘,開發人員反饋部分分區表上的業務失敗。具體錯誤為:
ORA-01502錯誤:索引或這類索引的分區處於不可用狀態(英文:ora-01502:index 'schema.index_name' or partition of such index is in unusable state)。
(二)原因分析
查看出現問題的分區表,均有一個共同點:表上以“pk_”開頭的索引為unusable狀態,以“pk_”開頭的索引是隨創建主鍵約束而創建的。當用戶在創建主鍵約束或唯一性約束的時候,會在相應的列上創建唯一性索引
經過查證,發現是在刪除分區的時候,導致分區表上的唯一性全局索引為不可用狀態,導致新的數據無法正常插入,從而引發了該錯誤。
是不是索引不可用會導致DML操作失敗呢?經過驗證,發現以下特點:
1.對於非唯一性索引,如果索引不可用,是不會影響到到DML操作的;
2.對於唯一性索引,如果索引不可用,在進行DML操作時,會觸發ORA-01502錯誤;
這裡記錄一下哪些操作會導致索引失效:
圖1.索引失效原因總結
(三)解決方案
(3.1)瞭解唯一性索引
在解決問題之前,我們來分析一下,哪些行為會創建唯一性索引(3種):
--直接創建唯一性索引。
語法為:CREATE UNIQUE INDEX index_name on table_name(col1,col2,…);
--創建主鍵約束時自動創建唯一性索引。
語法為:ALTER TABLE table_name ADD CONSTRAINT constraint_name PRIMARY KEY(col1,col2,..);
--創建唯一性約束時自動創建唯一性索引。
語法為:ALTER TABLE table_name ADD CONSTRAINT constraint_name UNIQUE(col1,col2,…);
這裡,我總結了3套方案來對應ORA-01502問題。
(3.2)方案一:刪除唯一性索引
與業務方面溝通,確認唯一性索引是否可以刪除,如果可以,直接刪除索引,刪除語法為:
SQL> DROP INDEX schema.index_name;
對於由主鍵約束或唯一性約束創建的唯一性索引,不能直接刪除
SQL> drop index lijiaman.sale_pk; drop index lijiaman.sale_pk ORA-02429: cannot drop index used for enforcement of unique/primary key
正確的方法是刪除相應的約束。
SQL> alter table sales drop constraint sale_pk; Table altered
小結:該方法簡單粗暴,前提是在約束或索引在業務方面可以刪除的情況下才能使用。
(3.3)方案二:重建唯一性索引(針對非分區表)
語法為:
SQL> ALTER INDEX [schema.]index_name REBUILD [ONLINE] [TABLESPACE tablespace name]
小結:該方法可以使索引可用。但對於分區表而言,依然存在問題:在下一次刪除分區後,索引狀態又會變為不可用。
(3.4)方案三:刪除不可用的索引,創建唯一性分區索引(針對分區表)
創建唯一性分區索引:
SQL> CREATE UNIQUE INDEX index_name on [schema.]table(col1,col2,...);
對於主鍵約束、唯一性約束,可以使用以下語法添加唯一性局部分區索引:
SQL> ALTER TABLE [schema.]table_name ADD CONSTRAINT constarint [PRIMARY KEY | UNIQUE](col1,col2) USING INDEX LOCAL TABLESPACE tablespace_name;
小結:該方法可以有效解決分區表因刪除分區導致的索引不可用問題。
附錄:模擬實驗
(1)首先模擬生產情況,創建一張表:
create table sales ( prod_id number, cust_id number, time_id date, quantity_sold number(3) ) partition by range(time_id) ( partition sales_q1_2017 values less than(to_date('1-4-2017','dd-mm-yyyy')) , partition sales_q2_2017 values less than(to_date('1-7-2017','dd-mm-yyyy')) , partition sales_q3_2017 values less than(to_date('1-10-2017','dd-mm-yyyy')) , partition sales_q4_2017 values less than(to_date('1-1-2018','dd-mm-yyyy')) );
插入數據,確保每個分區都有數據
insert into sales(prod_id,cust_id,time_id,quantity_sold)values(1,11,to_date('2017-02-01','yyyy-mm-dd'),103); insert into sales(prod_id,cust_id,time_id,quantity_sold)values(2,12,to_date('2017-06-01','yyyy-mm-dd'),103); insert into sales(prod_id,cust_id,time_id,quantity_sold)values(3,14,to_date('2017-08-01','yyyy-mm-dd'),103); insert into sales(prod_id,cust_id,time_id,quantity_sold)values(4,14,to_date('2017-12-01','yyyy-mm-dd'),103);
檢查一下資料庫的數據信息
SQL> select * from sales; --查看整個分區表的數據 PROD_ID CUST_ID TIME_ID QUANTITY_SOLD ---------- ---------- ----------- ------------- 1 11 2017/2/1 103 2 12 2017/6/1 103 3 14 2017/8/1 103 4 14 2017/12/1 103 SQL> select * from sales partition(sales_q1_2017); --查看分區“sales_q1_2017”的數據 PROD_ID CUST_ID TIME_ID QUANTITY_SOLD ---------- ---------- ----------- ------------- 1 11 2017/2/1 103
(2)由於出現ORA-01502問題時,與表相關的對象只有主鍵約束和索引。所以,我在表上創建了索引和約束,並確認了所有索引可用
alter table sales add constraint sale_pk primary key(time_id,cust_id); --創建主鍵約束 create index inx_sales_1 on sales(cust_id); --創建普通(全局)索引 create index inx_sales_2 on sales(time_id) local; --創建局部分區索引
確認索引狀態:
SQL> select owner,table_name,index_name,status 2 from dba_indexes 3 where owner = 'LIJIAMAN' 4 and table_name = 'SALES'; OWNER TABLE_NAME INDEX_NAME STATUS ------------------------------ ------------------------------ ------------------------------ -------- LIJIAMAN SALES INX_SALES_2 N/A LIJIAMAN SALES SALE_PK VALID LIJIAMAN SALES INX_SALES_1 VALID
對於索引“SALES_PK”和“INX_SALES_1”,索引狀態為可用,那”INX_SALES_2“這個索引狀態為”N/A“,這又是怎麼回事麽?經過查找資料,確認索引共有四種狀態:
- N/A :說明這個是分區索引需要查user_ind_partitions或者user_ind_subpartitions來確定每個分區是否可用;
- VAILD :說明這個索引可用;
- UNUSABLE:說明這個索引不可用;
- USABLE :說明這個索引的分區是可用的。
我們再去查看數據字典DBA_IND_PARTITIONS,確認”INX_SALES_2”的狀態,索引可用。
SQL> SELECT INDEX_OWNER,INDEX_NAME,PARTITION_NAME,STATUS FROM DBA_IND_PARTITIONS I WHERE I.INDEX_OWNER = 'LIJIAMAN' AND I.INDEX_NAME = 'INX_SALES_2'; INDEX_OWNER INDEX_NAME PARTITION_NAME STATUS ------------------------------ ------------------------------ ------------------------------ -------- LIJIAMAN INX_SALES_2 SALES_Q1_2017 USABLE LIJIAMAN INX_SALES_2 SALES_Q2_2017 USABLE LIJIAMAN INX_SALES_2 SALES_Q3_2017 USABLE LIJIAMAN INX_SALES_2 SALES_Q4_2017 USABLE
確認主鍵約束的狀態,確認可用
SQL> select owner,table_name,constraint_name,constraint_type,status,deferrable,deferred,validated 2 from dba_constraints 3 where owner = 'LIJIAMAN' 4 and table_name = 'SALES'; OWNER TABLE_NAME CONSTRAINT_NAME CONSTRAINT_TYPE STATUS DEFERRABLE DEFERRED VALIDATED --------- ------------ ----------------- --------------- -------- -------------- --------- ------------- LIJIAMAN SALES SALE_PK P ENABLED NOT DEFERRABLE IMMEDIATE VALIDATED
(3)接下來,我們模擬數據清理,刪除分區”sales_q1_2017“
SQL> alter table sales drop partition sales_q1_2017 ; Table altered
查看分區表的數據,可以看到,分區”sales_q1_2017“的數據已經隨著分區被刪除
SQL> select * from sales; PROD_ID CUST_ID TIME_ID QUANTITY_SOLD ---------- ---------- ----------- ------------- 2 12 2017/6/1 103 3 14 2017/8/1 103 4 14 2017/12/1 103
==================轉折點==========================
(4)此時,我們模擬正常的業務交易,發現如下情況
--對於insert操作,無法完成,報ORA-01502錯誤;
--對於delete操作,無法完成,報ORA-01502錯誤;
--對於update操作,如果不涉及到主鍵相關的列,則可以執行成功,如果涉及到主鍵列,報ORA-01502錯誤;
--數據插入測試,發現無法插入數據 SQL> insert into sales(prod_id,cust_id,time_id,quantity_sold)values(5,15,to_date('2017-8-01','yyyy-mm-dd'),103); insert into sales(prod_id,cust_id,time_id,quantity_sold)values(5,15,to_date('2017-8-01','yyyy-mm-dd'),103) ORA-01502: index 'LIJIAMAN.SALE_PK' or partition of such index is in unusable state --數據刪除測試,發現無法刪除數據 SQL> delete from sales where prod_id = 3 ; delete from sales where prod_id = 3 ORA-01502: index 'LIJIAMAN.SALE_PK' or partition of such index is in unusable state --數據跟新測試,測試3次,發現涉及到與主鍵相關的列,就會更新失敗,其他情況更新成功 SQL> update sales set QUANTITY_SOLD = 105 where PROD_ID = 2; 1 row updated SQL> commit; Commit complete SQL> update sales set QUANTITY_SOLD = 105 where cust_id = 12; 1 row updated SQL> commit; Commit complete SQL> update sales set cust_id = 15 where PROD_ID = 2; update sales set cust_id = 15 where PROD_ID = 2 ORA-01502: index 'LIJIAMAN.SALE_PK' or partition of such index is in unusable state
再次確認,索引的狀態,可以看到,普通索引狀態已經轉變為不可用,而局部分區索引狀態未發生改變。
SQL> select owner,table_name,index_name,status 2 from dba_indexes 3 where owner = 'LIJIAMAN' 4 and table_name = 'SALES'; OWNER TABLE_NAME INDEX_NAME STATUS ------------------------------ ------------------------------ ------------------------------ -------- LIJIAMAN SALES INX_SALES_2 N/A LIJIAMAN SALES SALE_PK UNUSABLE LIJIAMAN SALES INX_SALES_1 UNUSABLE SQL> SELECT INDEX_OWNER,INDEX_NAME,PARTITION_NAME,STATUS FROM DBA_IND_PARTITIONS I WHERE I.INDEX_OWNER = 'LIJIAMAN' AND I.INDEX_NAME = 'INX_SALES_2'; INDEX_OWNER INDEX_NAME PARTITION_NAME STATUS ------------------------------ ------------------------------ ------------------------------ -------- LIJIAMAN INX_SALES_2 SALES_Q2_2017 USABLE LIJIAMAN INX_SALES_2 SALES_Q3_2017 USABLE LIJIAMAN INX_SALES_2 SALES_Q4_2017 USABLE
主鍵約束狀態也為發生改變
OWNER TABLE_NAME CONSTRAINT_NAME CONSTRAINT_TYPE STATUS DEFERRABLE DEFERRED VALIDATED ---------- ------------ ---------------- --------------- -------- -------------- --------- ------------- LIJIAMAN SALES SALE_PK P ENABLED NOT DEFERRABLE IMMEDIATE VALIDATED
這裡,我們對deop分區前後表的信息對比做一個小結
刪除分區前 | 刪除分區後 | |
查詢(select) | 正常 | 正常 |
插入(insert) | 正常 | 無法插入 |
刪除(delete) | 正常 | 無法刪除 |
更新(update) | 正常 | 設計到主鍵相關的列,更新失敗,其他情況更新成功 |
約束狀態(constraint) | 可用 | 可用 |
索引狀態(index) | 全部可用 | 1.分區索引可用 2.主鍵約束上的唯一性索引不可用 3.普通索引不可用 |
通過對比,我們可以推測,索引不可用導致了無法正常DML操作。那麼到底是哪個索引導致的問題呢?
(5)首先測試普通索引,先重建索引INX_SALES_1
SQL> alter index lijiaman.inx_sales_1 rebuild; Index altered SQL> select owner,table_name,index_name,status 2 from dba_indexes 3 where owner = 'LIJIAMAN' 4 and table_name = 'SALES'; OWNER TABLE_NAME INDEX_NAME STATUS ------------------------------ ------------------------------ ------------------------------ -------- LIJIAMAN SALES INX_SALES_2 N/A LIJIAMAN SALES SALE_PK UNUSABLE LIJIAMAN SALES INX_SALES_1 VALID
繼續模擬DML交易,情況與刪除分區後的DML結果相同,可以確認,普通索引不可用並不會引起DML操作失敗
--數據依然無法插入 SQL> insert into sales(prod_id,cust_id,time_id,quantity_sold)values(5,15,to_date('2017-8-01','yyyy-mm-dd'),103); insert into sales(prod_id,cust_id,time_id,quantity_sold)values(5,15,to_date('2017-8-01','yyyy-mm-dd'),103) ORA-01502: index 'LIJIAMAN.SALE_PK' or partition of such index is in unusable state --數據無法刪除 SQL> delete from sales where prod_id = 3 ; delete from sales where prod_id = 3 ORA-01502: index 'LIJIAMAN.SALE_PK' or partition of such index is in unusable state --如果沒有更新到逐漸相關列,可以更新數據,否則不行 SQL> update sales set QUANTITY_SOLD = 105 where PROD_ID = 2; 1 row updated SQL> update sales set QUANTITY_SOLD = 105 where cust_id = 12; 1 row updated SQL> update sales set cust_id = 15 where PROD_ID = 2; update sales set cust_id = 15 where PROD_ID = 2 ORA-01502: index 'LIJIAMAN.SALE_PK' or partition of such index is in unusable state
(6)接著重建唯一性索引”SALE_PK”
SQL> alter index lijiaman.sale_pk rebuild; Index altered
對SALES表進行DML操作,可以正常進行
SQL> insert into sales(prod_id,cust_id,time_id,quantity_sold)values(5,15,to_date('2017-8-01','yyyy-mm-dd'),103); 1 row inserted SQL> delete from sales where prod_id = 3 ; 1 row deleted SQL> update sales set QUANTITY_SOLD = 105 where PROD_ID = 2; 1 row updated SQL> update sales set QUANTITY_SOLD = 105 where cust_id = 12; 1 row updated SQL> update sales set cust_id = 15 where PROD_ID = 2; 1 row updated SQL> commit; Commit complete
至此,我們可以大膽猜測:唯一性索引導致的ORA-01502問題。由於我們在創建索引的時候,並未直接創建唯一性索引,而是在創建主鍵約束的時候自動創建的唯一性索引,那麼到底是主鍵約束的問題,還是唯一性索引的問題?根據上面刪除分區前後約束狀態相同,而索引狀態不同,我覺得是索引的問題。繼續求證。我們新建一個表,在上面直接創建唯一性索引,不創建任何約束。
創建表test01,錄入數據
SQL> select * from test01; ID NAME AGE ---------- -------------------- ---------- 1 lijiaman 2 gegeman 25 3 xiaoman 26 4 Lijiaman 25
在“ID”列創建唯一性索引
SQL> create unique index inx_test01 on test01(id); Index created SQL> select table_owner,index_name,index_type,uniqueness,status 2 from user_indexes 3 where table_name = 'TEST01'; TABLE_OWNER INDEX_NAME INDEX_TYPE UNIQUENESS STATUS ------------------------------ ------------------------------ --------------------------- ---------- -------- LIJIAMAN INX_TEST01 NORMAL UNIQUE VALID
插入數據,沒有異常
SQL> insert into test01 values(5,'bokeyuan',22); 1 row inserted
接著將索引置為不可用狀態,然後往表裡面插入數據,出現了01502錯誤;
SQL> alter index lijiaman.inx_test01 unusable; --將索引置為不可用狀態 Index altered SQL> select table_owner,index_name,index_type,uniqueness,status 2 from user_indexes 3 where table_name = 'TEST01'; TABLE_OWNER INDEX_NAME INDEX_TYPE UNIQUENESS STATUS ------------------------------ ------------------------------ --------------------------- ---------- -------- LIJIAMAN INX_TEST01 NORMAL UNIQUE UNUSABLE SQL> insert into test01 values(5,'bokeyuan',25); --插入數據,發生ORA-01502錯誤 insert into test01 values(5,'bokeyuan',25) ORA-01502: index 'LIJIAMAN.INX_TEST01' or partition of such index is in unusable state
至此可以明確的說:ORA-01512錯誤是由於唯一性索引失效導致的。
如何解決這個問題,前面給出了3種方案,只要選擇其中一種即可,不再模擬。