Advanced Indexing Concepts and Query Tuning
From the Database System curriculum
TL;DR
You'll learn how advanced indexing techniques go beyond basic primary keys to speed up complex queries. We'll explore different index types and how to choose the right one, then dive into query tuning strategies to optimize database performance. By understanding these concepts, you can significantly improve the efficiency of your database applications.
1. The Mental Model
Think of indexing as creating a highly organized, separate lookup table for specific data in your main database table. Query tuning is like adjusting the engine of a car to make it run more efficiently, using these indices and other tricks to get results faster.
2. The Core Material
You already know that indexes help databases find data faster by creating a sorted data structure that points to the actual rows. Now, let's explore some more advanced types and concepts beyond simple B-tree indexes on primary keys.
a. B-Tree Indexes (Revisited)

Photo by Tima Miroshnichenko on Pexels
Most common index type. They're good for equality searches (WHERE column = 'value') and range searches (WHERE column BETWEEN 'A' AND 'Z'). They keep data sorted, allowing for quick lookups.
b. Hash Indexes

Photo by Element5 Digital on Pexels
Hash indexes store a hash value for each indexed column and a pointer to the row. They're incredibly fast for equality lookups (WHERE column = 'value') because they can directly jump to the data location using the hash. However, they are not good for range queries or sorting, as the hash values don't maintain order.
c. Bitmap Indexes

Photo by Element5 Digital on Pexels
Bitmap indexes are useful for columns with low cardinality (few distinct values), like gender ('M', 'F') or status ('Active', 'Inactive', 'Pending'). Instead of storing a pointer for each row, they store a bitmap (a sequence of bits) where each bit corresponds to a row. If the bit is '1', the row matches the value. They're excellent for complex OR and AND queries, as bitmaps can be combined very efficiently using bitwise operations.
d. Full-Text Indexes

Photo by Nothing Ahead on Pexels
When you need to search for keywords within large blocks of text (like product descriptions or article bodies), standard indexes are inefficient. Full-text indexes allow you to perform linguistic searches, ranking results by relevance, and handling natural language queries.
e. Clustered vs. Non-Clustered Indexes
This is a crucial distinction.
- Clustered Index: This index dictates the physical order of the data rows on disk. A table can only have one clustered index because data can only be physically sorted in one way. It's often created automatically on the primary key. If you search by the clustered index, the data itself is retrieved in order.
- Non-Clustered Index: This index is a separate data structure that contains the indexed column(s) and pointers (row IDs or primary key values) back to the actual data rows. A table can have multiple non-clustered indexes. Think of it like a book's index: it tells you where to find topics, but the book pages aren't physically ordered by index entries.
graph TD
A["User Query"] --> B{"Query Optimizer"};
B --> C{Analyze Query};
C --> D{Check Available Indexes};
D --> E{Evaluate Query Plans};
E -- "Cost Estimation" --> F{Select Best Plan};
F --> G["Execute Query Plan"];
G --> H["Fetch Data (using Indexes if chosen)"];
H --> I["Return Results"];
subgraph Index Considerations
D --> D1["B-Tree (Equality, Range)"];
D --> D2["Hash (Equality)"];
D --> D3["Bitmap (Low Cardinality, AND/OR)"];
D --> D4["Full-Text (Text Search)"];
D --> D5["Clustered vs. Non-Clustered"];
end
f. Query Tuning Strategies
Optimizing queries involves making sure the database uses the best path to get your data.
EXPLAIN/EXPLAIN ANALYZE: This is your best friend. Most databases (like PostgreSQL, MySQL) have commands likeEXPLAINorEXPLAIN PLAN(Oracle) that show you exactly how the database plans to execute your query. It tells you which indexes are used (or not), how many rows are scanned, and the estimated cost.- Analyze Your Query Plan: Look for:
- Full Table Scans: If you see a full table scan on a large table for a selective query, you likely need an index.
- High Costs: Identify expensive operations.
- Temporary Tables / Sorts: These can be slow. Can an index help avoid sorting?
- Index Creation: Based on your
EXPLAINoutput, create appropriate indexes. Consider:WHEREclauses: Index columns used inWHERE.ORDER BYclauses: An index on columns inORDER BYcan avoid sorting.JOINconditions: Index columns used for joining tables.- Covering Indexes: A non-clustered index that includes all columns needed by a query, so the database doesn't have to go back to the main table to fetch data. This is very efficient!
- Query Rewriting: Sometimes, the way you write a query can prevent index usage.
- Avoid functions on indexed columns:
WHERE YEAR(order_date) = 2023prevents an index onorder_datefrom being used directly. Instead,WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31'would use it. - Use
LIKEcarefully:LIKE 'prefix%'can use an index, butLIKE '%suffix'orLIKE '%middle%'usually cannot. - Proper
JOINs: EnsureJOINconditions are correctly written and have appropriate indexes.
- Avoid functions on indexed columns:
- Database Statistics: Databases rely on statistics about the data (e.g., number of rows, distribution of values) to make good query plans. Ensure these statistics are up-to-date (e.g.,
ANALYZEcommand in PostgreSQL). - Hardware and Configuration: While not strictly "indexing," query performance is also affected by hardware (CPU, RAM, disk I/O) and database configuration settings (buffer sizes, memory allocation).
3. Worked Example
Let's say you have a large orders table with millions of rows:
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
total_amount DECIMAL(10, 2),
status VARCHAR(50) NOT NULL
);
You frequently run this query to find all pending orders for a specific customer:
SELECT order_id, order_date, total_amount
FROM orders
WHERE customer_id = 12345 AND status = 'Pending'
ORDER BY order_date DESC;
First, let's see how it performs without an appropriate index. In a PostgreSQL-like database, you'd run:
EXPLAIN ANALYZE
SELECT order_id, order_date, total_amount
FROM orders
WHERE customer_id = 12345 AND status = 'Pending'
ORDER BY order_date DESC;
The output might look something like this (simplified):
Sort (cost=12345.67..12346.89 rows=100 width=20) (actual time=123.456..123.567 ms)
Sort Key: order_date DESC
-> Seq Scan on orders (cost=0.00..12000.00 rows=100 width=20) (actual time=0.123..120.000 ms)
Filter: (customer_id = 12345 AND (status)::text = 'Pending'::text)
Rows Removed by Filter: 9999900
Notice the "Seq Scan on orders". This means a full table scan – the database is reading every single row to find the matching ones, then sorting them. This is slow for a large table.
Now, let's create a covering index that can satisfy this query directly:
CREATE INDEX idx_customer_status_date ON orders (customer_id, status, order_date DESC);
Here's why this is a good index:
1. customer_id and status are in the WHERE clause, so the index helps filter quickly.
2. order_date DESC is included, meaning the index itself is sorted by order_date in descending order. This allows the database to retrieve the results already sorted, avoiding a separate Sort operation.
3. order_id, order_date, and total_amount are in the SELECT list. If we add order_id and total_amount to the INCLUDE clause (supported by some databases like PostgreSQL), it becomes a perfect covering index, meaning the database never needs to touch the main orders table for this query.
-- More advanced covering index (PostgreSQL syntax for INCLUDE)
CREATE INDEX idx_customer_status_date_covering ON orders (customer_id, status, order_date DESC) INCLUDE (order_id, total_amount);
Now, re-run the EXPLAIN ANALYZE for the same query:
EXPLAIN ANALYZE
SELECT order_id, order_date, total_amount
FROM orders
WHERE customer_id = 12345 AND status = 'Pending'
ORDER BY order_date DESC;
The output would now be much different (and faster):
Index Scan using idx_customer_status_date_covering on orders (cost=0.42..15.44 rows=100 width=20) (actual time=0.045..0.210 ms)
Index Cond: (customer_id = 12345 AND (status)::text = 'Pending'::text)
Notice the "Index Scan". The database used our new index, completely avoiding the full table scan and the separate sort operation, leading to a much lower cost and faster actual execution time.
4. Key Takeaways
- Indexes are specialized data structures that significantly speed up data retrieval, but they also add overhead to data modifications (inserts, updates, deletes).
- Different index types (B-Tree, Hash, Bitmap, Full-Text) are optimized for specific query patterns and data characteristics.
- Clustered indexes define the physical order of data, while non-clustered indexes are separate lookup structures.
EXPLAIN ANALYZEis an indispensable tool for understanding how your database executes a query and identifying performance bottlenecks.- Covering
Frequently asked about Advanced Indexing Concepts and Query Tuning
Study this next
Get the full Database System curriculum
Clone the complete plan to your dashboard for unlimited AI-generated notes, practice quizzes, and a personalised revision schedule.
Save this course free