VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
How Row Lookups Work (and Why They’re Expensive)
A regular index lookup is two steps:- MySQL searches the index to find which rows match
- For each matching row, MySQL fetches the full row from the table (a “row lookup” or “back-to-table lookup”)
Identifying a Covering Index in EXPLAIN
EXPLAIN tells you when a covering index is in use: look for Extra: Using index. If you’re not yet familiar with EXPLAIN output, Reading EXPLAIN in MySQL covers all the key columns.
Using index — MySQL read only the index. No row lookups.
Now add a column that’s not in the index:
Extra is now empty — MySQL uses the index to find rows, then fetches the full row to get order_total. The row lookup is back.
Designing a Covering Index
The goal is to include every column the query touches — in theWHERE, ORDER BY, GROUP BY, and SELECT list. Column order still follows the leftmost prefix rule for WHERE and ORDER BY filtering:
user_idfirst — the equality filtercreated_atsecond — the sort column (index already ordered, no filesort)statuslast — only in theSELECT, not in filtering or sorting
SELECT columns come last because they don’t affect which rows are found or in what order — they’re just along for the ride.
When Covering Indexes Are Worth It
| Scenario | Worth it? |
|---|---|
| High-traffic query on a large table | Yes |
Narrow SELECT list (a few columns) | Yes |
| Read-heavy workload | Yes |
| Small table | No — row lookups are fast anyway |
SELECT * query | No — you can’t cover all columns without indexing the whole table |
| Write-heavy table | Evaluate — every INSERT/UPDATE/DELETE updates the index |
Frequently Asked Questions
Does a covering index help with SELECT *?
No. An index can only cover a query if it contains every column in the SELECT list. SELECT * selects all columns, so you’d need to index all columns — at that point you’ve essentially duplicated the table as an index, which isn’t useful.
Can a covering index still speed up SELECT * queries?
Yes, partially. Even if MySQL has to do row lookups, the index narrows down which rows to fetch. The improvement comes from the WHERE filtering, not from eliminating row lookups.
How do I know if a covering index would help?
RunEXPLAIN and check Extra. If you see Using index condition or nothing (no Using index), and rows is large, a covering index might help. EXPLAIN ANALYZE shows actual vs. estimated row counts, which is useful for confirming the optimizer’s choices.
Troubleshooting
| Problem | Solution |
|---|---|
Extra shows nothing instead of Using index | A column in the SELECT is not in the index — add it, or remove it from the query |
Extra: Using index condition instead of Using index | The index is used for filtering but not fully covering — add the remaining SELECT columns |
Index exists but key is NULL | Run ANALYZE TABLE to refresh statistics; check that the query’s WHERE uses the leftmost index column |
| Covering index slowing down writes | Expected trade-off — evaluate whether the read benefit outweighs the write cost for your workload |

