What is a covering index, and why does it make a query faster?
A covering index is one that contains every column the query needs. Because the index already holds those values, the engine can answer the query entirely from the index structure and never look up the row in the table.
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) NOT NULL,
note TEXT
);
-- The index holds customer_id and amount: everything the query needs.
CREATE INDEX idx_orders_cust_amount ON orders (customer_id, amount);
EXPLAIN QUERY PLAN
SELECT amount FROM orders WHERE customer_id = 1;The plan says SEARCH orders USING COVERING INDEX idx_orders_cust_amount (customer_id=?). The word COVERING is the important part — the engine reads the index and stops there.
The saving is a whole random access per matching row. A normal lookup is two steps: find the key in the index, then follow the row pointer to the table and read the row's page. On a table larger than your buffer cache, that second step is a disk read, and a query matching a thousand rows does a thousand of them, in whatever order the index happens to produce. A covering index turns that into a single sequential scan of the narrow index structure.
That is why covering matters most on wide tables, on queries that return many rows, and on tables with poor locality — where the clustered structure is not in the order the query needs.
Two notes. The index must contain the columns the query selects as well as the ones it filters on, so the covering index is a composite index whose key is the filter columns and whose tail is the selected columns. And the cover only holds while the query stays the same — add a column to the SELECT list and the index stops covering, which the plan will tell you immediately.