Oracle直接路徑加載--append的深度解析

轉自:http://www.linuxidc.com/Linux/2013-02/79108.htm

 ㈠ 直接路徑加載和buffer cache

直接路徑插入的數據不經過buffer cache,從PGA直接把數據格式化成Oracle

然後由普通的Oracle Server Process進程把數據塊寫入數據文件

因爲不經過buffer cache,所以不需要DBWn介入

假如有表a,現要將a中的數據插入表b,在普通的插入下,需先將a的數據塊I/O到buffer cache

在buffer cache中從a的塊中讀出行,插進b的塊中

此時,b的塊就都變成了髒塊,再等待DBWn把他們flush到數據文件

因此,普通插入後,a表和b表的塊都會在buffer cache中出現

而直接路徑插入,將a表的數據塊I/O到buffer cache,讀出行,直接寫進b表所在的數據文件

插入完成後,除了表頭塊外,b表的數據塊並未在buffer cache中出現過

測試:

hr@ORCL> create table a (id number,name varchar2(10));

Table created.

hr@ORCL> create table b (id number,name varchar2(10));

Table created.

hr@ORCL> insert into a values(1,'aa');

1 row created.

hr@ORCL> insert into a values(2,'bb');

1 row created.

hr@ORCL> insert into a values(3,'cc');

1 row created.

hr@ORCL> insert into a values(4,'dd');

1 row created.

hr@ORCL> commit;

Commit complete.

hr@ORCL> select dbms_rowid.rowid_relative_fno(rowid),dbms_rowid.rowid_block_number(rowid) from a;

DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------ ------------------------------------
                                  4                                  508
                                  4                                  508
                                  4                                  508
                                  4                                  508
--現在a表有4行,佔用塊508,而目前b表還木有數據
  
--將buffer cache清空

hr@ORCL> alter system flush buffer_cache;

System altered.

--先用直接路徑插入,從a表向b表插入數據

hr@ORCL> insert /*+ append */ into b select * from a;

4 rows created.

hr@ORCL> commit;

Commit complete.

--使用v$bh查看buffer cache中的塊

hr@ORCL> select file#,block# from v$bh where objd=(select data_object_id from user_objects where object_name='A');

    FILE#    BLOCK#
---------- ----------
        4        508    ←←當前包含數據的塊
        4        508    ←←當前包含數據的塊
        4        511
        4        511
        4        506
        4        509
        4        509
        4        512
        4        512
        4        507
        4        507
        4        510
        4        510
        4        505

14 rows selected.

--這是因爲對a表進行全表掃,a表中低高水位點下所有的塊都被讀進buffer cache,這其中當然也包括508了

hr@ORCL> select file#,block# from v$bh where objd=(select data_object_id from user_objects where object_name='B');

    FILE#    BLOCK#
---------- ----------
        4      2571
        4      2569
        4      2570

hr@ORCL> select dbms_rowid.rowid_relative_fno(rowid),dbms_rowid.rowid_block_number(rowid) from b;

DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------ ------------------------------------
                                  4                                2572
                                  4                                2572
                                  4                                2572
                                  4                                2572
--上面兩個查詢可以看到,b表中的數據佔用地2572塊,但是,直接路徑插入後,2572並沒有被調入buffer cache
--buffer cache中只有2569 2570 2571
--其中2571是段頭塊(select header_file,header_block from dba_segments where segment_name='B') 
--2570 2569則是L1 L2這兩個位圖塊
--接下來使用普通插入
hr@ORCL> alter system flush buffer_cache;

System altered.

hr@ORCL> insert into b select * from a;

4 rows created.

hr@ORCL> commit;

Commit complete.

hr@ORCL> select file#,block# from v$bh where objd=(select data_object_id from user_objects where object_name='B');

    FILE#    BLOCK#
---------- ----------
        4      2571
        4      2574
        4      2569
        4      2575
        4      2570
        4      2570
        4      2573
        4      2576  ←←本次普通插入的數據所在的塊

8 rows selected.

hr@ORCL> select dbms_rowid.rowid_relative_fno(rowid),dbms_rowid.rowid_block_number(rowid) from b;

DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------ ------------------------------------
                                  4                                2572
                                  4                                2572
                                  4                                2572
                                  4                                2572
                                  4                                2576
                                  4                                2576
                                  4                                2576
                                  4                                2576

8 rows selected.

從上面的實驗可以證明,普通插入,要先將數據塊傳進buffer cache

這是Oracle通常修改數據的方式,不對數據文件直接進行修改,而是在內存中完成修改,再由日誌提供保護

對於小量的修改,這是種可取的方法,但對於大數據交易,直接路徑將可以提供更好的性能

還有就是,直接路徑加載是在高水位之上完成的插入動作,因此無論高水位下有多少空閒塊都會被忽略,段空間將會隨之增大

 ㈡ 直接路徑加載和undo

差別比較明顯的是undo(直接路徑幾乎沒有undo),倆者redo差不多(普通插入稍微多點redo,因爲Oracle需要redo來保護undo)

直接路徑用HWM回滾,普通插入用undo回滾

--再次向b表使用直接路徑插入

hr@ORCL> insert /*+ append */ into b select id+4,name from a;

4 rows created.

--查看事務信息

hr@ORCL> select xidusn,xidslot,xidsqn,ubafil,ubablk,ubasqn from v$transaction;

    XIDUSN    XIDSLOT    XIDSQN    UBAFIL    UBABLK    UBASQN
---------- ---------- ---------- ---------- ---------- ----------
        10        23        314          0          0          0
--因爲當前只有一個事務,因此選擇v$transaction視圖時沒有加條件
--從上面的顯示結果可以看到,ubafil ubablk爲0,也就是,此事務沒有對應的回滾塊
--只有在回滾段頭的事務表中佔用了一行而已

那麼直接路徑插入是如何提供回滾的呢?觀察b表的HWM的變化,就可以解答這個問題

--查找b表的HWM

hr@ORCL> select header_file,header_block from dba_segments where segment_name='B';

HEADER_FILE HEADER_BLOCK
----------- ------------
          4        2571

hr@ORCL> alter session set tracefile_identifier='hr_2571';

Session altered.

hr@ORCL> alter system dump datafile 4 block 2571;

System altered.

--trc文件摘入如下:

  Extent Control Header
  -----------------------------------------------------------------
  Extent Header:: spare1: 0      spare2: 0      #extents: 2      #blocks: 16
                  last map  0x00000000  #maps: 0      offset: 2716
      Highwater::  0x01000a11  ext#: 0      blk#: 8      ext size: 8
  #blocks in seg. hdr's freelists: 0
  #blocks below: 8
  mapblk  0x00000000  offset: 0
                  Unlocked
  --------------------------------------------------------
  Low HighWater Mark :
      Highwater::  0x01000a11  ext#: 0      blk#: 8      ext size: 8

--高水位點是4號文件2577號塊
--提交後查看直接路徑插入到哪個塊中

hr@ORCL> select dbms_rowid.rowid_relative_fno(rowid),dbms_rowid.rowid_block_number(rowid) from b where id>=8;

DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------ ------------------------------------
                                  4                                2577
--通過上面的查詢,本次直接路徑插入,數據被存進2577號塊,而提交的HWM正是2577

|--------------|--------|--------|----------
|數據塊 ...... |2575|2576|2577|2578|...
|--------------|--------|--------|----------
                          ↑
                          ↑
                          ↑
                        此處是高水位點,直接路徑插入從此塊開始分配空間
--直接路徑插入,是在高水位點之上分配臨時段,將數據插入時進入此臨時段
--在提交後將高水位點提升至臨時段之上
--現在已經提交,再來看高水位點的信息

hr@ORCL> alter system dump datafile 4 block 2571;

System altered.

--trc文件摘入如下:

  Extent Control Header
  -----------------------------------------------------------------
  Extent Header:: spare1: 0      spare2: 0      #extents: 2      #blocks: 16
                  last map  0x00000000  #maps: 0      offset: 2716
      Highwater::  0x01000a12  ext#: 1      blk#: 1      ext size: 8
  #blocks in seg. hdr's freelists: 0
  #blocks below: 9
  mapblk  0x00000000  offset: 1
                  Unlocked
  --------------------------------------------------------
  Low HighWater Mark :
      Highwater::  0x01000a12  ext#: 1      blk#: 1      ext size: 8

--第一次dump是Highwater::  0x01000a11,而現在是 Highwater::  0x01000a12
--高水位點升至2578塊,如下圖

|--------------|--------|--------|--------|-----
|數據塊 ......  |2575  |2576|2577|2578|
|--------------|--------|--------|--------|-----                                      
                                  ↑
                                  ↑
                                  ↑
                                高水位點上升至此處

--再試一次直接路徑插入回滾時的情況
--先猜想一下,此次插入應該插入到2578,如果提交的話,就提升高水位點到2579,如果回滾的話,保持高水位點不變

hr@ORCL> insert /*+ append */ into b select id+4,name from a;

4 rows created.

hr@ORCL> commit;

Commit complete.

--trc摘入如下:

  Extent Control Header
  -----------------------------------------------------------------
  Extent Header:: spare1: 0      spare2: 0      #extents: 2      #blocks: 16
                  last map  0x00000000  #maps: 0      offset: 2716
      Highwater::  0x01000a13  ext#: 1      blk#: 2      ext size: 8
  #blocks in seg. hdr's freelists: 0
  #blocks below: 10
  mapblk  0x00000000  offset: 1
      Disk Lock:: Locked by xid:  0x0006.012.0000018d
  --------------------------------------------------------
  Low HighWater Mark :
      Highwater::  0x01000a13  ext#: 1      blk#: 2      ext size: 8
--高水位點變爲 0x01000a13 ,也就是2579塊
--接下來是回滾,回滾則將保持高水位點不變,也就是在2579塊

hr@ORCL> insert /*+ append */ into b select id+12,name from a;

4 rows created.

hr@ORCL> rollback;

Rollback complete.

hr@ORCL> alter system dump datafile 4 block 2571;

System altered.


--trc文件摘入如下:

  Extent Control Header
  -----------------------------------------------------------------
  Extent Header:: spare1: 0      spare2: 0      #extents: 2      #blocks: 16
                  last map  0x00000000  #maps: 0      offset: 2716
      Highwater::  0x01000a13  ext#: 1      blk#: 2      ext size: 8
  #blocks in seg. hdr's freelists: 0
  #blocks below: 10
  mapblk  0x00000000  offset: 1
                  Unlocked
  --------------------------------------------------------
  Low HighWater Mark :
      Highwater::  0x01000a13  ext#: 1      blk#: 2      ext size: 8

--0x01000a13,也就是2579塊,rollback,高水位點沒有變化

在執行直接路徑加載的過程中,高水位並沒有真正提高,只有在事務提交後纔會完成這個動作,在所有維護工作完成之後表纔可以被訪問

所以,在提交之前如果想查詢這張表是不被允許的,同理可知對錶的增刪改以及merge操作也是不被允許的 

㈢ 直接路徑加載與index

直接路徑插入時,不產生表塊的回滾信息,而是依賴高水位點實現回滾

但是,如果表有索引,將會產生索引的回滾信息,而且索引的塊會被讀進buffer cache

測試:

--爲b表創建一個索引

hr@ORCL> create index idx_b on b (id);

Index created.

hr@ORCL> select file#,block# from v$bh where objd=(select data_object_id from user_objects where object_name='IDX_B');

    FILE#    BLOCK#
---------- ----------
        4      2587  ←← 段頭塊
        4      2585  ←← L1塊
        4      2588  ←← 第一個索引數據塊
        4      2586  ←← L2塊

--重啓數據庫,清空buffer cache

hr@ORCL> select file#,block# from v$bh where objd=(select data_object_id from user_objects where object_name='IDX_B');

no rows selected

hr@ORCL> insert /*+ append */ into b select * from a;

4 rows created.

hr@ORCL> select file#,block# from v$bh where objd=(select data_object_id from user_objects where object_name='IDX_B');

    FILE#    BLOCK#
---------- ----------
        4      2588
--直接路徑插入時,索引塊仍然會被調入buffer cache

hr@ORCL> select xidusn,xidslot,xidsqn,ubafil,ubablk,ubasqn from v$transaction;

    XIDUSN    XIDSLOT    XIDSQN    UBAFIL    UBABLK    UBASQN
---------- ---------- ---------- ---------- ---------- ----------
        6        41        399          2      1456        261

--並且,對於索引塊的修改,將會產生回滾信息,回滾信息保存在回滾塊1456處
--因此,索引並不會"直接路徑插入"
--因此,插入的索引數據,應該是在高水位點下:

hr@ORCL>  select header_file,header_block from dba_segments where segment_name='IDX_B';

HEADER_FILE HEADER_BLOCK
----------- ------------
          4        2587

hr@ORCL> alter system dump datafile 4 block 2587;

System altered.

--trc文件摘入如下:

  Extent Control Header
  -----------------------------------------------------------------
  Extent Header:: spare1: 0      spare2: 0      #extents: 1      #blocks: 8
                  last map  0x00000000  #maps: 0      offset: 2716
      Highwater::  0x01000a1d  ext#: 0      blk#: 4      ext size: 8
  #blocks in seg. hdr's freelists: 0
  #blocks below: 1
  mapblk  0x00000000  offset: 0
                  Unlocked
  --------------------------------------------------------
  Low HighWater Mark :
      Highwater::  0x01000a1d  ext#: 0      blk#: 4      ext size: 8

--高水位點在2589塊處,插入的索引數據在2588處,在高水位點下

Oracle官方文檔建議,如果使用直接路徑插入,向表中傳送大量數據,可先將表上的索引刪掉,插入結束後,再重新建立索引

 ㈣ 直接路徑加載和一些限制

使用直接路徑加載方法時需要注意的地方如下:

1)直接路徑加載方法不是所有插入方式都支持的,最常見的帶有value子句的insert語句就不支持

2)該技術最常用在insert into ... select ...結構的插入語句中

3)在使用直接路徑加載技術插入數據直到事務提交,其他的增、刪、改、查和merge操作是被禁止的

4)因爲是直接路徑加載,所以高水位以下的空閒數據庫塊將不被使用,可能會因此導致數據段無限擴張

5)當被操作的表上存在insert觸發器、外鍵、IOT、表使用到了聚簇技術以及表中包含LOB字段時

直接路徑加載技術是無效的,此時將會自動的轉變爲常規插入

㈤ 小結

使用直接路徑加載技術之所以能提高性能是因爲,該方法可以保證在加載數據的過程中最大限度的減少回滾數據的生成

expdp/impdp不支持直接路徑,而sqlldr支持(由參數direct=true指定,並且同時指定parallel=true,速度將會更快)

如果我們在權衡利弊成本之後能最大化該方法,提高加載速度是必然的!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章