Hello! Welcome back to our journey through query execution algorithms.
In the last lesson, we tackled the Block Nested Loop Join (BNLJ). Its major weakness was the need to repeatedly scan the entire inner table, leading to an I/O cost of . Even with optimizations, this can be very expensive if the outer table is large.
Today, we'll explore a much more efficient strategy for specific scenarios: the Index Nested Loop Join (INLJ). As the name implies, the goal is to leverage an existing index to avoid those costly full scans of the inner table. This lesson directly addresses the learning outcome: to implement and analyze the I/O cost of the index nested loop join algorithm. This is a common and powerful algorithm, especially for the selective joins you often see in transactional (OLTP) workloads.
1. The Core Idea: Replacing Scans with Probes
The fundamental idea behind the Index Nested Loop Join is simple yet powerful. Instead of scanning the inner table S to find matches for tuples from the outer table R, we can use an index on S's join attribute to look them up directly.
The algorithm proceeds as follows:
- Scan the outer table
R, one tuple at a time. - For each tuple
trfromR, take its value for the join attribute (e.g.,tr.id). - Use this value to probe the index on the inner table
Sto find matching tuples. - For each match found, combine the tuples and add them to the result.
This process is visualized in the diagram below. For each tuple from R, we make a targeted query into the index, which then points us directly to the required tuples in S.

To see a full explanation, let's turn again to Professor Andy Pavlo from CMU.
CMU Database Systems - 12 Join Algorithms (Fall 2018)
This segment from the CMU lecture explains the Index Nested Loop Join. It shows how using an index on the inner table changes the algorithm from scanning to probing.
Watch from 22:55 to 25:50. Focus on the pseudocode and the high-level description of the cost formula M + (m * C).
2. Implementation and a Practical View
Given your programming background, you can think of this as replacing a nested for loop that iterates through an array with one that uses a hash map or a balanced tree for the inner loop lookups.
Here is what the algorithm looks like in pseudocode:
// R is the outer table, S is the inner table
// Assume S_index is an index on S's join attribute
for (tuple tr in R) {
// Use the join key from the outer tuple to probe the index
join_key = tr.join_attribute;
list_of_matches = S_index.lookup(join_key);
// For each matching tuple found in S, emit the joined result
for (tuple ts in list_of_matches) {
add <tr, ts> to the result;
}
}
This is a clean and simple approach. The following technical blog post provides a similar pseudocode and, more importantly, shows what an EXPLAIN plan for this join looks like in a real database system like PostgreSQL.
Demystifying JOIN Algorithms - Runtime Checks
This article from felipe.rs provides a practical view on join algorithms. This section shows a PostgreSQL query plan using a Nested Loop with an index scan and presents clear pseudo-code.
Read the section 'Indexing'. Pay close attention to the query plan output and the corresponding pseudo-code. This connects the abstract algorithm to a concrete system's output.
3. Analyzing the I/O Cost
Now for the crucial part: analyzing the I/O cost. As the video mentioned, the cost formula is:
Let's break this down:
- : The I/O cost to perform a full sequential scan of the outer table
R. We have to read every one of its pages once. - : The total number of tuples in the outer table
R. This is important: we perform one index probe for each tuple in the outer table. - : The average I/O cost for a single index probe. This includes traversing the index and fetching the actual data tuple(s) from the inner table
S.
The total cost is the scan of the outer table plus the cumulative cost of all the probes into the inner table. The value of C is where the real analysis lies. It depends heavily on the type of index and how the data is stored.
The Cost of a Probe (C)
The cost C isn't just one number. It varies based on two key factors: the index structure (e.g., B+ Tree) and whether the index is clustered or unclustered.

-
With a Clustered Index on S:
- In a clustered index, the actual data rows are stored in the leaf pages of the index itself.
- Therefore, the cost
Cis simply the cost to traverse the B+ Tree from the root to the correct leaf page. - This typically requires 2-4 I/Os for a reasonably sized tree. Once at the leaf page, we have the data.
-
With an Unclustered Index on S:
- In an unclustered index, the leaf pages contain the index keys and pointers (e.g., Record IDs) to the actual data pages.
- The cost
Cis the cost to traverse the B+ Tree (2-4 I/Os) plus one additional I/O to fetch the data page containing the tuple from the table's heap file. - In a worst-case scenario where each lookup points to a different data page that isn't in the buffer pool, this can be costly. If
ktuples inSmatch a given key, this could require up tokadditional I/Os.
For a concise summary of the algorithm and its cost, please refer to the CMU lecture notes.
15-445/645 Database Systems - 12 Joins Algorithms
These are the official notes for the CMU course. They offer a clear and compact definition of the Index Nested Loop Join and its cost formula.
Read the short section 'Index Nested Loop Join' on page 2. It's a great textual reinforcement of what we've just discussed.
Test your understanding!
You need to join two tables, Orders (outer) and Customers (inner). An unclustered B+ tree index exists on Customers.c_id.
- Orders (R):
- Size: 2,000 pages ()
- Tuples: 200,000 ()
- Customers (S):
- Size: 10,000 pages ()
- Buffer Pool: 502 pages ()
- Index Probe Cost (): Assume an average of 3 I/Os to traverse the index and 1 I/O to fetch the customer data record. So, I/Os.
- Calculate the I/O cost for a Block Nested Loop Join (with
Ordersas outer). - Calculate the I/O cost for an Index Nested Loop Join.
- Which one is better in this case?
Show answer
-
Block Nested Loop Join Cost:
The formula is .- Scan of outer table
Orders: I/Os. - Number of chunks for
Orders: chunks. - We must scan the inner table
Customers4 times. Each scan costs I/Os. - Total Cost = .
- Scan of outer table
-
Index Nested Loop Join Cost:
The formula is .- Scan of outer table
Orders: I/Os. - Number of probes: This equals the number of tuples in
Orders, so . - Cost per probe: I/Os.
- Total Cost = .
- Scan of outer table
-
Conclusion:
In this specific scenario, the Block Nested Loop Join is significantly better (42,000 I/Os vs. 802,000 I/Os). This happens because the outer table is relatively large, leading to a huge number of index probes. Even though each probe is cheap, the sheer volume makes it more expensive than scanning the inner table a few times. This highlights that INLJ is not always the best choice; its effectiveness depends on the size of the outer table.
Conclusion
Today we explored the Index Nested Loop Join, a powerful alternative to the brute-force scanning of BNLJ. By leveraging an index, it can transform a join from a massive scanning operation into a series of quick, targeted lookups.
Key Takeaways:
- The Index Nested Loop Join algorithm avoids scanning the inner table by probing an index for each tuple of the outer table.
- The I/O cost is , where the efficiency is critically dependent on
m(the number of outer tuples) andC(the cost per probe). - The cost of a probe (
C) is lower for a clustered index than for an unclustered index because the latter requires an extra I/O to fetch the data record. - INLJ is most effective for joins where the outer table is small, as this minimizes the number of probes. This is common in OLTP queries like "find the customer for this one order."
Preview of the Next Lesson
INLJ is great if a suitable index exists. BNLJ is a fallback, but can be slow. What if we don't have an index, and BNLJ is too costly because both tables are large? In our next lesson, we will investigate the Sort-Merge Join, an elegant algorithm that takes a completely different approach: it sorts both tables on the join key and then merges them.