Composite Indexes
Optimize multi-column queries with composite indexes.
Before (Inefficient Query)
SELECT * FROM orders
WHERE status = 'shipped'
AND created_at > '2024-01-01';
After (Optimized Query)
-- Create composite index
CREATE INDEX idx_orders_status_created
ON orders(status, created_at);
-- Query uses the composite index efficiently
SELECT * FROM orders
WHERE status = 'shipped'
AND created_at > '2024-01-01';
Why This Optimization Works
-
Index Column Order Matters: The order of columns in a composite index determines its usefulness. Columns used in equality conditionsi (=status) should come first, followed by columns used in range conditionsi (created_at >).
-
Index Only Scani: With a proper composite index, the database can satisfy the entire query from the index without accessing the table data (index-only scan).
-
Leading Column Rulei: A composite index can only be used if the query filters on the leading (leftmost) column(s).
Composite Index Rules
Good Order (Equality + Range)
CREATE INDEX idx ON table(status, created_at);
-- Used: WHERE status = 'x' AND created_at > 'y'
Bad Order (Range + Equality)
CREATE INDEX idx ON table(created_at, status);
-- NOT used: WHERE status = 'x' AND created_at > 'y'
Column Order in Tables
Sometimes a row may exceed the size of one database page. When this is the case, the database benefits from having indexed columns first since they are the first things read on the page. If a large column comes before the index, it may take the database engine 1, 2, or even 3 page reads to find the index. Place indexed fields before fields with large amounts of data.
-- Recommended: Indexed columns first
CREATE TABLE orders (
id INT PRIMARY KEY, -- Indexed, read first
status VARCHAR(20), -- Indexed, read second
notes TEXT, -- Large field, read last
created_at TIMESTAMP -- Indexed, read third
);
-- Less efficient: Large columns before indexes
CREATE TABLE orders_bad (
id INT PRIMARY KEY,
notes TEXT, -- Large field read before indexes
status VARCHAR(20),
created_at TIMESTAMP
);
Query Coverage
An index-only scani occurs when all required columns are included in the index:
-- Covering index includes all selected columns
CREATE INDEX idx_covering ON orders(status, created_at, order_id, total);
SELECT order_id, total FROM orders WHERE status = 'shipped';