現象:
應用的夜維從夜裡00:00開始執行,但因為hang的原因(暫時猜測為夜維處理的某條資料和目前應用正常處理的某條資料相同,出現前後等待同一資源鎖的現象),直到第二天白天09:25左右才繼續執行,但此時應用日志記錄:
snapshot too old: rollback segment number 29 with name "_SYSSMU29$" too small
原因分析:
因hang導緻夜維的DELETE語句一直處于等待狀态(超過一天),直到資源鎖釋放,但此時由于開始存放于UNDO中的前鏡像超過UNDO_RETENTION參數設定的時間,且這是高并發的一個系統,很快可能就會被應用session覆寫UNDO中的記錄,導緻無法找到UNDO中的記錄産生一緻性讀,是以報錯ORA-1555,此次執行失敗。
雖然分析可能是這個原因,但是是什麼語句讓這個系統的夜維語句hang,還沒有找到,有待進一步分析。。。
引申:
不過從這個報錯現象可以接觸到ORA-1555這個經典的錯誤号,尤其是在生産中,也是一種不多見的情況,尤其在現在UNDO基本都是用Oracle自動管理方式,且磁盤空間配置設定都比較大的情況下。
這個ORA-1555的錯誤是Oracle復原段錯誤中的一種經典。UNDO用于記錄DML操作資料的前鏡像,ORA-1555的錯誤簡單用一句話總結,我覺得就是當DML語句需要用UNDO記錄的資料找到前鏡像時,該記錄已經被覆寫,導緻無法利用UNDO中的記錄完成一緻性讀。當然Oracle也有UNDO_RETENTION等參數避免這種情況的産生,但仍舊可能發生,原因有多種,解決方法也有多種,下面就簡單說明介紹下。
首先,Tom、hellodba等高人也對ORA-1555有過經典的介紹。從原因來講,ORA-1555的錯誤原因歸為兩種,一是一緻性讀,一個是延遲塊(鎖)清除。
和ORA-1555相關的參數:
1、UNDO_RETENTION。
UNDO_RETENTION參數設定了復原段中被送出或復原的資料強制保留時間,但并不是說超過這個時間,復原段中的資料就會被清除,而是等待後面的事務産生的復原資料覆寫之前的。
2、對于Oracle 9i及以上版本,有兩種管理UNDO的方法,由UNDO_MANAGEMENT參數指定,手動管理UNDO和自動管理UNDO的差別:手動管理是會回繞的,會盡可能地重用UNDO空間,Oracle會擴充UNDO段,而不是回繞,進而保證UNDO_RETENTION的時間要求。是以從這可以看出,為了避免ORA-1555,使用自動管理UNDO的方法也許可以一定程度上降低機率。
概念介紹:
1、一緻性讀(摘自hellodba的blog:“http://www.hellodba.com/reader.php?ID=170&lang=CN”)
Oracle通過復原段進行一緻性讀,即避免了髒讀,又大大減少了系統的阻塞、死鎖問題。Oracle更新資料塊(Data Block Oracle中最小的存儲機關)時,會在兩個地方記錄下這一更新動作。一個是重做段(Redo Segment),是用于資料庫恢複(Recover)用的。一個是復原段(UNDO Segment),而復原段是用于事務復原(Rollback)的(我們隻關心復原段了)。并在資料塊頭部标示出來是否有修改資料。一個語句在讀取資料快時,如果發現這個資料塊是在它讀取的過程中被修改的(即開始執行讀操作時并沒有被修改),就不直接從資料塊上讀取資料,而是從相應的復原段條目中讀取資料。這就保證了最終結果應該是讀操作開始時的那一時刻的快照(snapshot),而不會受到讀期間其他事務的影響。這就是Oracle的一緻性讀,也可以叫做多版本(Multi-Versioning)。
2、延遲塊清除(摘自hellodba的blog:“http://www.hellodba.com/reader.php?ID=170&lang=CN”)
當Oracle更新資料塊時,會在復原段(UNDO Segment)記錄下這一更新動作。并且産生一個Cleanout SCN,在復原段中,會産生對應的Transaction ID以及相應的資料記錄鏡像。并在對應的資料記錄上,産生鎖标志。在事務送出(commit)前,會在資料塊的頭部記錄下這個Cleanout SCN(Csc)号、Undo Block Address(Uba)和Transaction ID(Xid);并且在在對應Interested
Transaction List(Itl)中設定鎖标志,記錄這個事務在這資料塊中産生的鎖的數目;同時在對應修改的資料記錄上打上行級鎖标志,并映射到對應的Itl去。當送出時,并不會一一清除掉所有鎖标志,而是給對應的Itl打上相應标志,告訴後面通路該資料塊的事務,相應的事務已經送出。這就叫做快速送出(Fast Commit)。而後面通路該資料塊的的事務就先檢查鎖标志和對應的事務狀态,如果發現前面的事務沒有送出,并且要通路的資料記錄被鎖住了,就被阻塞;否則就清除相應的鎖标志,并送出自己的鎖标志,再重複以上動作。這就事延遲塊清除。
如果大事務接觸到了非常多的塊,并且到了緩沖區緩存的10%以上,此時就會出現待清理的塊,并未由COMMIT操作清理,即不是FAST COMMIT,沒有其它事務DML接觸這些塊,而是SELECT一個表時就有可能出現ORA-1555的錯誤。
另外,看到網上很多驗證ORA-1555的錯誤實驗,都是用SELECT語句測試的,但其實應該是“事務”或“查詢”語句需要UNDO中資料時,出現記錄被覆寫的情況下都有可能報這個錯,是以開始介紹的夜維報錯就是DELETE語句報的ORA-1555,且這個夜維比較特殊的地方就是他是若幹條(22條)DELETE語句在一個事務中,即都執行完成後才一次COMMIT,這樣無形當中增加了事務的複雜度,但凡其中一條語句等待,則其他語句就無法送出,也隻能等待,雖然這是由業務決定的,但這種情況還是應該避免,當然這是另一個話題了。
執行個體:UNDO空間太小導緻的ORA-1555。
建立一個2M大小,不能自動擴充的UNDO空間。
SQL> create undo tablespace undo_small datafile '/opt/oracle/oradata/bisal/undo_small.dbf' size 2m autoextend off;
将其設定為系統UNDO空間。
SQL> alter system set undo_tablespace = undo_small;
System altered.
建立一張測試表。(注意:這裡使用dbms_random.random是為了将行弄亂,使他們不至于認為有某種順序,進而得到随機的分布,因為CTAS方式建表是力圖按照查詢擷取的順序将行放在塊中。)
SQL> create table t as select * from all_objects order by dbms_random.random;
Table created.
建立主鍵。
SQL> alter table t add constraint t_undo_pk primary key(object_id);
Table altered.
收集表的統計資訊。
SQL> exec dbms_stats.gather_table_stats(user, 'T', cascade=>true);
PL/SQL procedure successfully completed.
重複更新表中所有資料。
SQL> begin
for x in (select rowid rid from t)
loop
update t set object_name = lower(object_name) where rowid = x.rid;
commit;
end loop;
end;
/
PL/SQL procedure successfully completed.
上述語句執行過程中,建立查詢語句,這裡使用DBMS_LOCK.SLEEP(0.01)來模拟查詢單次時間是0.01秒,由于是随機插入到表中的,是以此處相當于随機地查詢表中的塊。這個查詢語句執行幾秒就可能失敗。
declare
cursor c is
select object_name
from t
order by object_id;
l_object_name t.object_name%type;
l_rowcnt number:=0;
begin
open c;
loop
fetch c into l_object_name;
exit when c%notfound;
dbms_lock.sleep(0.01);
l_rowcnt := l_rowcnt + 1;
end loop;
close c;
exception
when others then
dbms_output.put_line('rows fetched = '|| l_rowcnt);
raise;
end;
/
(注:報錯
l_rowcnt number:=0;
*
ERROR at line 8:
ORA-06550: line 8, column 4:
PLS-00103: Encountered the symbol "L_ROWCNT" when expecting one of the
following:
:= ( ; not null range default character
The symbol ";" was substituted for "L_ROWCNT" to continue.
可能是l_object_name t.object_name%type少分号)
報錯是:
declare
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number 11 with name "_SYSSMU11$" too small
ORA-06512: at line 21
總結:
對于報錯,原因在于SELECT語句是按照object_id的索引進行讀取,(INDEX FULL SCAN),由于之前資料是按照随機順序插入得到的,是以此處是在全表上執行随機讀,這樣就可能出現:SELECT讀到的資料可能是不同塊中的,此時UPDATE更新資料并送出,辨別UNDO中該記錄可被覆寫,由于UNDO空間較小,是以出現SELECT讀取的UNDO塊被UPDATE更新、送出而被覆寫的可能性就會變大,于是出現了ORA-1555的錯誤。
為了解決這種問題,将UNDO設定為可擴充,讓Oracle自動管理UNDO,可以最大幅度地擴充UNDO容量,滿足UNDO_RETENTION時間要求的同時,也保證了SELECT可以讀到的塊不會被UPDATE送出所覆寫。
SQL> column file_name new_val F
SQL> select file_name from dba_data_files where tablespace_name='UNDO_SMALL';
FILE_NAME
--------------------------------------------------------------------------------
/opt/oracle/oradata/bisal/undo_small.dbf
SQL> Alter database datafile '&F' autoextend on
2 next 1m
3 maxsize 2048m;
old 1: Alter database datafile '&F' autoextend on
new 1: Alter database datafile '/opt/oracle/oradata/bisal/undo_small.dbf' autoextend on
Database altered.
SQL> select bytes/1024/1024 from dba_data_files where tablespace_name='UNDO_SMALL';
BYTES/1024/1024
---------------
4
此時再執行上述UPDATE和SELECT語句,即可執行完成。
http://www.dengb.com/oracle/702397.htmlwww.dengb.comtruehttp://www.dengb.com/oracle/702397.htmlTechArticle現象: 應用的夜維從夜裡00:00開始執行,但因為hang的原因(暫時猜測為夜維處理的某條資料和目前應用正常處理的某條資料相同,出現前後...