MORE INFORMATION
Microsoft SQL Server version 6.5 and earlier had only one technique for
joining records: the nested loops join. SQL Server 7.0 implements hash and
sort-based algorithms. The decision by the query engine on whether to use
hash versus sort depends on relative sizes of the inputs, whether they are
sorted or not, whether the hash table will fit entirely in memory, and so
on. The optimizer will always choose the best join plan to execute your
query, and that may be any one of the three options. The difference between
hash joins and merge joins is often small, but there are certain situations
where one is better than the other. There are considerable differences
between these two join types and the nested loops join, as shown below. The
example in this article is provided only as an illustration. The table at
the end of this article is an illustration of some decisions that may
affect the join type used. It is generally recommended that you not force
join types, overriding the optimizer.
Nested Loop Joins
In a nested loops join, one of the tables is scanned; for every row in that
table, a lookup is performed on the second table and the matching rows are
retrieved. We will call these tables outer and inner input. The basic
algorithm is to scan the inner input once for each value of outer input,
looking for a match. Depending on the presence of indexes on the inner
input the lookup can run quite efficiently. Indexed nested loops join is
obviously more efficient than non-indexed nested loops. Also, if the tables
have a 1-1 relationship, the scan of the inner table can stop as soon as
the row is found that matches the row in the outer table.
Any query can be processed using this methodology including joins on
inequality conditions.
Nested loops example:
SELECT * FROM reserves AS r1 JOIN sailors AS s1 ON r1.sid=s1.sid option
(loop join)
Reserves has 529 pages and 100000 rows
Sailors has 195 pages 40000 rows
Table 'sailors'. Scan count 100000, logical reads 207596, physical reads 3,
read-ahead reads 0.
Table 'reserves'. Scan count 1, logical reads 529, physical reads 0, read-
ahead reads 0.
|--Nested Loops(Inner Join)
|--Sort(ORDER BY:([r1].[sid] ASC))
| |--Table Scan(OBJECT:([Northwind].[dbo].[reserves] AS [r1]))
|--Clustered Index Seek(OBJECT:([Northwind].[dbo].[sailors].[I2] AS
[s1]), SEEK:([s1].[sid]=[r1].[sid]) ORDERED)
Hash Joins
In a hash match, a hash table is created in memory for one of the inputs
(build input) using a repeatable randomizing function and this table is
searched for matching values from the other input (probe input). The hash
table performs like an index. The data from the build input is stored in
memory. If the build input does not fit in memory it is partitioned
recursively until it fits in memory. The probe input is then hashed and
used to search for matches. Unlike with nested loops, the presence or
absence of indexes is not particularly a concern in this case. Hash joins
are CPU-intensive in comparison to nested loops and are affected by
available memory. Hash joins are better when there is a significant
difference in the sizes of the tables being joined.
In a hash join the build input determines the recursion depth because it
can stop partitioning when the input fits in memory. A single hash join can
perform both grouping and join at the same time when the grouping attribute
is also the join attribute. The result of this join is not in any
particular order. Inequality conditions cannot be satisfied by this type of
join.
|--Hash Match(Inner Join, HASH:([s1].[sid])=([r1].[sid]),
RESIDUAL:([s1].[sid]=[r1].[sid]))
|--Clustered Index Scan(OBJECT:([Northwind].[dbo].[sailors].[I2] AS [s1]))
|--Table Scan(OBJECT:([Northwind].[dbo].[reserves] AS [r1]))
Table 'reserves'. Scan count 1, logical reads 529, physical reads 0, read-
ahead reads 0.
Table 'sailors'. Scan count 1, logical reads 208, physical reads 0, read-
ahead reads 0.
Sort-Merge Join
In a sort-merge, sorting the larger input dominates a large portion of the
total cost. In a merge join, the inputs are sorted on the join column and
the sorted pair is merged into one, eliminating the column values that do
not match from the sorted result set.
The algorithm first examines the tuple of one input, scans the second
sorted input until a row with equal or larger join value is found. Match up
tuple in R1 with all tuples in R2 with matching join value. Repeat for all
tuples in R1.
Merge join requires that both inputs be in memory (as opposed to a hash
join, where only the smaller input is in memory). The merge join is
obviously more efficient if the inputs are already sorted. Also the result
of the join is sorted on the join attribute. Inequality conditions cannot
be satisfied by this type of join.
|--Merge Join(Inner Join, MANY-TO-MANY MERGE:([r1].[sid])=([s1].[sid]),
RESIDUAL:([s1].[sid]=[r1].[sid]))
|--Sort(ORDER BY:([r1].[sid] ASC))
| |--Table Scan(OBJECT:([Northwind].[dbo].[reserves] AS [r1]))
|--Clustered Index Scan(OBJECT:([Northwind].[dbo].[sailors].[I2] AS s1]), ORDERED)
Table 'sailors'. Scan count 1, logical reads 208, physical reads 0, read-
ahead reads 0.
Table 'reserves'. Scan count 1, logical reads 529, physical reads 0, read-
ahead reads 0.
By changing the size of the input such that the relative sizes of the
inputs vary, (in our example reserves had only 500 rows and sailors 50,000
rows) merge join for the same query as above took much longer than hash as
can be seen below:
Merge join:
SQL Server Execution Times:
CPU time = 1081 ms, elapsed time = 2211 ms.
Hash join:
SQL Server Execution Times:
CPU time = 181 ms, elapsed time = 479 ms.
If an index exists on the join key, the data does not need to be sorted and
therefore a merge join will be the best choice.
==========================================================================
Property | Nested loop | Hash or merge
=========================|=============================================
Only limited memory | Better |Worse,
|memory is needed for sorting
|or building hash table
Need first row fast | Better |Worse,
|need to sort or hash
|before a row is returned
Selecting only a few | Better |Worse
rows from inner table | |
Parallelism |Works best |Will work
Index available on inner |Works best |Will work
Without one equality |Works |Needs at least 1 equality
2 Very large inputs |Worse |Not much better,
|hash and sort will have
|almost equal cost
One very small |Worse |Hash better than merge
and large input | |