问题
I have query like:
delete from tableA
where tableA.fk in (select id
from tableB
where tableB.column1='somevalue'
and tableB.date between date1 and date2)
;
Table tableB contains near 100,000,000 records. So
select id
from tableB
where tableB.column1='somevalue'
and tableB.date between date1 and date2
returns near 1,000,000 records. As result - delete doesn't work at all - problem with size of rollback segment. I cannot increase size of segment.
How it can be executed?
回答1:
If you are filling the rollback segment it is probably because the amount of data involved in your transaction.
I'd split into several chunks and commit each of them, something like :
delete from tableA where tableA.fk in (
select id from (
select id from tableB where
tableB.column1='somevalue' and tableB.date between date1 and date2 and id in (select distinct fk from tableA.fk)
)
where rownum < XXXX
)
give XXXX the value you want (10000) or whatever, and then loop executing this statement and don't forget to commit in each iteration.
You have to loop until the delete statement returns 0 (number of affected rows).
UPDATED QUERY As noticed, this will not give the best performance, but will solve the rollback segment problem
Hope it helps
回答2:
Deletions are the most expensive operation in terms of rollback (undo) space usage, because we have to store the whole deleted row (whereas to undo an insert statement simply requires the database to store the rowid). The simplest solution to your problem would be to add more files to the UNDO tablespace.
But you say
"I cannot increase size of segment"
This is slightly surprising, After all, disk is cheap these days. Perhaps you have an angry DBA who you are scared to approach? But your DBA is failing in their duty to provision the database so it can be maintained; frankly, it is stupid to have a VLDB (a hundred million row table counts as such, even in these days of petabytes and zettabytes) with insufficient Undo space.
But if you won't beard the mintotaur in the data centre all you can do is change your code. Here is one option. Given this ...
select id
from tableB
where tableB.column1='somevalue'
and tableB.date between date1 and date2
... returns one million rows, and hence tries to delete too many rows from tableA
, you could try a sub-query which returns fewer rows. For the purposes on the exercise I am assuming the range specified by date1
to date2
is thirty days: you will need to adjust the following code accordingly.
for i in 1..10 loop
delete from tableA
where tableA.fk in (select id
from tableB
where tableB.column1='somevalue'
and tableB.date between date1 + (3 * (i-1)) and date1 + (3 * i)
;
commit;
end loop
This will simply split the select into ten three-day chunks. It will take much longer to do it this way, but it shouldn't blow the Undo tablespace.
回答3:
what you want to do is this:
create table foo_bar
as select *
from tableA
where tableA.fk not in (select id from tableB where
tableB.column1='somevalue' and tableB.date between date1 and date2);
followed by a truncate + delete of the original table
truncate tableA
drop tableA
Then rename foo_bar
to tableA
alter table foo_bar rename to tableA
Of note, make sure to disable all indexes.
COMMENTS
I cannot drop table. It is always in use.
You most certainly can. You need to hide the table behind a view
which is just a virtual function and/or set aside some maintenance time because you cannot blow data away while you are inserting. Now a more typical approach is to push this data into a materialized view
that will operate as a fact table
. This allows you to modify the base table (tableA) at any time without fear of hurting the user queries against the materialized view.
Use of the nologging
parameter will reduce the amount of rollback being utilized. You cannot recover a materialized view built with nologging
来源:https://stackoverflow.com/questions/14143510/how-to-optimize-delete-query-with-subquery-in-oracle