Oracle11g: Zero Sized Unusable Indexes (Zeroes) February 25, 2011
Posted by Richard Foote in 11g, 11g New features, Oracle Indexes, Unusable Indexes.trackback
Following on from my previous discussion on “Create On Demand” segments, Oracle 11g R2 has also introduced storage saving initiatives in relation to useable indexes. Starting with a simple Oracle 10g example, we create a table and associated index:
SQL> create table bowie as select rownum id, 'BOWIE' name from dual connect by level <= 1000000; Table created. SQL> create index bowie_id_i on bowie(id); Index created. SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=>'BOWIE', cascade=> true, estimate_percent=> null, method_opt=> 'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed.
If we now make the index unusable:
SQL> alter index bowie_id_i unusable; Index altered. SQL> select index_name, blevel, leaf_blocks, num_rows, status, dropped from dba_indexes where index_name = 'BOWIE_ID_I'; INDEX_NAME BLEVEL LEAF_BLOCKS NUM_ROWS STATUS DRO ---------- ---------- ----------- ---------- -------- --- BOWIE_ID_I 2 2226 1000000 UNUSABLE NO SQL> select segment_name, bytes, blocks, extents from dba_segments where segment_name = 'BOWIE_ID_I'; SEGMENT_NAME BYTES BLOCKS EXTENTS ------------ ---------- ---------- ---------- BOWIE_ID_I 18874368 2304 18
We notice that the storage associated with the segment remains, even though the data within the index is now totally useless to us now. The index definition is of course vital but why bother continuing to assign 18 extents of storage (in this example) to the index ? Oracle 11g Release 2 has now by default changed this behaviour.
Using the same demo as before but running Oracle11g R2:
SQL> create table bowie as select rownum id, 'BOWIE' name from dual connect by level <= 1000000; Table created. SQL> create index bowie_id_i on bowie(id); Index created. SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=>'BOWIE', cascade=> true, estimate_percent=> null, method_opt=> 'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed. SQL> alter index bowie_id_i unusable; Index altered. SQL> select index_name, blevel, leaf_blocks, num_rows, status, dropped from dba_indexes where index_name = 'BOWIE_ID_I'; INDEX_NAME BLEVEL LEAF_BLOCKS NUM_ROWS STATUS DRO ---------- ---------- ----------- ---------- -------- --- BOWIE_ID_I 2 2226 1000000 UNUSABLE NO SQL> select segment_name, bytes, blocks, extents from dba_segments where segment_name = 'BOWIE_ID_I'; no rows selected
We notice that the storage associated with the object is all gone. The index object remains but the underling segment and storage have been automatically dropped.
If we now look at a partitioning example, where we create 3 types of indexes:
SQL> CREATE TABLE big_album_sales(id number, album_id number, country_id number, release_date date, total_sales number) PARTITION BY RANGE (release_date) (PARTITION ALBUMS_2007 VALUES LESS THAN (TO_DATE('01-JAN-2008', 'DD-MON-YYYY')), PARTITION ALBUMS_2008 VALUES LESS THAN (TO_DATE('01-JAN-2009', 'DD-MON-YYYY')), PARTITION ALBUMS_2009 VALUES LESS THAN (TO_DATE('01-JAN-2010', 'DD-MON-YYYY')), PARTITION ALBUMS_2010 VALUES LESS THAN (MAXVALUE)); Table created. SQL> INSERT INTO big_album_sales SELECT rownum, mod(rownum,5000)+1, mod(rownum,100)+1, sysdate-mod(rownum,2000), ceil(dbms_random.value(1,500000)) FROM dual CONNECT BY LEVEL <= 1000000; 1000000 rows created. SQL> commit; Commit complete.
We first create a Non-Partitioned Index:
SQL> CREATE INDEX big_album_tot_sales_i ON big_album_sales(total_sales); Index created.
Next a Global Partitioned Index:
SQL> CREATE INDEX big_album_country_id_i ON big_album_sales(country_id) GLOBAL PARTITION BY RANGE (country_id) (PARTITION TS1 VALUES LESS THAN (26), PARTITION TS2 VALUES LESS THAN (51), PARTITION TS3 VALUES LESS THAN (76), PARTITION TS4 VALUES LESS THAN (MAXVALUE)); Index created.
Finally, a Local Partitioned index:
SQL> CREATE INDEX big_album_album_id_i ON big_album_sales(album_id) local; Index created. SQL> exec dbms_stats.gather_table_stats(ownname=> 'BOWIE', tabname=> 'BIG_ALBUM_SALES', estimate_percent=> null, method_opt=> 'FOR ALL COLUMNS SIZE 1'); PL/SQL procedure successfully completed.
If we now split the last table partition, this will effectively make the:
1) Non-Partitioned Unusable
2) All partitions of the Global Partitioned index unusable
3) Just the last 2 partitions of the Local Partitioned Index unusable
SQL> ALTER TABLE big_album_sales SPLIT PARTITION ALBUMS_2010 AT (TO_DATE('01-JAN-2011', 'DD-MON-YYYY')) INTO (PARTITION ALBUMS_2010, PARTITION ALBUMS_2011); Table altered. SQL> select index_name, status from dba_indexes where table_name = 'BIG_ALBUM_SALES'; INDEX_NAME STATUS ------------------------ -------- BIG_ALBUM_TOT_SALES_I UNUSABLE BIG_ALBUM_COUNTRY_ID_I N/A BIG_ALBUM_ALBUM_ID_I N/A SQL> select index_name, partition_name, status, leaf_blocks from dba_ind_partitions where index_name like 'BIG_ALBUM_%'; INDEX_NAME PARTITION_NAME STATUS LEAF_BLOCKS ----------------------- -------------- -------- ----------- BIG_ALBUM_ALBUM_ID_I ALBUMS_2007 USABLE 807 BIG_ALBUM_ALBUM_ID_I ALBUMS_2008 USABLE 381 BIG_ALBUM_ALBUM_ID_I ALBUMS_2009 USABLE 383 BIG_ALBUM_ALBUM_ID_I ALBUMS_2010 UNUSABLE BIG_ALBUM_ALBUM_ID_I ALBUMS_2011 UNUSABLE BIG_ALBUM_COUNTRY_ID_I TS1 UNUSABLE 629 BIG_ALBUM_COUNTRY_ID_I TS2 UNUSABLE 629 BIG_ALBUM_COUNTRY_ID_I TS3 UNUSABLE 629 BIG_ALBUM_COUNTRY_ID_I TS4 UNUSABLE 629 SQL> select segment_name, partition_name, bytes, blocks from dba_segments where segment_name like 'BIG_ALBUM_%' and segment_type like 'INDEX%'; SEGMENT_NAME PARTITION_NAME BYTES BLOCKS --------------------- -------------- -------- ------ BIG_ALBUM_ALBUM_ID_I ALBUMS_2007 7340032 896 BIG_ALBUM_ALBUM_ID_I ALBUMS_2008 3145728 384 BIG_ALBUM_ALBUM_ID_I ALBUMS_2009 4194304 512 BIG_ALBUM_TOT_SALES_I 23068672 2816
We notice that all segments associated with the Global Partitioned index which are now unusable have been dropped. As have both unusable partitions from the Local Partitioned Index. However, the segment and storage associated with the unusable Non-Partitioned index still remains. Perhaps a missing feature for another time …
It’s a nice little touch that the unusable and somewhat useless index segments now get automatically cleaned out in Oracle11g R2, although they did previously act as “placeholders” in that nothing else within the tablespace could come along and use the occupied storage.
If we create partitioned (or non partitioned) indexes, I beleive we don’t have the intention to let them unusable for a long time (with altering the session to skip unusable indexes in passing).This is why I don’t clearly understood the added value of this new feature.
As far as my index understanding goes, we ought to set indexes in an usable state only when we want to reduce the execution time of an insert; but immediatley after the load has finished we have to rebuild those indexes.
So, were are the real life cases where we set indexes in an unusable state for a long time and hence get benefits from this Oracle 11gR2 new feature.
Best Regards
Mohamed Houri
LikeLike
Hi Mohamed
There are a number of real life cases where this can be extremely useful. However, to do the answer justice, I’ll write a follow-up blog piece.
LikeLike