split分割槽操作導致的librarycachelock

redhouser發表於2014-11-03


1. 問題
1.1 問題
在Oracle10.2.0.5.0版本中,使用split新增分割槽時,由於被split分割槽缺少統計資訊,split操作中特定遞迴sql對存在全域性索引的表進行索引範圍掃描,導致大量邏輯IO,執行時間變長,進而導致大量insert會話等待事件library cache lock。

1.2 解決思路
思路1:每次對分割槽P_CSTTRANMONI_DEFAULT做split前收集該分割槽的統計資訊:
exec dbms_stats.gather_table_stats('MCIS','CSTTRANMONI',partname=>'P_CSTTRANMONI_DEFAULT ');
==>可行

思路2:將全域性索引設定為invisible,避免遞迴sql使用索引
==>測試表明:技術上可行

思路3:將CSTTRANMONI表上的Global索引改成local索引。
==>僅技術上可行,調整索引涉及應用SQL調整和驗證

思路4:收集並鎖定預設分割槽統計資訊
==>測試表明:split操作後,預設分割槽統計資訊丟失

思路5:會話級設定引數_optimizer_ignore_hints=true
經網友測試驗證,對於客戶提交的sql,該引數有效;但對遞迴sql,該引數無效。後又透過在會話級設定optimizer_feature_enable='8.0.0'實現。
---參考:http://www.oracledatabase12g.com/archives/resolve-split-partition-recursive-sql-hint-performance-issue.html


2. Oracle11.2.0.1問題重現
2.1 版本
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
PL/SQL Release 11.2.0.1.0 - Production
CORE    11.2.0.1.0      Production
TNS for Linux: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 - Production

2.2 建立測試表
create table tmp_part(x int,y int)
partition by range
(x)
(
partition p_1 values less than (1),
partition p_2 values less than (2),
partition p_3 values less than (3),
partition p_4 values less than (4),
partition p_default values less than (maxvalue)
);

create index idx_tmp_part on tmp_part(x);

insert into tmp_part(x,y)
select 1,rownum from dual connect by level<100000;

insert into tmp_part(x,y)
select 2,rownum from dual connect by level<100000;

insert into tmp_part(x,y)
select 3,rownum from dual connect by level<100000;
commit;

begin
   dbms_stats.gather_table_stats(user,'TMP_PART');
end;
/

select index_name,status from user_indexes where index_name='IDX_TMP_PART';
INDEX_NAME                     STATUS
------------------------------ --------
IDX_TMP_PART                   VALID

select count(*) from tmp_part partition(p_default);
  COUNT(*)
----------
         0

2.3 P_DEFAULT分割槽有統計資訊情況
--split分割槽前
select table_name,
       partition_name,
       to_char(last_analyzed,'YYYYMMDD HH24MI'),
       num_rows,
       stattype_locked,
       global_stats,
       user_stats
  from user_tab_statistics
 where table_name = 'TMP_PART';
TABLE_NAME PARTITION_ TO_CHAR(LAST_   NUM_ROWS STATT GLO USE
---------- ---------- ------------- ---------- ----- --- ---
TMP_PART              20141102 0314     299997       YES NO
TMP_PART   P_1        20141102 0314          0       YES NO
TMP_PART   P_2        20141102 0314      99999       YES NO
TMP_PART   P_3        20141102 0314      99999       YES NO
TMP_PART   P_4        20141102 0314      99999       YES NO
TMP_PART   P_DEFAULT  20141102 0314          0       YES NO

set timing on
oradebug setmypid
oradebug event 10046 trace name context forever,level 12;

alter table mh.tmp_part
split partition p_default at (5) into (partition p_5,partition p_default) update indexes;
Elapsed: 00:00:01.21

--分析跟蹤檔案
tkprof orcl_ora_5466.trc orcl_ora_5466.tkf
  
--獲取分割槽排他鎖  
SQL ID: bgy16fj5td0yz
Plan Hash: 0
LOCK TABLE "MH"."TMP_PART" PARTITION ("P_DEFAULT")  IN EXCLUSIVE MODE  NOWAIT

--原始SQL
alter table mh.tmp_part
split partition p_default at (5) into (partition p_5,partition p_default)
update indexes

--遞迴SQL1
SQL ID: 85698vrqj1mh6
Plan Hash: 1105471716
select /*+ FIRST_ROWS(1) PARALLEL("TMP_PART", 1) */ 1
from
 "MH"."TMP_PART" PARTITION ("P_DEFAULT")  where ( (  (  ( "X" < 5 )  )  ) )
  and rownum < 2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=3 pr=0 pw=0 time=0 us)
      0   PARTITION RANGE SINGLE PARTITION: 5 5 (cr=3 pr=0 pw=0 time=0 us cost=2 size=13 card=1)
      0    TABLE ACCESS FULL TMP_PART PARTITION: 5 5 (cr=3 pr=0 pw=0 time=0 us cost=2 size=13 card=1)
==》待split分割槽有統計資訊時,使用了分割槽全表掃描,邏輯讀3很少

--遞迴SQL2
SQL ID: 2uvud5a8z8dpg
Plan Hash: 1105471716
select /*+ FIRST_ROWS(1) PARALLEL("TMP_PART", 1) */ 1
from
 "MH"."TMP_PART" PARTITION ("P_DEFAULT")  where ( (  (  ( "X" >= 5 OR "X" IS
  NULL  )  )  ) ) and rownum < 2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=3 pr=0 pw=0 time=0 us)
      0   PARTITION RANGE SINGLE PARTITION: 5 5 (cr=3 pr=0 pw=0 time=0 us cost=2 size=13 card=1)
      0    TABLE ACCESS FULL TMP_PART PARTITION: 5 5 (cr=3 pr=0 pw=0 time=0 us cost=2 size=13 card=1)


2.4 P_DEFAULT分割槽無統計資訊情況
--split分割槽前
select table_name,
       partition_name,
       to_char(last_analyzed,'YYYYMMDD HH24MI'),
       num_rows,
       stattype_locked,
       global_stats,
       user_stats
  from user_tab_statistics
 where table_name = 'TMP_PART';
TABLE_NAME PARTITION_ TO_CHAR(LAST_   NUM_ROWS STATT GLO USE
---------- ---------- ------------- ---------- ----- --- ---
TMP_PART              20141102 0314     299997       YES NO
TMP_PART   P_1        20141102 0314          0       YES NO
TMP_PART   P_2        20141102 0314      99999       YES NO
TMP_PART   P_3        20141102 0314      99999       YES NO
TMP_PART   P_4        20141102 0314      99999       YES NO
TMP_PART   P_5                                       NO  NO
TMP_PART   P_DEFAULT                                 NO  NO

set timing on
oradebug setmypid
oradebug event 10046 trace name context forever,level 12;

alter table mh.tmp_part
split partition p_default at (6) into (partition p_6,partition p_default) update indexes;


--分析跟蹤檔案
  
--獲取分割槽排他鎖  
SQL ID: bgy16fj5td0yz
Plan Hash: 0
LOCK TABLE "MH"."TMP_PART" PARTITION ("P_DEFAULT")  IN EXCLUSIVE MODE  NOWAIT

--原始SQL
alter table mh.tmp_part
split partition p_default at (6) into (partition p_6,partition p_default)
update indexes

--遞迴SQL1
SQL ID: 4hfsvkycsgxwd
Plan Hash: 82649502
select /*+ FIRST_ROWS(1) PARALLEL("TMP_PART", 1) */ 1
from
 "MH"."TMP_PART" PARTITION ("P_DEFAULT")  where ( (  (  ( "X" < 6 )  )  ) )
  and rownum < 2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=1206 pr=0 pw=0 time=0 us)
      0   INDEX RANGE SCAN IDX_TMP_PART (cr=1206 pr=0 pw=0 time=0 us cost=3 size=3 card=1)(object id 74930)
==》待split分割槽無統計資訊時,使用了索引範圍掃描,邏輯讀1206較高


--遞迴SQL2
SQL ID: 1vy0ywnpz3r0r
Plan Hash: 1105471716
select /*+ FIRST_ROWS(1) PARALLEL("TMP_PART", 1) */ 1
from
 "MH"."TMP_PART" PARTITION ("P_DEFAULT")  where ( (  (  ( "X" >= 6 OR "X" IS
  NULL  )  )  ) ) and rownum < 2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=3 pr=0 pw=0 time=0 us)
      0   PARTITION RANGE SINGLE PARTITION: 6 6 (cr=3 pr=0 pw=0 time=0 us cost=35 size=3 card=1)
      0    TABLE ACCESS FULL TMP_PART PARTITION: 6 6 (cr=3 pr=0 pw=0 time=0 us cost=35 size=3 card=1)

 

2.5 透過鎖定分割槽統計資訊,試圖避免split操作後統計資訊缺失,是否可行?
==>split操作後P_DEFAULT分割槽統計資訊丟失,不可行

--當前分割槽統計資訊
select table_name,
       partition_name,
       to_char(last_analyzed,'YYYYMMDD HH24MI'),
       num_rows,
       stattype_locked,
       global_stats,
       user_stats
  from user_tab_statistics
 where table_name = 'TMP_PART';
TABLE_NAME PARTITION_ TO_CHAR(LAST_   NUM_ROWS STATT GLO USE
---------- ---------- ------------- ---------- ----- --- ---
TMP_PART              20141102 0314     299997       YES NO
TMP_PART   P_1        20141102 0314          0       YES NO
TMP_PART   P_2        20141102 0314      99999       YES NO
TMP_PART   P_3        20141102 0314      99999       YES NO
TMP_PART   P_4        20141102 0314      99999       YES NO
TMP_PART   P_5                                       NO  NO
TMP_PART   P_6                                       NO  NO
TMP_PART   P_DEFAULT                                 NO  NO

--複製並鎖定P_DEFAULT分割槽統計資訊
begin
  dbms_stats.copy_table_stats(user,'TMP_PART','P_1','P_DEFAULT');
  dbms_stats.lock_partition_stats(user,'TMP_PART','P_DEFAULT');
end;
/

--當前分割槽統計資訊
select table_name,
       partition_name,
       to_char(last_analyzed,'YYYYMMDD HH24MI'),
       num_rows,
       stattype_locked,
       global_stats,
       user_stats
  from user_tab_statistics
 where table_name = 'TMP_PART';
TABLE_NAME PARTITION_ TO_CHAR(LAST_   NUM_ROWS STATT GLO USE
---------- ---------- ------------- ---------- ----- --- ---
TMP_PART              20141102 0314     299997       YES NO
TMP_PART   P_1        20141102 0314          0       YES NO
TMP_PART   P_2        20141102 0314      99999       YES NO
TMP_PART   P_3        20141102 0314      99999       YES NO
TMP_PART   P_4        20141102 0314      99999       YES NO
TMP_PART   P_5                                       NO  NO
TMP_PART   P_6                                       NO  NO
TMP_PART   P_DEFAULT  20141102 0314          0 ALL   YES NO
==>P_DEFAULT分割槽統計資訊被鎖定,行數為0


alter system flush shared_pool;
set timing on
oradebug setmypid
oradebug event 10046 trace name context forever,level 12;

alter table mh.tmp_part
split partition p_default at (7) into (partition p_7,partition p_default) update indexes;

--split分割槽操作後
select table_name,
       partition_name,
       to_char(last_analyzed,'YYYYMMDD HH24MI'),
       num_rows,
       stattype_locked,
       global_stats,
       user_stats
  from user_tab_statistics
 where table_name = 'TMP_PART';
TABLE_NAME PARTITION_ TO_CHAR(LAST_   NUM_ROWS STATT GLO USE
---------- ---------- ------------- ---------- ----- --- ---
TMP_PART              20141102 0314     299997       YES NO
TMP_PART   P_1        20141102 0314          0       YES NO
TMP_PART   P_2        20141102 0314      99999       YES NO
TMP_PART   P_3        20141102 0314      99999       YES NO
TMP_PART   P_4        20141102 0314      99999       YES NO
TMP_PART   P_5                                       NO  NO
TMP_PART   P_6                                       NO  NO
TMP_PART   P_7                                       NO  NO
TMP_PART   P_DEFAULT                           ALL   NO  NO
==>split分割槽後,P_DEFAULT上的統計資訊沒有了,雖然還處於鎖定狀態


2.6 透過設定全域性索引為不可見,可以避免全域性索引掃描

--設定全域性索引invisible
alter index idx_tmp_part invisible;

--索引狀態
select index_name,status,VISIBILITY from user_indexes where index_name='IDX_TMP_PART';
INDEX_NAME                     STATUS   VISIBILIT
------------------------------ -------- ---------
IDX_TMP_PART                   VALID    INVISIBLE

show parameter invisible
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_use_invisible_indexes      boolean     FALSE


begin
  dbms_stats.unlock_partition_stats(user,'TMP_PART','P_DEFAULT');
end;
/

--分割槽當前統計資訊,P_DEFAULT分割槽無統計資訊
select table_name,
       partition_name,
       to_char(last_analyzed,'YYYYMMDD HH24MI'),
       num_rows,
       stattype_locked,
       global_stats,
       user_stats
  from user_tab_statistics
 where table_name = 'TMP_PART';
TABLE_NAME PARTITION_ TO_CHAR(LAST_   NUM_ROWS STATT GLO USE
---------- ---------- ------------- ---------- ----- --- ---
TMP_PART              20141102 0314     299997       YES NO
TMP_PART   P_1        20141102 0314          0       YES NO
TMP_PART   P_2        20141102 0314      99999       YES NO
TMP_PART   P_3        20141102 0314      99999       YES NO
TMP_PART   P_4        20141102 0314      99999       YES NO
TMP_PART   P_5        20141102 1920          0       YES NO
TMP_PART   P_6        20141102 1920          0       YES NO
TMP_PART   P_7        20141102 1920          0       YES NO
TMP_PART   P_DEFAULT                                 NO  NO


--split 分割槽
alter system flush shared_pool;
set timing on
oradebug setmypid
oradebug event 10046 trace name context forever,level 12;

alter table mh.tmp_part
split partition p_default at (8) into (partition p_8,partition p_default) update indexes;


--分析跟蹤檔案

--獲取分割槽排他鎖
SQL ID: bgy16fj5td0yz
Plan Hash: 0
LOCK TABLE "MH"."TMP_PART" PARTITION ("P_DEFAULT")  IN EXCLUSIVE MODE  NOWAIT

--原始SQL
alter table mh.tmp_part
split partition p_default at (8) into (partition p_8,partition p_default) update indexes

--遞迴SQL1
SQL ID: bhbt564jk3dct
Plan Hash: 1105471716
select /*+ FIRST_ROWS(1) PARALLEL("TMP_PART", 1) */ 1
from
 "MH"."TMP_PART" PARTITION ("P_DEFAULT")  where ( (  (  ( "X" < 8 )  )  ) )
  and rownum < 2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=3 pr=1 pw=0 time=0 us)
      0   PARTITION RANGE SINGLE PARTITION: 8 8 (cr=3 pr=1 pw=0 time=0 us cost=2 size=3 card=1)
      0    TABLE ACCESS FULL TMP_PART PARTITION: 8 8 (cr=3 pr=1 pw=0 time=0 us cost=2 size=3 card=1)


--遞迴SQL2
SQL ID: 99t3mrypzgbaf
Plan Hash: 1105471716
select /*+ FIRST_ROWS(1) PARALLEL("TMP_PART", 1) */ 1
from
 "MH"."TMP_PART" PARTITION ("P_DEFAULT")  where ( (  (  ( "X" >= 8 OR "X" IS
  NULL  )  )  ) ) and rownum < 2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=3 pr=0 pw=0 time=0 us)
      0   PARTITION RANGE SINGLE PARTITION: 8 8 (cr=3 pr=0 pw=0 time=0 us cost=27 size=3 card=1)
      0    TABLE ACCESS FULL TMP_PART PARTITION: 8 8 (cr=3 pr=0 pw=0 time=0 us cost=27 size=3 card=1)

 

3,Oracle10.2.0.1測試
在此版本中,無論P_DEFAULT是否有統計資訊,split操作的遞迴SQL都透過索引範圍掃描執行,即使收集P_DEFAULT統計資訊還會存在效能問題。

3.1 版本
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
PL/SQL Release 10.2.0.1.0 - Production
CORE 10.2.0.1.0 Production
TNS for Linux: Version 10.2.0.1.0 - Production
NLSRTL Version 10.2.0.1.0 - Production

3.2 建立分割槽表
create table tmp_part(x int,y int)
partition by range(x)
(
partition p_1 values less than (1),
partition p_2 values less than (2),
partition p_3 values less than (3),
partition p_4 values less than (4),
partition p_default values less than (maxvalue)
);

create index idx_tmp_part on tmp_part(x);

insert into tmp_part(x,y)
select 1,rownum from dual connect by level<100000;

insert into tmp_part(x,y)
select 2,rownum from dual connect by level<100000;

insert into tmp_part(x,y)
select 3,rownum from dual connect by level<100000;
commit;

begin
  dbms_stats.gather_table_stats(user,'TMP_PART');
end;
/


select index_name,status from user_indexes where index_name='IDX_TMP_PART';
INDEX_NAME                     STATUS
------------------------------ --------
IDX_TMP_PART                   VALID


select table_name,
       partition_name,
       to_char(last_analyzed, 'YYYYMMDD HH24MI'),
       num_rows,
       stattype_locked,
       global_stats,
       user_stats
  from user_tab_statistics
 where table_name = 'TMP_PART';
TABLE_NAME   PARTITION_NAME  TO_CHAR(LAST_ANALYZED,'YYYYMMD   NUM_ROWS STATTYPE_LOCKED GLOBAL_STATS USER_STATS
------------ --------------- ------------------------------ ---------- --------------- ------------ ----------
TMP_PART                     20141102 1413                      299997                 YES          NO
TMP_PART     P_1             20141102 1413                           0                 YES          NO
TMP_PART     P_2             20141102 1413                       99999                 YES          NO
TMP_PART     P_3             20141102 1413                       99999                 YES          NO
TMP_PART     P_4             20141102 1413                       99999                 YES          NO
TMP_PART     P_DEFAULT       20141102 1413                           0                 YES          NO


select count(*) from tmp_part partition(p_default);
  COUNT(*)
----------
         0

3.3 P_DEFAULT分割槽有統計資訊情況

oradebug setmypid;
oradebug event 10046 trace name context forever,level 12;

alter table bnet.tmp_part
split partition p_default at (5) into (partition p_5,partition p_default) update indexes;
Elapsed: 00:00:09.55


--分析跟蹤檔案
tkprof bnet_ora_14014.trc bnet_ora_14014.tkf


--原始SQL
alter table bnet.tmp_part
split partition p_default at (5) into (partition p_5,partition p_default) update indexes;

--遞迴SQL1:
select /*+ FIRST_ROWS PARALLEL("TMP_PART", 1) */ 1
from
 "bnet"."TMP_PART" PARTITION ("P_DEFAULT")  where ( (  (  ( "X" < 5 )  )  )
  ) and rownum < 2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=1019 pr=1019 pw=0 time=5297158 us)
      0   TABLE ACCESS BY GLOBAL INDEX ROWID TMP_PART PARTITION: 5 5 (cr=1019 pr=1019 pw=0 time=5297147 us)
 299997    INDEX RANGE SCAN IDX_TMP_PART (cr=1019 pr=1019 pw=0 time=2873717 us)(object id 257942)
==>該SQL使用了索引掃描,即使P_DEFAULT分割槽上有統計資訊,邏輯讀1019成本較高,讀取299997行後發現沒有符合條件的資料

--遞迴SQL2:
select /*+ FIRST_ROWS PARALLEL("TMP_PART", 1) */ 1
from
 "bnet"."TMP_PART" PARTITION ("P_DEFAULT")  where ( (  (  ( "X" >= 5 OR "X"
  IS NULL  )  )  ) ) and rownum < 2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=3 pr=1 pw=0 time=6413 us)
      0   PARTITION RANGE SINGLE PARTITION: 5 5 (cr=3 pr=1 pw=0 time=6405 us)
      0    TABLE ACCESS FULL TMP_PART PARTITION: 5 5 (cr=3 pr=1 pw=0 time=6379 us)


3.4 P_DEFAULT分割槽無統計資訊情況
--split操作前
select table_name,
       partition_name,
       to_char(last_analyzed, 'YYYYMMDD HH24MI'),
       num_rows,
       stattype_locked,
       global_stats,
       user_stats
  from user_tab_statistics
 where table_name = 'TMP_PART';
TABLE_NAME  PARTITION_NAME   TO_CHAR(LAST_ANALYZED,'YYYYMMD   NUM_ROWS STATTYPE_LOCKED GLOBAL_STATS USER_STATS
----------- ---------------- ------------------------------ ---------- --------------- ------------ ----------
TMP_PART                     20141102 1413                      299997                 YES          NO
TMP_PART    P_1              20141102 1413                           0                 YES          NO
TMP_PART    P_2              20141102 1413                       99999                 YES          NO
TMP_PART    P_3              20141102 1413                       99999                 YES          NO
TMP_PART    P_4              20141102 1413                       99999                 YES          NO
TMP_PART    P_5                                                                        NO           NO
TMP_PART    P_DEFAULT                                                                  NO           NO
==> P_5、P_DEFAULT缺少統計資訊

--為避免前面執行計劃的影響:
alter system flush shared_pool;

set timing on
oradebug setmypid;
oradebug event 10046 trace name context forever,level 12;

alter table bnet.tmp_part
split partition p_default at (6) into (partition p_6,partition p_default) update indexes;
Elapsed: 00:00:02.29


--跟蹤檔案
tkprof bnet_ora_14391.trc bnet_ora_14391.tkf

--原始SQL
alter table bnet.tmp_part
split partition p_default at (6) into (partition p_6,partition p_default) update indexes

--遞迴SQL1:
select /*+ FIRST_ROWS PARALLEL("TMP_PART", 1) */ 1
from
 "bnet"."TMP_PART" PARTITION ("P_DEFAULT")  where ( (  (  ( "X" < 6 )  )  )
  ) and rownum < 2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=1021 pr=1 pw=0 time=227276 us)
      0   TABLE ACCESS BY GLOBAL INDEX ROWID TMP_PART PARTITION: 6 6 (cr=1021 pr=1 pw=0 time=227267 us)
 299997    INDEX RANGE SCAN IDX_TMP_PART (cr=1021 pr=1 pw=0 time=1500093 us)(object id 257942)


--遞迴SQL2:
select /*+ FIRST_ROWS PARALLEL("TMP_PART", 1) */ 1
from
 "bnet"."TMP_PART" PARTITION ("P_DEFAULT")  where ( (  (  ( "X" >= 6 OR "X"
  IS NULL  )  )  ) ) and rownum < 2

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  COUNT STOPKEY (cr=3 pr=1 pw=0 time=235 us)
      0   PARTITION RANGE SINGLE PARTITION: 6 6 (cr=3 pr=1 pw=0 time=226 us)
      0    TABLE ACCESS FULL TMP_PART PARTITION: 6 6 (cr=3 pr=1 pw=0 time=203 us)

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/18922393/viewspace-1318531/,如需轉載,請註明出處,否則將追究法律責任。

相關文章