Skip to Main Content
  • Questions
  • estimate space can reclaimed from indexes when delete is performed on corresponding table

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, abhishek.

Asked: February 01, 2016 - 9:38 am UTC

Last updated: February 02, 2016 - 3:07 pm UTC

Version: 10g

Viewed 1000+ times

You Asked

Hi Team,

hope you are doing good :) and thanks a lot for helping the entire oracle community :).

## My question: how to calculate the amount of space which can be reclaimed or is reclaimed from indexes when we perform delete on the corresponding table.

## scenario: say we have table A (100 rows) and 3 indexes on it A1 ,A2 ,A3

when I delete 10 rows from table A, I am able to estimate the amount of space that can be reclaimed (space reclamation will be possible in case of table recreate or similar) using the avg_row_len (number of rows deleted* av_row_len) and the number of rows deleted.

but the problem is how to calculate the amount of space getting freed by indexes A1 A2 A3 due to the 10 rows deleted from their table A.

## some information about the table A:

1.) stats locked.
2.) stats gather not allowed due to production database restrictions.
3.) table recreation not allowed
4.) actual table size approx. 30 GB

please let me know if I need to provide more information.

Thanks,
Abhishek

and Chris said...

The size of each index entry is the length of the indexed values plus a rowid and some overheads.

If you just delete the rows, then (as with tables) the blocks are still allocated to the table. Here I'll delete 10% of data. The allocated space remains the same however:

create tablespace uniform_ts
    datafile size 1M autoextend on
    segment space management manual 
    extent management local uniform size 32768;

create table t as
  select rownum x, lpad('x', 700, 'x') y from dual connect by level <= 1000;
 
create index i on t (x, y) tablespace uniform_ts;

select blocks, bytes/1024 from user_segments
where  segment_name = 'I';

    BLOCKS BYTES/1024
---------- ----------
       104        832

delete t
where  x <= 100;

commit;

select blocks, bytes/1024 from user_segments
where  segment_name = 'I';

    BLOCKS BYTES/1024
---------- ----------
       104        832


So this doesn't make any extra space available to other objects. To reclaim this, you must shrink/rebuild the index:

alter index i rebuild;

select blocks, bytes/1024 from user_segments
where  segment_name = 'I';

    BLOCKS BYTES/1024
---------- ----------
        92        736


So how much space does removing rows free up within the index?

It depends!

Indexes are ordered data structures. So if you delete all the "first" entries this will empty blocks on the left hand edge of the index. So repeating the example above, deleting the first 100 rows (10% of them) empties the first 10 blocks. So this frees up for reuse (by the index):

drop table t purge;
create table t as
  select rownum x, lpad('x', 700, 'x') y from dual connect by level <= 1000;
 
create index i on t (x, y) tablespace uniform_ts;
exec dbms_stats.gather_index_stats(user, 'i');
select leaf_blocks from user_indexes
where  index_name = 'I';

                            LEAF_BLOCKS
---------------------------------------
                                    100

delete t
where  x <= 100;

commit;
exec dbms_stats.gather_index_stats(user, 'i');
select leaf_blocks from user_indexes
where  index_name = 'I';

                            LEAF_BLOCKS
---------------------------------------
                                     90


So if you insert more values it can reuse them. If we add another 100 rows, starting with x = 1000 Oracle uses the ten blocks you just freed up:

insert into t
  select rownum+1000 x, lpad('x', 700, 'x') y from dual connect by level <= 100;
 
commit;
exec dbms_stats.gather_index_stats(user, 'i');
select leaf_blocks from user_indexes
where  index_name = 'I';

                            LEAF_BLOCKS
---------------------------------------
                                     99


But if you delete 100 rows spread throughout the index you only remove one row/block. Oracle can only reuse these if you insert the same (or similar) values back in. So if you insert rows starting with x = 1000, these have to go at the right hand edge of the index. The total size of the index increases:

drop table t purge;
create table t as
  select rownum x, lpad('x', 700, 'x') y from dual connect by level <= 1000;
 
create index i on t (x, y) tablespace uniform_ts;
exec dbms_stats.gather_index_stats(user, 'i');

delete t
where  mod(x, 10) = 0;

commit;
exec dbms_stats.gather_index_stats(user, 'i');
select leaf_blocks from user_indexes
where  index_name = 'I';

                            LEAF_BLOCKS
---------------------------------------
                                    100

insert into t
  select rownum+1000 x, lpad('x', 700, 'x') y from dual connect by level <= 100;
 
commit;
exec dbms_stats.gather_index_stats(user, 'i');
select leaf_blocks from user_indexes
where  index_name = 'I';

                            LEAF_BLOCKS
---------------------------------------
                                    109


If you use a sequence to assign x, the free space is effectively lost. To reclaim this you need to shrink/rebuild the index. For more discussion about this read:

https://richardfoote.wordpress.com/2008/02/08/index-rebuild-vs-coalesce-vs-shrink-space-pigs-3-different-ones/

Also note that if you delete a significant fraction of the rows, a rebuild may reduce the number of branch blocks. So for indexes with a couple of branch levels a big delete could free up these too.

Note: deleting rows can cause an index's size to increase:

https://richardfoote.wordpress.com/2015/06/29/quiz-time-why-do-deletes-cause-an-index-to-grow-solution/



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

More to Explore

DBMS_STATS

More on PL/SQL routine DBMS_STATS here