Posted by Kyle Hankinson May 20, 2022
An index makes queries faster by letting the database find rows without scanning the entire table. Understanding how they work helps you write faster queries and avoid common performance traps.
A database index works like a book's index. Without an index, finding a topic means reading every page (a full table scan). With an index, you look up the topic, get the page number, and go directly there.
A database index stores a sorted copy of specific columns along with pointers back to the full rows.
CREATE INDEX idx_users_email ON users (email);
Now queries filtering by email can use the index instead of scanning every row:
-- Fast: uses the index
SELECT * FROM users WHERE email = 'alice@example.com';
-- Slow without an index: full table scan
The standard index type in every database. Supports equality (=), range (>, <, BETWEEN), and sorting (ORDER BY). This is what you get with a plain CREATE INDEX.
Fast for equality lookups only. Not useful for range queries or sorting. Available in PostgreSQL and MySQL (MEMORY engine).
Generalized Inverted Index — designed for values that contain multiple elements: arrays, JSONB, and full-text search.
Purpose-built for searching text content. Available in MySQL, PostgreSQL, and SQL Server.
An index on multiple columns:
CREATE INDEX idx_orders_customer_date ON orders (customer_id, order_date);
The leftmost prefix rule is critical: this index helps queries that filter by:
customer_id alonecustomer_id AND order_dateBut NOT:
order_date alone (the index cannot be used)Column order matters. Put the most selective (most-filtered) column first.
The table data is physically stored in the index order. Each table can have only one.
A separate structure that points back to the table data. A table can have many non-clustered indexes.
Indexes are not free — they slow down INSERT, UPDATE, and DELETE because the index must be updated too.
Avoid indexing:
status column with only 3 possible values)-- MySQL
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';
-- PostgreSQL
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@example.com';
-- SQL Server
SET SHOWPLAN_ALL ON;
SELECT * FROM users WHERE email = 'alice@example.com';
SET SHOWPLAN_ALL OFF;
Look for "index scan" or "index seek" in the output. If you see "table scan" or "seq scan," the index is not being used.
Over-indexing: Adding an index on every column wastes disk space and slows writes.
Indexing expressions without expression indexes: WHERE UPPER(email) = 'ALICE@EXAMPLE.COM' will not use an index on email. You need a functional index:
-- PostgreSQL
CREATE INDEX idx_users_email_upper ON users (UPPER(email));
-- MySQL 8.0+
CREATE INDEX idx_users_email_upper ON users ((UPPER(email)));
Forgetting covering indexes: If the index contains all columns the query needs, the database never has to look at the table at all (an index-only scan).
About the author — Kyle Hankinson is the founder and sole developer of SQLPro Studio and the Hankinsoft Development suite of database tools. He has been building native macOS and iOS applications since 2010.
Try SQLPro Studio — A powerful database manager for MySQL, PostgreSQL, Microsoft SQL Server, SQLite, Oracle, and Snowflake. Available on macOS, iOS, and Windows.
Download Free Trial View Pricing Compare