解決 oracle 11G 出現的ORA-01555 問題
oracle我很不熟, 但在公司使用exp指令備份oracle 11g資料時, 遇到以下錯誤訊息
EXP-00056: ORACLE error 1555 encountered
ORA-01555: snapshot too old: rollback segment number with name "" too small
ORA-22924: snapshot too old
爬了文, 解法有三, 我從最簡單的解法開始, 第一種,第二種都失敗, 直到第三種方式才成功.
我建議從第三種方式開始解,該方法不需修改系統設定, 影響較小.
- 方法一
修改 undo_retention秒數, 把時間拉大,
**我是用備份的方式測試, 發現改了之後還是一樣出錯
SQL> show parameter undo;
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
undo_management string AUTO
undo_retention integer 21600
undo_tablespace string UNDOTBS1
SQL> ALTER SYSTEM SET UNDO_RETENTION = 43200;
SQL>
System altered.
SQL>
- 方法二
直接加大undo tablespace ,
這要小心點, 有很多眉角, 似乎牽涉到小心舊undo tablesapce 是否已呈現”OFFLINE”, 且新的undo tablespace 呈現”ONLINE’
# 新增undo tablespace
create undo tablespace UNDOTBS2 datafile '/.../oradata/undotbs02.dbf' size 30000M autoextend on next 100m maxsize unlimited;
#生效
alter system set undo_tablespace = UNDOTBS2 scope=both;
#查看狀態
select tablespace_name, status, count(*) from dba_rollback_segs group by tablespace_name, status;
select status,segment_name from dba_rollback_segs where status not in ('OFFLINE') and tablespace_name='UNDOTBS1';
select tablespace_name, status, count(*) from dba_rollback_segs group by tablespace_name, status;
#確認舊的已是offline , 就可以刪掉舊的
Drop tablespace UNDOTBS1 including contents and datafiles;
#再次查看是否已變成新的undo tablespace
show parameter undo
select tablespace_name tablespace, status, sum(bytes)/1024/1024 sum_in_mb, count(*) counts
from dba_undo_extents
group by tablespace_name, status order by 1,2;
- 方法三
# 先建立一個資料表, 用來儲存有問題的資料ID(ROWID)
SQL> CREATE TABLE CORRUPTED_ROWS (CORRUPTED_ROWID ROWID, ERROR_NUMBER NUMBER);
Table created.
接下來將有錯誤資料表table schema , 欄位類型為clob , blob 欄位列出來, 問題一定在這幾個欄位之中
# 以下紅色部分請改為有問題資料表與欄位名稱, 若沒有出現錯誤, 就換下一個欄位(fieldname)試試看
SET TIMING ON
DECLARE
ERROR_1578 EXCEPTION;
ERROR_1555 EXCEPTION;
ERROR_22922 EXCEPTION;
PRAGMA EXCEPTION_INIT(ERROR_1578, -1578);
PRAGMA EXCEPTION_INIT(ERROR_1555, -1555);
PRAGMA EXCEPTION_INIT(ERROR_22922, -22922);
N NUMBER;
BEGIN
FOR ROW IN (SELECT ROWID, fieldname FROM user.table)
LOOP
BEGIN
N:=DBMS_LOB.INSTR(ROW.fieldname, HEXTORAW('889911'));
EXCEPTION
WHEN ERROR_1578 THEN
INSERT INTO CORRUPTED_ROWS VALUES (ROW.ROWID, 1578);
COMMIT;
WHEN ERROR_1555 THEN
INSERT INTO CORRUPTED_ROWS VALUES (ROW.ROWID, 1555);
COMMIT;
WHEN ERROR_22922 THEN
INSERT INTO CORRUPTED_ROWS VALUES (ROW.ROWID, 22922);
COMMIT;
END;
END LOOP;
END;
/
接下來查詢是否真有問題資料
SELECT * FROM CORRUPTED_ROWS;
#執行以下這行指令應該要出錯
SELECT fieldname FROM user.table WHERE ROWID IN (SELECT CORRUPTED_ROWID FROM CORRUPTED_ROWS);
清空有問題的資料欄位
#若欄位型態是clob就用empty_clob(), 若為blob,就改成empty_blob(), 有幾個欄位出問題就清空幾個
update xxx.table set fieldname = empty_clob()where ROWID IN (SELECT CORRUPTED_ROWID FROM CORRUPTED_ROWS);
若不敢清空, 可以跳過有問題的資料備份
exp system@yourinstance BUFFER=81920 file=/tmp/backup.dmp tables=user.table QUERY=\"WHERE rowid NOT IN \(SELECT CORRUPTED_ROWID FROM CORRUPTED_ROWS\)\"
參考資料