🎉 75% of content is free forever — Unlock Premium from $10/mo →
CW
Search courses…
💼 Servicesℹ️ About✉️ ContactView Pricing Plansfrom $10

SQL Indexes

SQL Database ObjectsPerformance🟢 Free Lesson

Advertisement

SQL Database Objects

SQL Indexes

Speed up your queries — the right index can turn a 30-second query into a millisecond lookup.

  • B-Tree Index — The default, ideal for equality and range queries
  • Composite Index — Covers multiple columns for multi-condition lookups
  • Unique Index — Enforces uniqueness while providing fast access An index is like a book's table of contents — it tells the database where to look without scanning every page.

What Is an Index?

B-Tree Index Structure[50][20][40][70][90][5..19][20..39][40..69][70..89][90+]→ Row Ptrs→ Row Ptrs→ Row Ptrs→ Row Ptrs→ Row PtrsO(log n) lookup — traverse root → internal → leaf
-- Create a basic index on a single column
CREATE INDEX idx_employees_last_name ON employees(last_name);
-- Query that benefits from the index
SELECT * FROM employees WHERE last_name = 'Smith';

How Indexes Work

Without an index, the database performs a full table scan — reading every row to find matches. With an index, it traverses the tree directly to the matching rows.

OperationWithout IndexWith Index
Find one row by IDO(n) — scan all rowsO(log n) — tree traversal
Range query (BETWEEN)O(n) — scan all rowsO(log n) — find range start, then scan leaves
COUNT with WHEREO(n) — scan all rowsO(log n) if index covers the filter
INSERTO(1) — append rowO(log n) — update the tree
UPDATE indexed columnO(1) — update rowO(log n) — rebuild tree node

Types of Indexes

Single-Column Index

CREATE INDEX idx_employees_email ON employees(email);

Composite Index (Multi-Column)

-- Order matters: leftmost prefix rule
CREATE INDEX idx_employees_dept_salary ON employees(department_id, salary);
QueryUses Index?Why
WHERE department_id = 5✅ YesMatches leftmost column
WHERE department_id = 5 AND salary > 70000✅ YesMatches both columns
WHERE salary > 70000❌ NoSkips leftmost column
WHERE department_id = 5 OR salary > 70000⚠️ PartialOR may prevent index use

Unique Index

-- Enforces uniqueness and provides fast lookups
CREATE UNIQUE INDEX idx_employees_email ON employees(email);

Partial Index (PostgreSQL)

-- Index only active employees
CREATE INDEX idx_active_employees ON employees(last_name)
WHERE status = 'Active';

Covering Index

-- Include columns needed by the query to avoid table lookups
CREATE INDEX idx_emp_covering ON employees(department_id, salary, first_name, last_name);
-- This query is fully served by the index — no table access needed
SELECT first_name, last_name
FROM employees
WHERE department_id = 5 AND salary > 70000;

Expression Index

-- Index on a function or expression
CREATE INDEX idx_emp_lower_email ON employees(LOWER(email));
-- Uses the expression index
SELECT * FROM employees WHERE LOWER(email) = 'john@example.com';

Creating and Managing Indexes

-- Create index
CREATE INDEX idx_orders_customer ON orders(customer_id);

-- Create index only if it doesn't exist (PostgreSQL)
CREATE INDEX IF NOT EXISTS idx_orders_customer ON orders(customer_id);

-- Drop an index
DROP INDEX idx_orders_customer;

-- Rename an index (PostgreSQL)
ALTER INDEX idx_orders_customer RENAME TO idx_cust_orders;
-- List all indexes on a table (PostgreSQL)
SELECT
    indexname,
    indexdef
FROM pg_indexes
WHERE tablename = 'employees';
-- List indexes (MySQL)
SHOW INDEX FROM employees;

When to Create an Index

ScenarioCreate Index?Why
Column in WHERE clause (equality)✅ YesFast lookup for exact matches
Column in WHERE clause (range)✅ YesB-Tree supports range scans efficiently
Column in JOIN (foreign key)✅ YesSpeeds up join operations
Column in ORDER BY✅ YesAvoids costly sort operations
Low-cardinality column (e.g., gender)⚠️ MaybeIndex may not help if it returns >20% of rows
Small table (<1000 rows)❌ Usually noFull scan is fast enough
Column rarely used in queries❌ NoWasted space and write overhead

Index Impact on Performance

OperationImpact of Index
SELECT with WHERE on indexed column✅ Dramatically faster
SELECT with JOIN on indexed column✅ Significantly faster
SELECT with ORDER BY on indexed column✅ Avoids sorting step
INSERT on indexed table⚠️ Slightly slower (index update)
UPDATE on indexed column⚠️ Slower (index rebuild)
DELETE on indexed table⚠️ Slightly slower (index cleanup)
Disk space⚠️ Extra storage per index

Index Monitoring

-- PostgreSQL: Check index usage statistics
SELECT
    schemaname,
    tablename,
    indexname,
    idx_scan AS times_used,
    idx_tup_read AS rows_read,
    idx_tup_fetch AS rows_fetched,
    pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE tablename = 'employees'
ORDER BY idx_scan DESC;
-- PostgreSQL: Find unused indexes
SELECT
    indexname,
    idx_scan,
    pg_size_pretty(pg_relation_size(indexrelid)) AS size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;
-- Check if an index is being used (PostgreSQL)
EXPLAIN ANALYZE
SELECT * FROM employees WHERE last_name = 'Smith';
-- Look for "Index Scan" vs "Seq Scan" in the output

Common Index Patterns

PatternSyntaxUse Case
Single columnCREATE INDEX idx ON t(col)Equality lookups
CompositeCREATE INDEX idx ON t(col1, col2)Multi-column WHERE
UniqueCREATE UNIQUE INDEX idx ON t(col)Enforce uniqueness
PartialCREATE INDEX idx ON t(col) WHERE conditionFiltered lookups
CoveringCREATE INDEX idx ON t(c1, c2) INCLUDE (c3, c4)Avoid table lookups
ExpressionCREATE INDEX idx ON t(LOWER(col))Function-based queries
DescendingCREATE INDEX idx ON t(col DESC)Reverse-order sorts

Database-Specific Notes

FeaturePostgreSQLMySQL (InnoDB)SQL ServerOracle
B-Tree index✅ Default✅ Default✅ Default✅ Default
Unique index
Partial index✅ (filtered)
Expression index✅ (8.0+)
Covering index (INCLUDE)✅ (covering key)
GiST / GIN (full-text)Full-text index
Hash index✅ (Memory only)
Reverse key index

Need Expert SQL Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement