天天看點

Oracle DML語句(insert,update,delete) 復原開銷估算

一、Oracle DML SQL復原邏輯簡介

 資料庫事務由1個或多個DML(insert,update,delete) SQL組成,我們知道Oracle資料庫在進行DML操作需要使用UNDO表空間來儲存事務復原的資訊,對于每種DML操作復原的UNDO資訊都不一樣,大緻如下:

insert操作很簡單,隻要儲存記錄插入到資料塊及資料塊内的槽号,復原時隻要根據資料塊号及槽号做删除就可以了。

update操作需要儲存記錄位置,還需要儲存變更的字段原内容,復原時采用原值即可。

delete操作麻煩一些,不僅要儲存記錄位置,還需要将原有記錄的内容全部儲存下來,復原時才能組成新的資料插入進去。     

如果表上有索引,則DML操作同時需要在UNDO表空間中儲存索引相關的復原資訊。

DML操作主要有以下幾方面的開銷構成:

擷取鎖(CPU開銷)

定位要變更的記錄(離散IO開銷)

記錄復原資訊(CPU+IO開銷)

變更記錄(CPU開銷)

記錄重做日志(順序IO開銷)

資料塊寫入(異步離散IO開銷)

因為DML操作過程中記錄復原資訊占用了非常大的一塊資源,為了更好的估算DML操作需要復原空間的大小,本文介紹了一些常用操作的估算方法及驗證示例。

二、如何檢視事務UNDO使用空間

如何檢視事務操作使用的UNDO空間,Oracle提供了系統視圖V$TRANSACTION,裡面儲存了目前資料庫活動事務的主要資訊,我們可以用如下SQL來檢視:

  1. select b.sid,--會話編号   
  2.        b.SERIAL#,  
  3.        b.USERNAME,  
  4.        b.MACHINE,  
  5.        b.sql_hash_value,  
  6.        a.START_TIME,--事務啟動時間   
  7.        a.USED_UBLK, --使用的UNDO塊數   
  8.        a.USED_UREC, --使用的UNDO記錄條數,是本文接下來的主要估算名額   
  9.        a.START_UBAFIL, --使用的UNDO檔案号   
  10.        a.START_UBABLK --使用的UNDO起始塊号   
  11.   from v$transaction a, v$session b  
  12.  where a.ses_addr = b.saddr and b.sid=?;  

 由于測試環境就我一個人使用,不存在并發,為簡化操作,忽略會活參數,簡化的SQL如下:

select USED_UREC  from v$transaction;

通過START_UBAFIL及START_UBABLK我們可以dump復原資料塊的分析,如下所示:

alter system dump datafile START_UBAFIL block START_UBABLK;

dump好後再通過日志檔案分析資料塊内的詳細資訊,筆者也是通過這樣的方法來确認計算公式,因為dump出來的内容比較複雜,是Oracle的具體實作細節,是以本文不介紹dump内容,有興趣的同學可以自己測試。

三、測試準備

  1. SQL> --建立表t1   
  2. SQL> create table t1 as select * from dba_objects;  
  3. Table created  
  4. SQL> select count(*) from t1;  
  5.   COUNT(*)  
  6. ----------   
  7.      29495  
  8. SQL> desc t1;  
  9. Name           Type          Nullable Default Comments   
  10. -------------- ------------- -------- ------- --------    
  11. OWNER          VARCHAR2(30)  Y                           
  12. OBJECT_NAME    VARCHAR2(128) Y                           
  13. SUBOBJECT_NAME VARCHAR2(30)  Y                           
  14. OBJECT_ID      NUMBER        Y                           
  15. DATA_OBJECT_ID NUMBER        Y                           
  16. OBJECT_TYPE    VARCHAR2(18)  Y                           
  17. CREATED        DATE          Y                           
  18. LAST_DDL_TIME  DATE          Y                           
  19. TIMESTAMP      VARCHAR2(19)  Y                           
  20. STATUS         VARCHAR2(7)   Y                           
  21. TEMPORARY      VARCHAR2(1)   Y                           
  22. GENERATED      VARCHAR2(1)   Y                           
  23. SECONDARY      VARCHAR2(1)   Y                           
  24. SQL> --object_id建立索引    
  25. SQL> create index idx_t1_object_id on t1(object_id);  
  26. Index created  
  27. SQL> --object_name建立索引   
  28. SQL> create index idx_t1_object_name on t1(object_name);  
  29. Index created  

四、計算方法及測試腳本

下面介紹事務中各種DML語句(insert,update,delete)使用UNDO記錄的計算方法,每種操作會介紹估算公式并簡單示例解釋:

4.1、delete 操作

4.1.2、一般删除

計算公式:USED_UREC=删除表記錄數+删除表索引記錄數(每個索引每行記錄算一條記錄)
假設表有2個索引,删除10條記錄
USED_UREC=10+2*10=30
  1. SQL> delete from t1 where rownum<=10;  
  2. 10 rows deleted  
  3. SQL> select USED_UREC  from v$transaction;  
  4.  USED_UREC  
  5. ----------   
  6.         30  
  7. SQL> commit;  
  8. Commit complete  

4.1.2、通過索引範圍條件删除記錄

USED_UREC=删除表記錄數+更新索引塊數
假設表有1個單字段普通索引,通過索引範圍查詢10000條記錄并删除,每個索引塊大塊儲存200條記錄
USED_UREC=10000+10000/200=10050
  1. SQL>  delete from t1 where object_id between 10000 and 20000;  
  2. 19871 rows deleted  
  3. SQL> select USED_UREC  from v$transaction;  
  4.  USED_UREC  
  5. ----------   
  6.      20242  
  7. SQL> commit;  
  8. Commit complete  

4.2、update 操作

4.2.1、一般更新

USED_UREC=更新表記錄數+更新索引記錄變更數*2(每行索引變更有2個記錄,一個是記錄原索引指針,另外是記錄新索引指針)
假設表有2個索引,更新10條記錄的2個字段,其中要更新1個是普通字段,1個是索引字段
USED_UREC=10+2*10=30
  1. SQL> update  t1 set  object_name='test',owner='MK' where rownum<=10;  
  2. 10 rows updated  
  3. SQL> select USED_UREC  from v$transaction;  
  4.  USED_UREC  
  5. ----------   
  6.         30  
  7. SQL> commit;  
  8. Commit complete  

注:在語句中,普通字段不管更新前與更新後是否發生變化,都會産生UNDO記錄,但是索引字段隻有發生了變化才會産生UNDO記錄,如下測試,object_name做了更新操作,但是沒有發生變化,是以索引記錄不會發生變更。

  1. SQL> update  t1 set  object_name=object_name,owner='MK' where rownum<=10;  
  2. 10 rows updated  
  3. SQL> select USED_UREC  from v$transaction;  
  4.  USED_UREC  
  5. ----------   
  6.         10  
  7. SQL> commit;  
  8. Commit complete  

4.2.2、通過索引範圍條件更新該索引字段

USED_UREC=更新表記錄數+更新索引塊數*2
假設表有1個單字段普通索引,通過索引範圍查詢10000條記錄并更新對應的索引字段,每個索引塊大塊儲存200條記錄
USED_UREC=10000+2*(10000/200)=10100
  1. SQL> update  t1 set  object_id=object_id+1 where object_id>10000;  
  2. 19584 rows updated  
  3. SQL> select USED_UREC  from v$transaction;  
  4.  USED_UREC  
  5. ----------   
  6.      19862  
  7. SQL> commit;  
  8. Commit complete  

注:可以看出這種通過索引範圍通路并更新該索引字段的情況非常少,要求也非常特殊。假設剛才的語句做一點小變化都不滿足要求,如下加了一個rownum條件,實際更新的記錄數都是一樣的,但是使用的UNDO記錄數隻能按一般更新計算。

  1. SQL> update  t1 set  object_id=object_id+1 where object_id>10000 and rownum<1000000;  
  2. 19584 rows updated  
  3. SQL> select USED_UREC  from v$transaction;  
  4.  USED_UREC  
  5. ----------   
  6.      58752  
  7. SQL> commit;  
  8. Commit complete  

4.3、insert 操作

4.3.1、單條insert (insert into t1 values ...)

USED_UREC=新增記錄數+表索引個數*新增記錄數

假設表有2個索引,新增3條記錄

USED_UREC=3+3*2=9

  1. SQL> insert into t1(owner,object_name,object_id) values('MK','test1',123456);  
  2. 1 row inserted  
  3. SQL> insert into t1(owner,object_name,object_id) values('MK','test2',1234567);  
  4. 1 row inserted  
  5. SQL> insert into t1(owner,object_name,object_id) values('MK','test3',12345678);  
  6. 1 row inserted  
  7. SQL> select USED_UREC  from v$transaction;  
  8.  USED_UREC  
  9. ----------   
  10.          9  
  11. SQL> commit;  
  12. Commit complete  

4.3.2、批量insert(insert into t1 select ...)

這個非常難準确計算,因為新增記錄會利用以前空閑的資料塊,隻有資料塊有記錄變化都需要儲存資料塊對應的復原記錄,同時也儲存索引的復原記錄,是以

USED_UREC≈新增記錄變更表資料塊數+∑每個變更表資料塊對應變更的索引塊數

假設表有2個索引,新增1000條記錄,每個資料塊大約可儲存600條記錄,新增第一個資料塊儲存了600條記錄,同時變更了第1個索引30個索引塊,第2個索引40個資料塊,新增第2個資料塊儲存了400條記錄,同時變更了第1個索引20個索引塊,第2個索引60個資料塊

USED_UREC≈2+(30+40+20+60)=152

  1. SQL> insert into t1 select * from dba_objects where rownum<=10000;  
  2. 10000 rows inserted  
  3. SQL> select USED_UREC  from v$transaction;  
  4.  USED_UREC  
  5. ----------   
  6.       7837  
  7. SQL> commit;  
  8. Commit complete  

五、總結

以上腳本是在Oracle9.2上測試,Oracle對UNDO的處理非常複雜,這裡介紹隻是常用的一些DML産生UNDO估算方法,從估算公式可以看出,索引對DML操作的影響非常大,當一個表有索引比沒索引時做DML操作花費的UNDO開銷非常具大, 因為資料庫要保證事務復原的可行性,需要對索引做許多額外的事情,更新索引字段及批量INSERT操作尤其明顯。在有索引和沒索引的表上做批量資料導入,性能有可能相關好幾倍。通過估算Oracle的DML操作需要的UNDO記錄數,也可以間接估算一個DML還需要的時間。工作中有時會遇到一個DML操作時間非常長,如果v$session_longops視圖也沒有可以跟蹤的資訊,這時就可以通過v$transaction的USED_UREC資訊估算SQL的進度。DML在操作時,USED_UREC是一直在增加的,當事務開始復原時USED_UREC會開始下降,直到等于0,則復原完成,是以我們也可以根據這個字段的變化判斷復原進度。