Sometimes when we drop objects it may take brief period of time to complete the operation. One of the many activities performed by Oracle during this duration is to flush all the dirty buffers related to this object on to the disk. We can check this behavior by tracing the session, in 11.1 "enq: RO - fast object reuse" will be the wait event which indicates performing object level checkpoint to flush dirty blocks related to dropped object, in 11.2 the event would be "enq: CR – block range reuse ckpt".
But why do we really need blocks related to dropped object on the disk? Why can't Oracle just discard the cached blocks in buffer cache related to dropped objects and reuse them? This seems to be unusual intially, but its really interesting to find out the rationale behind this. Also this leads to many interesting observations.
Let me try to explain the rationale behind this by simulating few cases...
Case 1:- (Drop table without purge)
What happens if we try to drop an TABLE when other sessions are using it at the same time ?
Session 1: Create the table TAB1.
SQL> create table TAB1 as
2 select rownum id, rpad('x',100) c1, rpad('y',200) c2
3 from dba_objects
4 /
Table created.
Session 2: Start long running query acessing table TAB1
SQL> begin
2 for i in (select c1 from TAB1)
3 loop
4 dbms_lock.sleep(0.01);
5 end loop;
6 end;
7 /
Session 1: Drop table TAB1 and flush buffer cache
SQL> drop table TAB1;
Table dropped.
SQL> alter system flush buffer_cache;
System altered.
Session 2: Query keeps running by re-reading the blocks from the disk.
SQL> begin
2 for i in (select c1 from TAB1)
3 loop
4 dbms_lock.sleep(0.01);
5 end loop;
6 end;
7 /
..
....
SQL>
PL/SQL procedure successfully completed.
As you can see when session 2 is retrieving the rows from the table TAB1,session 1 was able to drop the table at the same time. Intrestingly session 1 was able to retirve all the records from the table even after the table was dropped.
Case 1:- (Drop table with purge)
What happens if we try to drop an TABLE with purge option when other sessions are using it at the same time ?
Session 1: Create the table TAB1.
SQL> create table TAB1 as
2 select rownum id, rpad('x',100) c1, rpad('y',200) c2
3 from dba_objects
4 /
Table created.
Session 2: Start long running query acessing table TAB1
SQL> begin
2 for i in (select c1 from TAB1)
3 loop
4 dbms_lock.sleep(0.01);
5 end loop;
6 end;
7 /
Session 1: Drop table TAB1 with purge option and flush buffer cache
SQL> drop table TAB1 purge;
Table dropped.
Session 2: Query crashes with error ORA-08103
SQL> begin
2 for i in (select c1 from TAB1)
3 loop
4 dbms_lock.sleep(0.01);
5 end loop;
6 end;
7 /
begin
*
ERROR at line 1:
ORA-08103: object no longer exists
ORA-06512: at line 2
As you can see when session 2 is retrieving the rows from the table TAB1,session 1 was able to drop the table with purge option at the same time. Intrestingly session 1 crashed with ORA-08103.
Case 2:- (Drop index)
What happens when we try to DROP/REBUILD an index when other sessions are accessing it at the same time ?
Session 1: Create the table TAB1 and index IDX1.
SQL> create table TAB1 as
2 select rownum id, rpad('x',100) c1, rpad('y',200) c2
3 from dba_objects
4 /
Table created.
SQL> create index IDX1 on TAB1(id);
Index created.
Session 2: Start long running query which uses index IDX1
SQL> begin
2 for i in (select /*+ index(TAB1) c1 from TAB1 where id > 0)
3 loop
4 dbms_lock.sleep(0.01);
5 end loop;
6 end;
7 /
Session 1: Drop index IDX1 and flush buffer cache
SQL> drop index IDX1;
Index dropped.
SQL> alter system flush buffer_cache;
System altered.
Session 2: Query keeps running by re-reading the blocks from the disk.
SQL> begin
2 for i in (select /*+ index(TAB1) */ c1 from TAB1 where id > 0)
3 loop
4 dbms_lock.sleep(0.01);
5 end loop;
6 end;
7 /
...
....
SQL>
PL/SQL procedure successfully completed.
Session 2 was able to walk through the index when at the same time session 1 was able to drop the index. In case of rebuilding the index, no matter how we Rebuild index(Online or Offline) the results will be same.
Case 2:- (Drop index and create new segment to reuse the dropped index space)
What happens when we try to DROP/REBUILD an index and immedaitely create new table segment TAB2 when other sessions are accessing it at the same time ?
Session 1: Create the table TAB1 and index IDX1.
SQL> create table TAB1 as
2 select rownum id, rpad('x',100) c1, rpad('y',200) c2
3 from dba_objects
4 /
Table created.
SQL> create index IDX1 on TAB1(id);
Index created.
Session 2: Start long running query which uses index IDX1
SQL> begin
2 for i in (select /*+ index(TAB1) */ c1 from TAB1 where id > 0)
3 loop
4 dbms_lock.sleep(0.01);
5 end loop;
6 end;
7 /
Session 1: Drop index IDX1 and create new segment so that dropped index space is reused.
SQL> drop index IDX1;
Index dropped.
SQL> create table TAB2 as select * from TAB1;
Session 2: Query crashes with error
SQL> begin
2 for i in (select /*+ index(TAB1) c1 from TAB1 where id > 0)
3 loop
4 dbms_lock.sleep(0.01);
5 end loop;
6 end;
7 /
begin
*
ERROR at line 1:
ORA-08103: object no longer exists
ORA-06512: at line 2
In this case session 2 crashes with error ORA-08103 after creating new table TAB2 followed by dropping of index IDX1. Purpose of creating new table TAB2 is to overwrite the space used by the dropped index IDX1.
Caveat of testing:-
We have to be careful when we perform tests, in above cases if my table segment is having less than 11 extents then select statement will not fail even if segment is dropped using purge option. Usually select statement will revisit the segment header to get extent map information once after processing every 11 extents, so if segment is having more than 11 extents and if it is dropped using purge option then purge option will update the header contents due to which when select tries to revisit segment header to get next set of extents details after reading 11 extents it will fail.
Dumping TAB1 segment header block will reveal the details of actions performed by purge option. Below is the snippet of TAB1 segment header block dump.
Before drop:
--------------------------------------------------------
Segment Type: 1 nl2: 1 blksz: 8192 fbsz: 0
L2 Array start offset: 0x00001434
First Level 3 BMB: 0x00000000
L2 Hint for inserts: 0x010002f9
Last Level 1 BMB: 0x01005201
Last Level II BMB: 0x010002f9
Last Level III BMB: 0x00000000
Map Header:: next 0x00000000 #extents: 21 obj#: 18566 flag: 0x10000000
After drop without Purge:
--------------------------------------------------------
Segment Type: 1 nl2: 1 blksz: 8192 fbsz: 0
L2 Array start offset: 0x00001434
First Level 3 BMB: 0x00000000
L2 Hint for inserts: 0x010002f9
Last Level 1 BMB: 0x01005201
Last Level II BMB: 0x010002f9
Last Level III BMB: 0x00000000
Map Header:: next 0x00000000 #extents: 21 obj#: 18566 flag: 0x10000000
After drop with Purge:
--------------------------------------------------------
Segment Type: 1 nl2: 1 blksz: 8192 fbsz: 0
L2 Array start offset: 0x00001434
First Level 3 BMB: 0x00000000
L2 Hint for inserts: 0x010002f9
Last Level 1 BMB: 0x01005201
Last Level II BMB: 0x010002f9
Last Level III BMB: 0x00000000
Map Header:: next 0x00000000 #extents: 1 obj#: 18566 flag: 0x12000000
Before drop and after drop of the table without purge option has not touched the segment header, but the segment header has been updated to only one extent when table is dropped using purge option.
In Case 2 when I dropped the index select query kept running without any issues, but when I created new segment TAB2 immediately after dropping index IDX1 select query crashed as the blocks were reused/overwritten by the new segment. Results will be the same even in case of index rebuild, it doesn't matter how index is rebuilt online/offline.
CROSS DDL CONSISTENCY:-
This feature was implemented way long back in Oracle 8i when the partition-exchange feature was introduced. Intention behind this implemetation was to allow sessions to select data and to not interrupt when we perform partition maintenance tasks. This is why all the dirty blocks related to dropped objects are flushed to disk so that any session which are already accessing this dropped object should be able to adhere CONSISTENCY property of Oracle database and in-turn Oracle supports it by providing the dirty blocks they need to read by flushing them to disk. Its important to remember that whenever index lookup are done by the queries and when it finds that the index block doesn't contain the data its expecting for the related object id, query will crash with error "ORA-01410: invalid rowid". Also its important to note that this feature is not applicable for Truncate statement.
In the above simulated cases when I create new table immediately after dropping TABLE/INDEX session 1 was not able to read the dropped blocks from the disk as those blocks were overwritten/used by demanding new table TAB2. When "drop purge" is used all the dirty data blocks will be flushed from the buffer cache and the segment header block will be updated and breaks the "Cross DDL Consistency" feature, since the query re-reads the segment header block after every few scans, it gets to a point where it discovers that the table has been dropped due to which query will crash.
This feature has been leveraged in 12c for new feature out-of-place Materialized View complete refresh.
Conclusion:-
Occasionally in production database sessions running queries may crash without any clue with below error messages, in such scenarios we can easily try to debug and find whether it is due to failure of "CROSS-DDL CONSISTENCY" feature for the reasons explained above.
- ORA-08103: object no longer exists
- ORA-01410: invalid rowid