Carlos Sierra's Tools and Tips

Tools and Tips for Oracle Performance and SQL Tuning

Script to identify index rebuild candidates on 12c

with 17 comments

Some time back I blogged about an easy way to estimate the size of an index. It turns out there is an API that also uses the plan_table under the hood in order to estimate what would be the size of an index if it were rebuilt. Such API is DBMS_SPACE.CREATE_INDEX_COST.

Script below uses  DBMS_SPACE.CREATE_INDEX_COST, and when executed on 12c connected into a PDB, it outputs a list of indexes with enlarged space, which if rebuilt they would shrink at least 25% their current size. This script depends on the accuracy of the CBO statistics.

Once you are comfortable with the output, you may even consider automating its execution using OEM. I will post in a few days a way to do that using DBMS_SQL. In the meantime, here I share the stand-alone version.

----------------------------------------------------------------------------------------
--
-- File name: indexes_2b_shrunk.sql
--
-- Purpose: List of candidate indexes to be shrunk (rebuild online)
--
-- Author: Carlos Sierra
--
-- Version: 2017/07/12
--
-- Usage: Execute on PDB
--
-- Example: @indexes_2b_shrunk.sql
--
-- Notes: Execute connected into a PDB.
-- Consider then:
-- ALTER INDEX [schema.]index REBUILD ONLINE;
--
---------------------------------------------------------------------------------------

-- select only those indexes with an estimated space saving percent greater than 25%
VAR savings_percent NUMBER;
EXEC :savings_percent := 25;
-- select only indexes with current size (as per cbo stats) greater then 1MB
VAR minimum_size_mb NUMBER;
EXEC :minimum_size_mb := 1;

SET SERVEROUT ON ECHO OFF FEED OFF VER OFF TAB OFF LINES 300;

COL report_date NEW_V report_date;
SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD"T"HH24:MI:SS') report_date FROM DUAL;
SPO /tmp/indexes_2b_shrunk_&&report_date..txt;

DECLARE
l_used_bytes NUMBER;
l_alloc_bytes NUMBER;
l_percent NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE('PDB: '||SYS_CONTEXT('USERENV', 'CON_NAME'));
DBMS_OUTPUT.PUT_LINE('---');
DBMS_OUTPUT.PUT_LINE(
RPAD('OWNER.INDEX_NAME', 35)||' '||
LPAD('SAVING %', 10)||' '||
LPAD('CURRENT SIZE', 20)||' '||
LPAD('ESTIMATED SIZE', 20));
DBMS_OUTPUT.PUT_LINE(
RPAD('-', 35, '-')||' '||
LPAD('-', 10, '-')||' '||
LPAD('-', 20, '-')||' '||
LPAD('-', 20, '-'));
FOR i IN (SELECT x.owner, x.index_name, SUM(s.leaf_blocks) * TO_NUMBER(p.value) index_size,
REPLACE(DBMS_METADATA.GET_DDL('INDEX',x.index_name,x.owner),CHR(10),CHR(32)) ddl
FROM dba_ind_statistics s, dba_indexes x, dba_users u, v$parameter p
WHERE u.oracle_maintained = 'N'
AND x.owner = u.username
AND x.tablespace_name NOT IN ('SYSTEM','SYSAUX')
AND x.index_type LIKE '%NORMAL%'
AND x.table_type = 'TABLE'
AND x.status = 'VALID'
AND x.temporary = 'N'
AND x.dropped = 'NO'
AND x.visibility = 'VISIBLE'
AND x.segment_created = 'YES'
AND x.orphaned_entries = 'NO'
AND p.name = 'db_block_size'
AND s.owner = x.owner
AND s.index_name = x.index_name
GROUP BY
x.owner, x.index_name, p.value
HAVING
SUM(s.leaf_blocks) * TO_NUMBER(p.value) > :minimum_size_mb * POWER(2,20)
ORDER BY
index_size DESC)
LOOP
DBMS_SPACE.CREATE_INDEX_COST(i.ddl,l_used_bytes,l_alloc_bytes);
IF i.index_size * (100 - :savings_percent) / 100 > l_alloc_bytes THEN
l_percent := 100 * (i.index_size - l_alloc_bytes) / i.index_size;
DBMS_OUTPUT.PUT_LINE(
RPAD(i.owner||'.'||i.index_name, 35)||' '||
LPAD(TO_CHAR(ROUND(l_percent, 1), '990.0')||' % ', 10)||' '||
LPAD(TO_CHAR(ROUND(i.index_size / POWER(2,20), 1), '999,999,990.0')||' MB', 20)||' '||
LPAD(TO_CHAR(ROUND(l_alloc_bytes / POWER(2,20), 1), '999,999,990.0')||' MB', 20));
END IF;
END LOOP;
END;
/

SPO OFF;

And if you want to try the DBMS_SPACE.CREATE_INDEX_COST API by itself, you can also grab the estimated size of the index after calling this API, using query below. But the API already returns that value!


SELECT TO_NUMBER(EXTRACTVALUE(VALUE(d), '/info')) index_size
FROM XMLTABLE('/*/info'
PASSING (SELECT XMLTYPE(other_xml)
FROM plan_table
WHERE other_xml LIKE '%index_size%')) d
WHERE EXTRACTVALUE(VALUE(d), '/info/@type') = 'index_size'
/

Advertisement

Written by Carlos Sierra

July 12, 2017 at 11:03 am

Posted in Index Rebuild

17 Responses

Subscribe to comments with RSS.

  1. Very useful! Thanks again Carlos!!

    Zanotti

    July 12, 2017 at 6:09 pm

  2. Thanks for the post.
    Foued

    fouedgray

    July 13, 2017 at 5:55 am

  3. OWNER.INDEX_NAME SAVING % CURRENT SIZE ESTIMATED SIZE
    ———————————– ———- ——————– ——————–
    DECLARE
    *
    ERROR at line 1:
    ORA-38029: object statistics are locked
    ORA-06512: at “SYS.DBMS_SPACE”, line 2833
    ORA-06512: at “SYS.DBMS_SPACE”, line 2854
    ORA-06512: at line 42

    Ayaaz

    July 17, 2017 at 10:33 pm

  4. OWNER.INDEX_NAME SAVING % CURRENT SIZE ESTIMATED SIZE
    ———————————– ———- ——————– ——————–
    DECLARE
    *
    ERROR at line 1:
    ORA-38029: object statistics are locked
    ORA-06512: at “SYS.DBMS_SPACE”, line 2833
    ORA-06512: at “SYS.DBMS_SPACE”, line 2854
    ORA-06512: at line 42

    mayaazh

    July 17, 2017 at 11:29 pm

    • consider unlocking the stats

      Carlos Sierra

      July 28, 2017 at 3:07 pm

      • or create exception
        ..
        BEGIN
        DBMS_SPACE.CREATE_INDEX_COST(i.ddl,l_used_bytes,l_alloc_bytes);
        IF i.index_size * (100 – :savings_percent) / 100 > l_alloc_bytes THEN
        l_percent := 100 * (i.index_size – l_alloc_bytes) / i.index_size;
        DBMS_OUTPUT.PUT_LINE(‘alter index ‘||
        RPAD(i.owner||’.’||i.index_name, 35)||’ rebuild parallel 16; ‘||
        LPAD(TO_CHAR(ROUND(l_percent, 1), ‘990.0’)||’ % ‘, 10)||’ ‘||
        LPAD(TO_CHAR(ROUND(i.index_size / POWER(2,20), 1), ‘999,999,990.0’)||’ MB’, 20)||’ ‘||
        LPAD(TO_CHAR(ROUND(l_alloc_bytes / POWER(2,20), 1), ‘999,999,990.0’)||’ MB’, 20));
        END IF;
        EXCEPTION WHEN OTHERS THEN
        NULL;
        END;

        R/Peter

        Peter Sliwinski-Clausner

        August 21, 2017 at 12:51 am

  5. Hi,

    for indexes with long names

    I have incressed padding lenght to 55 from 35

    D:\sql\sql>grep 55 indexes_2b_shrunk.sq
    l
    RPAD(‘OWNER.INDEX_NAME’, 55)||’ ‘||
    RPAD(‘-‘, 55, ‘-‘)||’ ‘||
    RPAD(i.owner||’.’||i.index_name, 55)||’ ‘||

    R/Peter

    Peter Sliwinski-Clausner

    August 21, 2017 at 1:02 am

  6. […] Script to identify index rebuild candidates on 12c […]

  7. PL/SQL procedure successfully completed.

    PL/SQL procedure successfully completed.

    REPORT_DATE
    —————————————————————————
    2018-05-22T10:32:51
    PDB: misprod

    OWNER.INDEX_NAME SAVING % CURRENT SIZE ESTIMATED SIZE
    ———————————– ———- ——————– ——————–
    DECLARE
    *
    ERROR at line 1:
    ORA-00600: internal error code, arguments: [kkqcscpqbc_Int:!newOplP], [], [], [], [], [], [], [], [], [], [], []

    Lucho Perez

    May 22, 2018 at 8:38 am

    • You can reach out to Oracle or My Oracle Support in order to diagnose the ORA-00600 error you get in your database.

      Carlos Sierra

      May 23, 2018 at 8:39 am

  8. I’d like to order the output by “Saving %”. Is that possible?

    Steve S

    January 25, 2019 at 10:18 am

    • Not easy to do.

      Carlos Sierra

      February 19, 2019 at 5:15 pm

      • This script doesn’t seem to work for partitioned indexes. Can it be modified to do that?

        Steven Siadek

        March 28, 2019 at 3:23 pm

      • sure. i would do that but i have too many items on my to-do list. by all means please feel free to make it your own and update it as needed.

        Carlos Sierra

        March 28, 2019 at 4:36 pm

  9. I tried modification to identify partitioned indexes but the ddl passed to the dbms_space.create_index_cost loop was too big for the varchar2 data type. Got ORA-06502: PL/SQL: numeric or value error. Too bad there isn’t a clob overload. Anybody else find a way??

    Gordon Ball

    February 8, 2021 at 5:57 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: