Learn about CREATE INDEX
, B-Tree and Bitmap indexes, Function-Based Indexes, and best practices for their application to boost your Oracle SQL query speed.
Indexes for Performance
Indexes are schema objects that speed up data retrieval by providing quick access paths to rows in a table. They are essential for improving the performance of SQL queries, especially on large datasets.
CREATE INDEX: B-Tree, Bitmap Indexes
The CREATE INDEX
statement is used to create an index on one or more columns of a table. Oracle 19c primarily uses B-Tree indexes for most scenarios and Bitmap indexes for data with low cardinality.
Example 1: CREATE B-Tree Index
CREATE INDEX employees_last_name_idx
ON employees (last_name); -- Creates a B-Tree index on the last_name column
Explanation This code creates a standard B-Tree index on the last_name
column of the employees
table, accelerating queries that filter or sort by last name.
Example 2: CREATE Unique B-Tree Index
CREATE UNIQUE INDEX employees_email_uk
ON employees (email); -- Creates a unique B-Tree index on the email column
Explanation This statement creates a unique B-Tree index on the email
column, ensuring that no two employees can have the same email address.
Example 3: CREATE Composite B-Tree Index
CREATE INDEX orders_cust_prod_idx
ON orders (customer_id, product_id); -- Creates a composite B-Tree index on two columns
Explanation This code creates a composite index on customer_id
and product_id
, useful for queries filtering on both columns.
Example 4: CREATE Bitmap Index
CREATE BITMAP INDEX products_category_bmi
ON products (category_id); -- Creates a Bitmap index on the category_id column
Explanation This statement creates a Bitmap index on category_id
, ideal for columns with few distinct values and typically used in data warehousing.
Example 5: CREATE Index with DESC/ASC Ordering
CREATE INDEX orders_order_date_desc_idx
ON orders (order_date DESC); -- Creates an index with descending order for efficient sorting
Explanation This code creates an index on order_date
in descending order, optimizing queries that sort results by order_date
in descending fashion.
Function-Based Indexes
Function-Based Indexes (FBIs) are indexes created on expressions or functions. They are invaluable for optimizing queries where the WHERE
clause or ORDER BY
clause involves functions or expressions on table columns.
Example 1: CREATE Function-Based Index for Uppercase Comparison
CREATE INDEX employees_upper_last_name_fbi
ON employees (UPPER(last_name)); -- Creates an FBI for case-insensitive last name searches
Explanation This index optimizes queries like WHERE UPPER(last_name) = 'SMITH'
, avoiding full table scans when searching for last names case-insensitively.
Example 2: CREATE Function-Based Index for Date Extraction
CREATE INDEX orders_year_fbi
ON orders (EXTRACT(YEAR FROM order_date)); -- Creates an FBI for filtering by year
Explanation This FBI accelerates queries that extract the year from the order_date
column, such as WHERE EXTRACT(YEAR FROM order_date) = 2024
.
Example 3: CREATE Function-Based Index for Substring Search
CREATE INDEX products_first_char_fbi
ON products (SUBSTR(product_name, 1, 1)); -- Creates an FBI for searching by first character
Explanation This index helps performance for queries looking up products by their first initial, for instance, WHERE SUBSTR(product_name, 1, 1) = 'A'
.
Example 4: CREATE Function-Based Index for Concatenated Columns
CREATE INDEX customers_full_name_fbi
ON customers (first_name || ' ' || last_name); -- Creates an FBI for searches on full name
Explanation This FBI is useful for optimizing searches or sorts based on the concatenated full name, e.g., WHERE first_name || ' ' || last_name = 'John Doe'
.
Example 5: CREATE Function-Based Index with NULL Handling
CREATE INDEX employees_commission_pct_fbi
ON employees (NVL(commission_pct, 0)); -- Creates an FBI to handle NULL values as zero
Explanation This index optimizes queries that treat NULL
commission percentages as zero, like WHERE NVL(commission_pct, 0) > 0.10
.
When to Use and Not Use Indexes
Indexes can significantly improve query performance, but they also incur overhead during DML operations. Understanding when to use them and when to avoid them is crucial for database optimization.
When to Use Indexes
Indexes are beneficial for columns frequently used in WHERE
clauses, JOIN
conditions, ORDER BY
clauses, or GROUP BY
clauses. They are especially effective on large tables where only a small percentage of rows are retrieved by a query.
Example 1: Indexing Foreign Keys for Joins
CREATE INDEX order_details_order_id_fk_idx
ON order_details (order_id); -- Indexing a foreign key column for faster joins
Explanation This index dramatically improves the performance of joins between orders
and order_details
tables on the order_id
column.
Example 2: Indexing Columns in WHERE Clauses
CREATE INDEX products_price_idx
ON products (price); -- Indexing a column frequently filtered by
Explanation This index speeds up queries that filter products based on their price
, such as SELECT * FROM products WHERE price > 100
.
Example 3: Indexing Columns in ORDER BY
CREATE INDEX employees_hire_date_idx
ON employees (hire_date); -- Indexing a column used for sorting results
Explanation This index allows Oracle to retrieve sorted results directly from the index, avoiding a separate sort operation for queries using ORDER BY hire_date
.
Example 4: Indexing High Cardinality Columns
CREATE INDEX customers_customer_id_idx
ON customers (customer_id); -- Indexing a unique or highly distinct column
Explanation customer_id
is likely unique or has a high number of distinct values (high cardinality), making a B-Tree index very efficient for lookups.
Example 5: Indexing Columns for Aggregation (GROUP BY)
CREATE INDEX sales_region_idx
ON sales (region); -- Indexing a column used in GROUP BY for aggregated reports
Explanation This index helps speed up queries that group sales data by region
, reducing the amount of data the database needs to scan for aggregation.
When Not to Use Indexes
Avoid indexing columns in small tables, columns with very few distinct values (low cardinality, unless for bitmap indexes), or columns that are rarely queried. Over-indexing can lead to slower DML operations and increased storage overhead.
Example 1: Small Tables
-- Assume 'lookup_countries' is a very small table (e.g., < 100 rows)
-- CREATE INDEX lookup_countries_code_idx ON lookup_countries (country_code);
-- Indexing small tables often provides no significant performance benefit.
Explanation For very small tables, a full table scan is often faster or as fast as using an index, as the overhead of index lookup outweighs the benefit.
Example 2: Columns with Very Low Cardinality (for B-Tree)
-- Assume 'employees' table has 'gender' column ('M', 'F')
-- CREATE INDEX employees_gender_idx ON employees (gender);
-- A B-Tree index on a column with very few distinct values is generally not effective.
Explanation For columns like gender
where only a few distinct values exist, a B-Tree index often causes Oracle to perform a full table scan anyway, rendering the index ineffective. Bitmap indexes are an exception here.
Example 3: Columns Rarely Used in WHERE Clauses
-- Assume 'product_description_last_updated' is rarely used for filtering
-- CREATE INDEX products_desc_updated_idx ON products (product_description_last_updated);
-- Indexing columns not frequently queried is a waste of resources.
Explanation If a column is rarely part of WHERE
, ORDER BY
, or JOIN
clauses, the overhead of maintaining its index outweighs any occasional query benefit.
Example 4: Columns with Frequent DML Operations
-- Assume 'transactions_status' is updated very frequently
-- CREATE INDEX transactions_status_idx ON transactions (status);
-- Frequent updates to an indexed column can degrade DML performance.
Explanation Every INSERT
, UPDATE
, or DELETE
operation on an indexed column requires the index to be updated, which adds overhead and slows down DML.
Example 5: Columns with Large Text Data (unless specific search patterns)
-- Assume 'large_comments' column stores extensive text
-- CREATE INDEX articles_comments_idx ON articles (large_comments);
-- Indexing large text columns without a specific function-based approach is usually inefficient.
Explanation Directly indexing large text columns (VARCHAR2
, CLOB
) is typically not efficient for general keyword searches; instead, Oracle Text or Function-Based Indexes with specific functions (like SUBSTR
) are preferred.
Monitoring Index Usage
Monitoring index usage is crucial to ensure that indexes are effectively contributing to database performance. Oracle 19c provides mechanisms to track whether indexes are being used by queries, helping identify unused or underutilized indexes.
Example 1: Enable Index Monitoring
ALTER INDEX employees_last_name_idx MONITORING USAGE; -- Starts monitoring for a specific index
Explanation This command enables monitoring for the employees_last_name_idx
, allowing Oracle to track if and how often this index is used.
Example 2: Query Index Usage View
SELECT index_name, used
FROM V$OBJECT_USAGE
WHERE object_name = 'EMPLOYEES_LAST_NAME_IDX'; -- Checks if the index has been used since monitoring started
Explanation This query against V$OBJECT_USAGE
shows whether employees_last_name_idx
has been accessed by a query since monitoring was enabled.
Example 3: Disable Index Monitoring
ALTER INDEX employees_last_name_idx NOMONITORING USAGE; -- Stops monitoring for the index
Explanation This command disables usage monitoring for the specified index, ceasing the tracking of its activity.
Example 4: Drop Unused Index (Based on Monitoring)
-- After monitoring for a period and confirming no usage:
-- DROP INDEX employees_unused_idx; -- Removes an index confirmed to be unused
Explanation If monitoring reveals an index is never used, dropping it can reduce DML overhead and save storage space, improving overall database efficiency.
Example 5: Gathering Index Statistics
EXEC DBMS_STATS.GATHER_INDEX_STATS('HR', 'EMPLOYEES_LAST_NAME_IDX'); -- Gathers statistics for an index
Explanation Gathering index statistics (DBMS_STATS.GATHER_INDEX_STATS
) ensures the Oracle optimizer has up-to-date information about the index, helping it make better execution plan decisions.