Non-Selective Index Statistics (dstatbi.sql)
What This Script Does
Section titled “What This Script Does”This script identifies indexes with poor selectivity by analyzing the average number of leaf blocks and data blocks per key. It helps DBAs find indexes that may be causing performance problems due to low cardinality, excessive index range scans, or poor clustering factor, making them candidates for redesign or removal.
Script
Section titled “Script”rem dstatbi.sqlremttitle 'Non-selective Index Statistics'remcol table_name format a24 heading 'TABLE NAME'col index_name format a23 heading 'INDEX NAME'col u format a1 heading 'U'col blevel format 0 heading 'BL'col leaf_blocks format 999990 heading 'LEAF|BLOCKS'col distinct_keys format 9999990 heading 'DISTINCT|KEYS'col avg_leaf_blocks_per_key format 990 heading 'LEAF|BLKS|/KEY'col avg_data_blocks_per_key format 990 heading 'DATA|BLKS|/KEY'rembreak on table_nameremselect table_name, index_name, decode( uniqueness, 'UNIQUE', 'U', null ) u, blevel, leaf_blocks, distinct_keys, avg_leaf_blocks_per_key, avg_data_blocks_per_key from sys.dba_indexes where table_owner like upper('&owner') and table_name like upper('&table') and ( avg_leaf_blocks_per_key >= 5 or avg_data_blocks_per_key >= 100 ) order by table_owner, table_name, index_name;
-- Run the script in SQL*Plus or SQLcl@dstatbi.sql
-- When prompted, enter parameters:-- owner: Schema owner (% for all)-- table: Table name pattern (% for all)
-- Examples:Enter value for owner: HREnter value for table: %
-- Check specific tableEnter value for owner: SALESEnter value for table: ORDERS
Parameters
Section titled “Parameters”- &owner - Schema owner (supports wildcards)
- &table - Table name pattern
Required Privileges
Section titled “Required Privileges”- SELECT on DBA_INDEXES
- Typically requires DBA role
Sample Output
Section titled “Sample Output”Non-selective Index Statistics
U LEAF DISTINCT LEAF DATATABLE NAME INDEX NAME BL BLOCKS KEYS BLKS BLKS /KEY /KEY------------------------ ----------------------- -- ------ -------- ---- ----EMPLOYEES EMP_DEPT_IDX 2 1234 12 103 856 EMP_STATUS_IDX 2 567 3 189 412
ORDER_ITEMS ITEM_STATUS_IDX 3 8901 5 1780 234 ITEM_CATEGORY_IDX 3 4567 45 101 156
TRANSACTIONS TRANS_TYPE_IDX 3 12345 10 1234 890 TRANS_FLAG_IDX 2 3456 2 1728 567
Key Output Columns
Section titled “Key Output Columns”- TABLE NAME - Name of the indexed table
- INDEX NAME - Name of the index
- U - Uniqueness indicator (U=Unique, blank=Non-unique)
- BL - B-level (height of index tree)
- LEAF BLOCKS - Number of leaf blocks in index
- DISTINCT KEYS - Number of distinct values
- LEAF BLKS/KEY - Average leaf blocks per distinct key
- DATA BLKS/KEY - Average data blocks per distinct key
Understanding the Metrics
Section titled “Understanding the Metrics”Selectivity Indicators
Section titled “Selectivity Indicators”- High LEAF BLKS/KEY (≥5) - Many index entries per distinct value
- High DATA BLKS/KEY (≥100) - Poor clustering, data scattered
- Low DISTINCT KEYS - Low cardinality, poor selectivity
Performance Impact
Section titled “Performance Impact”- Non-selective indexes cause excessive I/O
- Range scans read many blocks for few rows
- May perform worse than full table scans
- Increases buffer cache pressure
Common Use Cases
Section titled “Common Use Cases”Find Low Cardinality Indexes
Section titled “Find Low Cardinality Indexes”SELECT table_name, index_name, distinct_keys, num_rows, ROUND(distinct_keys/num_rows*100, 2) selectivity_pctFROM dba_indexesWHERE table_owner = '&owner'AND num_rows > 0AND distinct_keys/num_rows < 0.01 -- Less than 1% selectiveORDER BY distinct_keys/num_rows;
Identify Clustering Issues
Section titled “Identify Clustering Issues”SELECT table_name, index_name, clustering_factor, num_rows, blocksFROM dba_indexesWHERE table_owner = '&owner'AND clustering_factor > blocks * 10ORDER BY clustering_factor DESC;
Check Index Efficiency
Section titled “Check Index Efficiency”SELECT index_name, leaf_blocks, distinct_keys, ROUND(leaf_blocks/GREATEST(distinct_keys,1), 2) blocks_per_keyFROM dba_indexesWHERE table_owner = '&owner'AND leaf_blocks/GREATEST(distinct_keys,1) > 10ORDER BY leaf_blocks/GREATEST(distinct_keys,1) DESC;
Performance Analysis
Section titled “Performance Analysis”Index Usage Monitoring
Section titled “Index Usage Monitoring”-- Enable monitoringALTER INDEX owner.index_name MONITORING USAGE;
-- Check usage after workloadSELECT index_name, monitoring, usedFROM v$object_usageWHERE index_name = 'INDEX_NAME';
Cost Analysis
Section titled “Cost Analysis”-- Compare index scan vs full table scan costEXPLAIN PLAN FORSELECT * FROM table_nameWHERE indexed_column = 'value';
SELECT operation, options, cost, cardinalityFROM plan_tableWHERE plan_id = (SELECT MAX(plan_id) FROM plan_table);
Remediation Strategies
Section titled “Remediation Strategies”Improve Selectivity
Section titled “Improve Selectivity”-- Add columns to make index more selectiveCREATE INDEX new_idx ON table_name(low_card_col, high_card_col);
-- Use function-based index for better selectivityCREATE INDEX func_idx ON table_name(UPPER(column_name));
Handle Low Cardinality
Section titled “Handle Low Cardinality”-- Consider bitmap index for low cardinality (OLAP only)CREATE BITMAP INDEX bitmap_idx ON table_name(status_column);
-- Or use partitioning insteadALTER TABLE table_name PARTITION BY LIST (status_column);
Rebuild for Better Clustering
Section titled “Rebuild for Better Clustering”-- Rebuild index after table reorganizationALTER INDEX owner.index_name REBUILD;
-- Rebuild with better storage parametersALTER INDEX owner.index_name REBUILDTABLESPACE new_tsPCTFREE 5;
Best Practices
Section titled “Best Practices”Index Design
Section titled “Index Design”- Avoid indexing low cardinality columns alone
- Place selective columns first in composite indexes
- Consider bitmap indexes for data warehouses
- Monitor index usage before dropping
Maintenance
Section titled “Maintenance”- Regularly gather index statistics
- Monitor clustering factor trends
- Rebuild indexes after bulk operations
- Consider invisible indexes for testing
Troubleshooting
Section titled “Troubleshooting”False Positives
Section titled “False Positives”-- Check if statistics are staleSELECT table_name, index_name, last_analyzed, ROUND(SYSDATE - last_analyzed) days_oldFROM dba_indexesWHERE table_owner = '&owner'AND last_analyzed < SYSDATE - 30;
Verify with Real Queries
Section titled “Verify with Real Queries”-- Check actual execution plansSELECT sql_id, child_number, operation, options, cardinality, costFROM v$sql_planWHERE object_name = 'INDEX_NAME'AND operation LIKE '%INDEX%';
Related Scripts
Section titled “Related Scripts”- Index Column Analysis (../schema-analysis/dindcol.md) - Index composition
- Index Storage Analysis (../schema-analysis/dindex.md) - Index storage details
- Index Statistics Analysis (../schema-analysis/dindstats.md) - Comprehensive stats
Advanced Analysis
Section titled “Advanced Analysis”Histogram Impact
Section titled “Histogram Impact”-- Check if histograms exist for indexed columnsSELECT c.table_name, c.column_name, c.num_distinct, c.histogram, i.index_nameFROM dba_tab_col_statistics c, dba_ind_columns iWHERE c.owner = i.table_ownerAND c.table_name = i.table_nameAND c.column_name = i.column_nameAND c.owner = '&owner'AND c.histogram != 'NONE';
Composite Index Analysis
Section titled “Composite Index Analysis”-- Analyze multi-column index selectivitySELECT index_name, column_position, column_nameFROM dba_ind_columnsWHERE table_owner = '&owner'AND index_name IN ( SELECT index_name FROM dba_indexes WHERE avg_data_blocks_per_key > 100)ORDER BY index_name, column_position;