snippetsqlModerate
SQL Server 2017: How do Batch Mode Adaptive Joins work?
Viewed 0 times
adaptivesqlmodebatchworkhowserver2017joins
Problem
Batch Mode Adaptive Joins are part of a family of features in the 2017 query processor that consists of:
So how do Adaptive Joins work?
- Batch Mode Adaptive Joins
- Interleaved Execution
- Batch Mode Memory Grant Feedback
So how do Adaptive Joins work?
Solution
Batch Mode Adaptive Joins
For Batch Mode Adaptive Joins, the goal is to not pin join choice to a specific type at compile time.
When available, Adaptive Joins allow the optimizer to choose between Nested Loops Joins and Hash Joins based on row thresholds at run time.
At this time, Merge Joins are not considered. Pure speculation is that needing data to be sorted, or needing to inject a sort into the plan would add too much overhead when changing the course of a query.
When Do Batch Mode Adaptive Joins Occur?
At this time, Batch Mode query processing requires the presence of a ColumnStore index. They also require, well, a join, and an index that allows for the choice of a Nested Loops or Hash Join.
How do I know if my Join is Adaptive?
Query plans for adaptive joins are quite distinctive.
The Adaptive Join operator is new to SQL Server 2017, and currently has the following properties in actual execution plans.
-
Physical Operation: Adaptive Join
-
Actual Join Type: Will be Hash match or Nested Loops
-
Adaptive Threshold Rows: Signifies the tipping point when the join type will switch to Hash Match
-
Is Adaptive: True for Adaptive Joins
-
Estimated Join Type: Rather self-explanatory!
In an estimated or cached plan, there is rather less information:
Most notably, the Actual Join Type is missing.
What breaks Batch Mode Adaptive Joins?
To monitor this, there's an Extended Event session called
Aside from those, Batch Mode Adaptive Joins may be skipped for other reasons. Take these two queries for example:
They're identical except that the second query selects the
The Batch Mode Adaptive Join is skipped for the second query, but no reason is logged to the XE session. It appears that string data remains the steadfast enemy of ColumnStore indexes.
There are other query patterns that fail to get Adaptive Joins, that also do not trigger events.
Some examples:
-
-
eajsrExchangeTypeNotSupported
One thing that will trigger this event appears to be the presence of a Repartition Streams operator. In this query, the partitioning type is Hash Match. Special thanks to the Intergalactic Celestial Being masquerading as a humble blogging man known as Paul White for the bizarre query.
eajsrHJorNLJNotFound
No queries have triggered this XE yet.
What doesn't work:
-
Merge Join hint
-
Query patterns that rule out join type, for example Hash and Merge joins require at least one equality predicate. Writing a join on
SELECT uc.Id, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
JOIN (SELECT TOP 1 p2.OwnerUserId, p2.Score F
For Batch Mode Adaptive Joins, the goal is to not pin join choice to a specific type at compile time.
When available, Adaptive Joins allow the optimizer to choose between Nested Loops Joins and Hash Joins based on row thresholds at run time.
At this time, Merge Joins are not considered. Pure speculation is that needing data to be sorted, or needing to inject a sort into the plan would add too much overhead when changing the course of a query.
When Do Batch Mode Adaptive Joins Occur?
At this time, Batch Mode query processing requires the presence of a ColumnStore index. They also require, well, a join, and an index that allows for the choice of a Nested Loops or Hash Join.
How do I know if my Join is Adaptive?
Query plans for adaptive joins are quite distinctive.
The Adaptive Join operator is new to SQL Server 2017, and currently has the following properties in actual execution plans.
-
Physical Operation: Adaptive Join
-
Actual Join Type: Will be Hash match or Nested Loops
-
Adaptive Threshold Rows: Signifies the tipping point when the join type will switch to Hash Match
-
Is Adaptive: True for Adaptive Joins
-
Estimated Join Type: Rather self-explanatory!
In an estimated or cached plan, there is rather less information:
Most notably, the Actual Join Type is missing.
What breaks Batch Mode Adaptive Joins?
To monitor this, there's an Extended Event session called
adaptive_join_skipped, and it has the following reasons for skipping a Batch Mode Adaptive Join:- eajsrExchangeTypeNotSupported
- eajsrHJorNLJNotFound
- eajsrInvalidAdaptiveThreshold
- eajsrMultiConsumerSpool
- eajsrOuterCardMaxOne
- eajsrOuterSideParallelMarked
- eajsrUnMatchedOuter
Aside from those, Batch Mode Adaptive Joins may be skipped for other reasons. Take these two queries for example:
/*Selecting just integer data*/
SELECT uc.Id, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
JOIN dbo.Posts AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate >= '20160101';
/*Selecting one string column from Users*/
SELECT uc.Id, uc.DisplayName, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
JOIN dbo.Posts AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate >= '20160101';They're identical except that the second query selects the
DisplayName column, which has a type of NVARCHAR(40).The Batch Mode Adaptive Join is skipped for the second query, but no reason is logged to the XE session. It appears that string data remains the steadfast enemy of ColumnStore indexes.
There are other query patterns that fail to get Adaptive Joins, that also do not trigger events.
Some examples:
-
CROSS APPLY with a TOP-
OUTER APPLYeajsrExchangeTypeNotSupported
One thing that will trigger this event appears to be the presence of a Repartition Streams operator. In this query, the partitioning type is Hash Match. Special thanks to the Intergalactic Celestial Being masquerading as a humble blogging man known as Paul White for the bizarre query.
SELECT uc.Id, uc.Reputation, CONVERT(XML, CONVERT(NVARCHAR(10), p.Score)).value('(xml/text())[1]', 'INT') AS [Surprise!]
FROM dbo.Users_cx AS uc
JOIN dbo.Posts AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate <= '2009-08-01'
OPTION ( USE HINT ( 'ENABLE_PARALLEL_PLAN_PREFERENCE' ));
GOeajsrHJorNLJNotFound
No queries have triggered this XE yet.
What doesn't work:
-
Merge Join hint
-
Query patterns that rule out join type, for example Hash and Merge joins require at least one equality predicate. Writing a join on
>= and
eajsrInvalidAdaptiveThreshold
This event can be triggered by various TOP, FAST N, and OFFSET/FETCH queries. Here are some examples:
SELECT uc.Id, uc.DisplayName, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
INNER JOIN dbo.Posts AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate <= '2008-01-01'
OPTION ( FAST 1);
GO
SELECT TOP 1 uc.Id, uc.DisplayName, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
INNER JOIN dbo.Posts AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate <= '2008-01-01';
GO
In some circumstances, they can also be triggered by paging-style queries:
WITH pages
AS ( SELECT TOP 100 uc.Id, ROW_NUMBER() OVER ( ORDER BY uc.Id ) AS n
FROM dbo.Users_cx AS uc ),
rows
AS ( SELECT TOP 50 p.Id
FROM pages AS p
WHERE p.n > 50 )
SELECT u.Id, u.Reputation
FROM pages AS p
JOIN dbo.Users AS u
ON p.Id = u.Id;
eajsrMultiConsumerSpool
No known query patterns have triggered this event yet.
What hasn't triggered it so far:
-
Recursive CTEs
-
Grouping sets/Cube/Rollup
-
PIVOT and UNPIVOT
-
Windowing functions
eajsrOuterCardMaxOne
A couple different types of queries have triggered this event. A derived join with a TOP 1, and a join combined with a WHERE clause with an equality predicate on a unique column:
``SELECT uc.Id, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
JOIN (SELECT TOP 1 p2.OwnerUserId, p2.Score F
Code Snippets
/*Selecting just integer data*/
SELECT uc.Id, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
JOIN dbo.Posts AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate >= '20160101';
/*Selecting one string column from Users*/
SELECT uc.Id, uc.DisplayName, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
JOIN dbo.Posts AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate >= '20160101';SELECT uc.Id, uc.Reputation, CONVERT(XML, CONVERT(NVARCHAR(10), p.Score)).value('(xml/text())[1]', 'INT') AS [Surprise!]
FROM dbo.Users_cx AS uc
JOIN dbo.Posts AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate <= '2009-08-01'
OPTION ( USE HINT ( 'ENABLE_PARALLEL_PLAN_PREFERENCE' ));
GOSELECT uc.Id, uc.DisplayName, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
INNER JOIN dbo.Posts AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate <= '2008-01-01'
OPTION ( FAST 1);
GO
SELECT TOP 1 uc.Id, uc.DisplayName, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
INNER JOIN dbo.Posts AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate <= '2008-01-01';
GOWITH pages
AS ( SELECT TOP 100 uc.Id, ROW_NUMBER() OVER ( ORDER BY uc.Id ) AS n
FROM dbo.Users_cx AS uc ),
rows
AS ( SELECT TOP 50 p.Id
FROM pages AS p
WHERE p.n > 50 )
SELECT u.Id, u.Reputation
FROM pages AS p
JOIN dbo.Users AS u
ON p.Id = u.Id;SELECT uc.Id, uc.Reputation, p.Score
FROM dbo.Users_cx AS uc
JOIN (SELECT TOP 1 p2.OwnerUserId, p2.Score FROM dbo.Posts AS p2 ORDER BY Id) AS p
ON p.OwnerUserId = uc.Id
WHERE uc.LastAccessDate <= '2009-08-01';
GO
SELECT p.Id, p.ParentId, p.OwnerUserId
FROM dbo.Posts AS p
JOIN dbo.Users_cx AS uc
ON uc.Id = p.OwnerUserId
WHERE p.Id = 17333;
GOContext
StackExchange Database Administrators Q#187576, answer score: 12
Revisions (0)
No revisions yet.