Skip to Main Content
  • Questions
  • Deleting records using bulk collect throws ORA-01555 snapshot too old error

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, yatin.

Asked: December 07, 2016 - 10:55 pm UTC

Last updated: December 10, 2016 - 1:35 am UTC

Version: 11.2.0.4

Viewed 10K+ times! This question is

You Asked

I am using the below code to delete data using bulk collect

create or replace procedure DEL_BULK_ABC AS
declare CURSOR C_GetDelRec IS
SELECT rowid row_id
FROM ABC.XYZ a
WHERE EXISTS (SELECT 'X'
FROM ABC.CH_TEMP b
WHERE b.PACKAGEID = a.packageid);
TYPE tbl_rec_rowid IS TABLE OF ROWID INDEX BY PLS_INTEGER;
LP_rec_rowid tbl_rec_rowid;
Lv_no_recs NUMBER := 100000;
BEGIN
--get records
OPEN C_GetDelRec;
LOOP
FETCH C_GetDelRec bulk collect into LP_rec_rowid limit Lv_no_recs;
IF LP_rec_rowid.COUNT = 0 THEN
exit;
END IF;
--EXIT WHEN C_GetDelRec%NOTFOUND;
--Delete Records
FORALL rec in LP_rec_rowid.FIRST .. LP_rec_rowid.LAST
DELETE FROM ABC.XYZ where rowid = LP_rec_rowid(rec);
COMMIT;
END LOOP;
IF C_GetDelRec%ISOPEN THEN
CLOSE C_GetDelRec;
END IF;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Completed Successfully');
EXCEPTION
WHEN OTHERS THEN
IF C_GetDelRec%ISOPEN THEN
CLOSE C_GetDelRec;
END IF;
ROLLBACK;
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END DEL_BULK_ABC;
/

Please tell what is wrong with the code.
Many Thanks



and Connor said...

Deleting in batches always has this risk.

Your logic is doing:

"9am" - get me the first set of rows, and delete them

after lots of this, you will be doing:

"9:10am" - get the (say) 50th batch of rows *as they were at 9am*, and delete them.

And that "as at 9am" gets harder and harder each time you loop around - because of course, you are changing the table each time you delete, and thus we have more changes to undo to see the blocks as they were at 9am.

The solution here is

a) ideally, just do a single delete.

b) if you cant do (a), then see if you can bulk collect ALL of the rowids into a collection (pga limit is the issue here)

c) if you cant do (a) or (b), then catch the ora-1555, and when you encounter it, you close the cursor and re-open it. In this way, you are resetting the point in time at which you are querying. Obviously this only works (just like your current solution) if the identification of rows is a restartable one.


Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Many Thanks

yatin kumar, December 08, 2016 - 9:34 pm UTC


Many Thanks for the response

yatin kumar, December 08, 2016 - 9:37 pm UTC

Please suggest how to bulk collect all the row ids into a collection(in PGA).
Connor McDonald
December 10, 2016 - 1:35 am UTC

You are already doing it in your cursor. All I'm saying is - if you have sufficient memory you can do:

fetch bulk collect into ... -- with NO LIMIT

and then do deletes in batches of (say) 100,000 as you are currently doing, eg

loop
c := 1
forall i in c .. least(c+100000,lp_rec_rowid.count)
  DELETE FROM ABC.XYZ where rowid = LP_rec_rowid(i); 
  c := c + 100000;
  exit when c > lp_rec_rowid.count;
end loop;


or similar

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library