Skip to content

Expert: design, speed and engines · Indexes, query plans and views · topic 47 of 62 · 30 min

How indexes work

Explanation

Without an index, the database must read every row to find matches: a full table scan. An index is a separate, sorted structure (usually a B-tree) that lets it jump to the matching rows in a few steps, like the index at the back of a book. Indexes make reads fast but cost disk space and slow down every insert, update and delete a little, so you add them for real queries, not everywhere.

The problem: reading everything

Ask SHOP for the orders of customer 1 and the database has a choice. If nothing helps it, it reads every row of orders and keeps those with customer_id = 1. That is a full table scan. With 8 rows nobody notices. With 5 crore orders on a sale day, each scan reads gigabytes, and a page that ran this query on every visit would grind to a halt.

Think of it like this: An index is like the index at the back of a textbook. To find "photosynthesis" you do not read all 400 pages; you look it up in the alphabetical index, which says "pages 112, 118", and you turn straight there. The index is extra pages the publisher had to print and keep up to date, and that is exactly the trade-off in a database too.

What an index is

An index is a separate structure that stores the values of one or more columns in sorted order, each with a pointer to its row. In SQLite the pointer is the rowid; in PostgreSQL it is the row's physical location; in MySQL's InnoDB, secondary indexes store the primary key. Because the values are sorted, the database can find a value without looking at every entry.

B-trees in plain words

Almost every database uses a B-tree (balanced tree) for ordinary indexes. Picture a tree of pages. The top page holds a few hundred sorted keys that split the whole range into parts ("A to F go left, G to M next..."). Each part points to a page lower down that splits again, until the bottom pages (leaves) hold the actual values and row pointers.

  • Sorted: values are in order, so a range like order_date between two dates is one walk along the leaves.
  • Balanced: every leaf is the same distance from the top, so every lookup takes the same few steps.
  • Wide: each page holds hundreds of keys, so even crores of rows need only about three or four levels.
  • Fast: the number of steps grows with the logarithm of the row count, written O(log n). Doubling the table adds roughly one comparison, not double the work.

Indexes you already have

Primary keys and UNIQUE constraints are indexed automatically, because the database must check them quickly on every insert. In SQLite, an INTEGER PRIMARY KEY is the rowid itself, so the table is stored sorted by it. A composite key such as order_items (order_id, product_id) gets an automatic index named sqlite_autoindex_order_items_1.

Careful: Foreign key columns are NOT indexed automatically in SQLite or PostgreSQL. orders.customer_id has no index until you create one, even though every join from customers to orders uses it. MySQL's InnoDB is the exception: it creates an index on a foreign key column if none exists.

Creating, listing and dropping indexes

sql
CREATE INDEX idx_orders_customer ON orders(customer_id);
SELECT name, tbl_name FROM sqlite_master WHERE type = 'index';
-- sqlite_autoindex_order_items_1 | order_items
-- idx_orders_customer            | orders
DROP INDEX idx_orders_customer;
A common naming habit is idx_<table>_<columns>. sqlite_master (also called sqlite_schema) lists every table and index.

The query itself does not change. You still write WHERE customer_id = 1; the query planner, the part of the database that decides how to run a query, notices the index and uses it. Other engines list indexes differently: \di in PostgreSQL's psql or the pg_indexes view, and SHOW INDEX FROM orders in MySQL.

The cost of an index

  • Writes: every INSERT, every DELETE, and every UPDATE of an indexed column must also update each index. A table with ten indexes does eleven writes per new row.
  • Storage: an index is a copy of its columns plus pointers. Indexes on a big table can be gigabytes.
  • Memory: indexes compete with data for the cache. Unused indexes waste it.

Selectivity: which columns deserve an index?

Selectivity is how few rows a value picks out. An email or an order id picks one row out of crores: very selective, a perfect index. A status column with four values, where Delivered covers 90% of rows, picks almost everything; reading the index and then nearly every row is slower than a plain scan, so the planner ignores such an index. Good candidates are columns in WHERE, JOIN ... ON and ORDER BY of frequent queries, with many distinct values.

Common mistakes

  • Indexing every column "just in case". Fix: index for real, frequent queries; drop indexes nothing uses.
  • Forgetting the foreign key columns. Fix: index orders.customer_id, order_items.product_id and similar join columns.
  • Indexing a low-selectivity column such as a yes/no flag. Fix: skip it, or use a partial index on the rare value (next topic).
  • Creating the same index twice under different names. Fix: list existing indexes before adding one.
  • Judging speed on tiny test tables. Fix: test with realistic row counts; on 8 rows a scan is always fine.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

How indexes work · 1/7

How indexes work

Without help, finding rows means reading the whole table. That is a full table scan, and on big tables it is slow.

Prefer a video by a teacher? Find free videos on “how database indexes work b-tree explained full table scan” on YouTube ↗

In real life: Checking a PNR in an instant

A railway booking system holds crores of bookings. When you type a 10-digit PNR, the app runs a query like SELECT ... WHERE pnr = ?. Because pnr is the key (or has a unique index), the database walks a B-tree a few levels deep and returns your booking in milliseconds, whether the table holds one lakh rows or fifty crore. Without that index, every PNR check would read the whole table.

sql
CREATE UNIQUE INDEX idx_bookings_pnr ON bookings(pnr);
SELECT train_no, journey_date, status FROM bookings WHERE pnr = '4512367890';
One sorted lookup instead of a full scan.

Practice (0/4 done)

Task 1 · Code · SQL

Create an index named idx_orders_customer on orders(customer_id). Then list every index in the database: return name and tbl_name from sqlite_master where type = 'index', sorted by name.

Task 2 · Code · SQL

Shoppers filter by category all day. Create an index named idx_products_category on products(category), then write the query it helps: return name and price of products in the 'Kitchen' category.

Task 3 · Code · SQL

Create idx_orders_status on orders(status) and idx_orders_date on orders(order_date). status has only four values, so the status index is not selective: drop it. Finish by returning the name of every index on the orders table (from sqlite_master, type = 'index' and tbl_name = 'orders').

Task 4 · Question

An app's users table has 2 crore rows. Which single-column index will help the most?

Quick quiz

Question 1

1.What is a full table scan?

Question 2

2.Why can a B-tree find a value among crores of rows in a few steps?

Question 3

3.Which column gets an index automatically in SQLite and PostgreSQL?

Question 4

4.A table gets 5,000 inserts a second and has 12 indexes. What is the likely problem?

Question 5

5.After CREATE INDEX idx_orders_customer ON orders(customer_id), how must you change WHERE customer_id = 1?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is a full table scan?

Answer: Reading every row of a table to find the matches

Without a usable index, the database must look at every row.

Why can a B-tree find a value among crores of rows in a few steps?

Answer: It is sorted, balanced and wide, so each level narrows the search a lot

Each page splits the range into hundreds of parts, so a handful of levels is enough.

Which column gets an index automatically in SQLite and PostgreSQL?

Answer: A column with a UNIQUE constraint

UNIQUE and PRIMARY KEY are enforced with an index. Foreign keys are not indexed automatically.

A table gets 5,000 inserts a second and has 12 indexes. What is the likely problem?

Answer: Every insert must also update 12 indexes, slowing writes

Each index is extra work on every write. Drop the ones no query uses.

After CREATE INDEX idx_orders_customer ON orders(customer_id), how must you change WHERE customer_id = 1?

Answer: Nothing; the planner uses the index on its own

SQL is declarative; the planner decides to use the index.

An index exists on (customer_id, order_date). Which WHERE clause can NOT use it well?Related

Answer: order_date > '2026-01-01'

A composite index is sorted by its first column first. Without customer_id, the database cannot jump into it.

From Indexes: making queries fast

Which SQLite plan line means the index alone answered the query?Related

Answer: SEARCH orders USING COVERING INDEX idx_x (customer_id=?)

COVERING means every needed column was in the index.

From Reading query plans

A unique index on email exists. How many rows with a NULL email can SQLite or PostgreSQL store?Related

Answer: As many as you like

NULLs are not equal to each other, so they do not clash. SQL Server differs and allows one.

From Composite, covering, unique and partial indexes

In SQLite, you write INSERT INTO tickets (customer_id, subject, status) VALUES (3, 'Hi', DEFAULT). What happens?Related

Answer: A syntax error: SQLite does not accept DEFAULT in VALUES

Leave the column out instead. PostgreSQL, MySQL and SQL Server do accept DEFAULT there.

From INSERT

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Indexes, query plans and views” before its exam: Composite, covering, unique and partial indexes, Reading query plans, Writing fast queries, Views and materialized views.
  2. Module 10 exam: Indexes, query plans and views · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Procedures, triggers, SQL engines and NoSQL. 62 topics left in the course before the final exam.

← Previous: Design project: an online store