Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, kiril.

Asked: May 31, 2002 - 11:06 am UTC

Last updated: June 05, 2002 - 7:14 am UTC

Version: 8.1.6.1

Viewed 1000+ times

You Asked

Hi Tom I am trying to execute :

declare
cursor cur is
select /*+ USE_NL(t2 t1) */t1.art_grp_no,
t1.amount+t1.disc_amount amnt,
t2.invoice_date,
t2.cust_no,
t2.store_no
from invoice_line t1,invoice t2
where t1.invoice_no=t2.invoice_no and
t1.till_no = t2.till_no ;
retRow cur%ROWTYPE ;
cnt number := 0 ;
begin
for retRow in cur loop
begin
cnt := cnt + 1 ;
insert into TB_ART_GRP_CUST_STAT values(
retRow.art_grp_no, retRow.amnt, retRow.invoice_date,retRow.cust_no,retRow.store_no);
exception
when others then
if sqlcode = -1 then
update TB_ART_GRP_CUST_STAT set TOTAL_SUM = TOTAL_SUM + retRow.amnt
where ART_GRP_NO = retRow.art_grp_no and
INVOICE_DATE = retRow.invoice_date and
CUST_NO = retRow.cust_no and
STORE_NO = retRow.store_no ;
end if ;
end ;
if mod(cnt , 1000) = 0 then
commit ;
end if ;
end loop ;
end ;
/

And after while I have received 'ora-01555'
Why ???? If you can Please explain. I am not doing fetch across commit.
Can I find when and why I have received this error.
Some additional info:
During the execution of PL/SQL block there is no any DML or DDL on tables Invoice and Invoice_line.
I am duing commit because there is no place on server for rollback info and disk space for Temporary table is also very limited.

Info about Database parameters and tables:
oracle 8.1.6.1(dedicated ) in archivelog mode over 3 disks(separete indexes,data and RBS and Temp). OS Win NT 4.0 SP 5 1Gb memory.
All DB is around 35 Gb.
There is Temporary tablespace 500Mb;
Trere are 7 rollback segment at same size 10mb (with optimal size);
RBS tablespace is 1Gb.
All tablespace are dictionary managed ( sorry but was migrated from 7.3).

sort_area_size = 50Mb.
db_block_buffers = 200mb.
shared_pool_size = 72914560.

Table Invoice_line has 12 range partitions (about 37 milion rows) and all indexes on this table are local prefixed.
Table Invoice has 12 range partitions (about 7 milion rows) and all indexes on this table are local prefixed.
Table TB_ART_GRP_CUST_STAT has 4 range partitions (about 22 milion rows)

descr TB_ART_GRP_CUST_STAT
Name Null? Type
------------------------------- -------- ----
ART_GRP_NO NOT NULL NUMBER(3)
TOTAL_SUM NUMBER(18,2)
INVOICE_DATE NOT NULL DATE
CUST_NO NOT NULL NUMBER(6)
STORE_NO NOT NULL NUMBER(2)

Trere is local PK using index on (cust_no,INVOICE_DATE,STORE_NO,ART_GRP_NO).

Thank in advance.
Kiro.

and Tom said...

You most certainly are doing a fetch across a commit.

you have:

for x in cursor
loop
DML
if (mod) then commit end if;
end loop


you have a commit in your loop, you are fetching across that. Here is an example that shows what is happening to you:

</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:895410916429 <code>

there, we are reading from a READ ONLY tablespace and getting an ora-1555! the reason -- lots of small transactions wiping out transaction headers that we need for read consistency.


Have you priced a 36gig drive recently? About $200 usd. You've probably spent wwwwaaaayyyyy more then that trying to figure this out. It would be more cost effective to keep a closet full of these disks and just pull one out every time you wanted to do this process and use the disk. Just throw the disk away after you run your update! It would be cheaper.

You quite simply need to configure your system properly to do the jobs it needs to do. Period.


Let's take a further look at this code:

declare
cursor cur is
select /*+ USE_NL(t2 t1) */t1.art_grp_no,
t1.amount+t1.disc_amount amnt,
t2.invoice_date,
t2.cust_no,
t2.store_no
from invoice_line t1,invoice t2
where t1.invoice_no=t2.invoice_no and
t1.till_no = t2.till_no ;
retRow cur%ROWTYPE ;
cnt number := 0 ;
begin
for retRow in cur loop
begin
cnt := cnt + 1 ;
insert into TB_ART_GRP_CUST_STAT values(
retRow.art_grp_no, retRow.amnt,
retRow.invoice_date,retRow.cust_no,retRow.store_no);
exception
when others then
if sqlcode = -1 then
update TB_ART_GRP_CUST_STAT set TOTAL_SUM = TOTAL_SUM + retRow.amnt
where ART_GRP_NO = retRow.art_grp_no and
INVOICE_DATE = retRow.invoice_date and
CUST_NO = retRow.cust_no and
STORE_NO = retRow.store_no ;
end if ;
end ;
if mod(cnt , 1000) = 0 then
commit ;
end if ;
end loop ;
end ;
/

Hmm, couple of big problems I see:

1) it is not restartable. When you hit the ora-1555 or ANY OTHER error -- what then? Well, you'll update the rows you just inserted adding the retRow.amnt in erroneously. Or, you'll update a row for the second time if it existed on the first pass! In other words you will corrupt your data. Even if you get around the 1555 -- you have a TON more code to write to make this process safely restartable -- so you process a row at least once and at most once (else the integrity of your data -- GONZO)

2) you have a "WHEN OTHERS" that is not followed by a RAISE; This is a bug -- 100% of the time in my opinion. You are looking for DUP_VAL_ON_INDEX (don't use when others, use when dup_val_on_index and remove the sqlcode=-1 check).

What happens if the error is "out of space", "constraint xxxx violated", etc??? You silently ignore it. Again, this would result in a total, utter and complete loss of data integrity.




Bottom line -- in 8i this should be AT MOST two statements -- one to update existing rows and one to insert the rows that did not exist yet.

In 9i, this will be ONE statement -- MERGE to do an update of existing and insert of new.

In both cases -- you will configure sufficient disk space to do the work your system needs to do.




Actually 3rd option. If this table TB_ART_GRP_CUST_STAT is just a rollup of the invoice and invoice_line table -- you should just:

create materialized view TB_ART_GRP_CUST_STAT
<mv options>
as
select t1.art_grp_no,
t1.amount+t1.disc_amount amnt,
t2.invoice_date,
t2.cust_no,
t2.store_no
from invoice_line t1,invoice t2
where t1.invoice_no=t2.invoice_no and
t1.till_no = t2.till_no ;


and refresh that as needed. Let the system do the work for you (a full refresh would be faster then update/insert every single row)



Rating

  (4 ratings)

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

Comments

snapshot too old

kiro, June 03, 2002 - 2:55 am UTC

Tom
Thanks for your response.
As I understood I hit ora-1555 for the reason of ROLLBACK TRANSACTION SLOT OVERWRITTEN.

I think that TRANSACTION SLOT of "cursor Cur is..."
have been overwritten from some of subsequent transactions (insert ... ; update ... commit; ).

Am I right?
Please, make a short cooment.
Thanks
kiro.


Tom Kyte
June 03, 2002 - 6:44 am UTC

Yes, one of the rbs transaction slots for the segments:

from invoice_line t1,invoice t2

was overwritten by your frequent commits. Again, this process should be:

a) one CTAS or materialized view if the table is rollup of the data

or

b) 2 sql statements followed by a commit afterwards (for speed, data integrity and error free processing)



transaction slot

kiro, June 04, 2002 - 3:11 am UTC

Tom
Just to close picture how I can calculate number of transaction slots. Is there any rule or it depends from size of rollback segments.
If you can make short comment it will be graet.
thanks


Tom Kyte
June 04, 2002 - 6:53 am UTC

If you have my book -- I go into this in some detail in chapter 5 on redo and rollback. Here is a list of things that must be true in order for this to occur:

....
In order to receive the ORA-01555 from a delayed block cleanout, all of the following conditions must be met:

o A modification is made and commits and the blocks are not cleaned out automatically (for example, it modified more blocks then fit in 10% of the SGA block buffer cache).

o Those blocks are not touched by another session and will not be touched until our unfortunate query below hits it.

o A 'long running' query begins. This query will ultimately read some of those blocks from above. This query starts at SCN 't1'. That is the read consistent SCN it must roll data back to in order to achieve read consistency. The transaction entry for the modification transaction is still in the rollback segment transaction table when we began.

o During the query, many commits are made in the system. These transactions do not touch the blocks in question (if they did we wouldn’t have the impending problem).

o The transaction tables in the rollback segments roll around and reuse slots due to the high degree of commits. Most importantly, the transaction entry for the original modification transaction is cycled over and reused. In addition, the system has reused rollback segment extents, so as to prevent a consistent read on the rollback segment header block itself.

o Additionally, the lowest SCN recorded in the rollback segment now exceeds 't1' (it is higher than the read consistent SCN of the query) due to the large amount of commits.
....................


so, it depends on the size of the rollback segments. You should be aware that in 9i, using undo tablespaces, you can set an undo retention period which makes this a virtual non issue.



kiro, June 04, 2002 - 7:12 am UTC

thanks Tom
I am now reading your book.

Reader

A reader, June 05, 2002 - 7:14 am UTC

From ixora.au.com

You should be able to calculate the number of slots for any database block size
and for any given version from V$TYPE_SIZE without dumping a header block. For
example for your 2K block size it goes like this:

2048 bytes db_block_size
- 16 bytes cache layer undo block header (KTUBH)
- 4 bytes cache layer tail
- 72 bytes extent control header (KTECH)
- 44 bytes extent map header (KTECT)
- 960 bytes extent table = extent entry * (maxextents - 1)
= 8 bytes (KTETB) * (db_block_size/16 - 8)
- 104 bytes transaction control header (KTUXC)
=========
= 848 bytes for the transaction table
/ 40 bytes per transaction table entry (KTUXE)
= 21 transaction table slots!
(8 bytes unused)


More to Explore

Hints

Need more information on hints? Check out the complete hint descriptions