{"id":81107,"date":"2018-10-01T15:26:21","date_gmt":"2018-10-01T15:26:21","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=81107"},"modified":"2022-04-24T21:25:00","modified_gmt":"2022-04-24T21:25:00","slug":"adaptive-query-processing-in-sql-server-2017","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/adaptive-query-processing-in-sql-server-2017\/","title":{"rendered":"Adaptive Query Processing in SQL Server 2017"},"content":{"rendered":"<p>SQL Server 2017 now offers adaptive query processing, a new set of features aimed at improving query performance. Adaptive query processing addresses issues related to cardinality estimates in execution plans, leading to better memory allocation, join type selection, and row calculations for multi-statement table valued functions (MSTVFs).<\/p>\n<p>Prior to SQL Server 2017, if a query plan contained incorrect cardinality estimates, the database engine continued to use that plan for each statement execution, as long as the plan remained cached, often resulting in less-than-optimal query performance. For example, the execution plan might allocate too much memory for some queries, while underestimating the memory requirements for others.<\/p>\n<p>The adaptive query processing features attempt to resolve these types of issues by providing more accurate cardinality estimates when calculating query execution plans. SQL Server 2017 enables these features by default on databases configured with a compatibility level of 140 or greater.<\/p>\n<p>If a database has a lower compatibility level, you can use an <code>ALTER<\/code> <code>DATABASE<\/code> statement to change the level. For example, the following statement changes the compatibility level of the <code>WideWorldImporters<\/code> sample database to <code>140<\/code>:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">ALTER DATABASE WideWorldImporters \r\nSET COMPATIBILITY_LEVEL = 140;<\/pre>\n<p>The <code>WideWorldImporters<\/code> database is used for all the examples in this article. If you have this database installed on your system, you should be able to try out the examples without making any changes. If you want to use a different database, you can create <code>SELECT<\/code> statements comparable to the ones shown in the examples. The same principles should apply to any database with a compatibility level of 140 running on SQL Server 2017.<\/p>\n<p>You can verify a database\u2019s compatibility level by running the following <code>SELECT<\/code> statement, passing in the name of the database in the <code>WHERE<\/code> clause:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT compatibility_level FROM sys.databases \r\nWHERE name = 'WideWorldImporters';<\/pre>\n<p>If you run this <code>SELECT<\/code> statement after executing the preceding <code>ALTER<\/code> <code>DATABASE<\/code> statement, the <code>SELECT<\/code> statement should return a value of <code>140<\/code>.<\/p>\n<p>Setting the compatibility level on a database is the only step you need to take in SQL Server 2017 to enable the adaptive query processing features for that database. Currently, SQL Server 2017 supports three adaptive query processing types:<\/p>\n<ul>\n<li>Batch mode memory grant feedback<\/li>\n<li>Batch mode adaptive join<\/li>\n<li>Interleaved execution<\/li>\n<\/ul>\n<p>As already noted, these features are enabled by default. However, you can disable or enable each one individually, without changing the database\u2019s compatibility level. The following sections cover the three features in more detail, including the steps necessary to disable or enable them.<\/p>\n<h2>Memory Grant Feedback<\/h2>\n<p>SQL Server uses memory to store row data during join and sort operations. When compiling an execution plan, the query engine estimates how much memory is needed to store those rows. If the memory estimate is too small, excess data will spill over to the disk, impacting performance. If the estimate is too large, memory is wasted, impacting the performance of concurrent operations.<\/p>\n<p>The memory grant feedback feature helps remedy this situation by recalculating the row memory requirements when the statement is first executed. If the initial estimate is off, the cached plan is updated. Subsequent executions can then benefit from the new estimate, as long as the query plan remains in cache.<\/p>\n<p>The best way to understand how memory grant feedback works is to see it in action, starting with how SQL Server has traditionally behaved when estimating memory requirements. To demonstrate this behavior, first disable the memory grant feedback feature by running the following <code>ALTER<\/code> <code>DATABASE<\/code> statement:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">ALTER DATABASE SCOPED CONFIGURATION \r\nSET DISABLE_BATCH_MODE_MEMORY_GRANT_FEEDBACK = ON;<\/pre>\n<p>The statement sets the <code>DISABLE_BATCH_MODE_MEMORY_GRANT_FEEDBACK<\/code> configuration option to <code>ON<\/code>, which disables the memory grant feedback features without impacting the database\u2019s compatibility level. To verify that the setting has been updated, run the following <code>SELECT<\/code> statement:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT * FROM sys.database_scoped_configurations;<\/pre>\n<p>The <code>SELECT<\/code> statement returns data about the database\u2019s scoped configuration settings, as shown in Figure 1.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"710\" height=\"189\" class=\"wp-image-81108\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/10\/word-image.png\" \/><\/p>\n<p class=\"caption\">Figure 1. Disabling memory grant feedback<\/p>\n<p>Row 6 of the results includes the <code>DISABLE_BATCH_MODE_MEMORY_GRANT_FEEDBACK<\/code> option. Notice that the option\u2019s current value is <code>1<\/code> (<code>ON<\/code>) and that the default value is <code>0<\/code> (<code>OFF<\/code>), indicating that memory grant feedback is enabled by default (but only for databases with a compatibility level of 140 or greater).<\/p>\n<p>Next, run the following <code>SELECT<\/code> statement with the <em>Actual Execution Plan<\/em> enabled:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT il.StockItemID, il.Quantity, il.ExtendedPrice, iv.InvoiceDate\r\nFROM Sales.InvoiceLines il INNER JOIN Sales.Invoices iv\r\n  ON il.InvoiceID = iv.InvoiceID\r\nWHERE iv.InvoiceDate BETWEEN '2013-01-01' AND '2015-12-31'\r\nORDER BY il.StockItemID, il.Quantity DESC;<\/pre>\n<p>After the statement runs, go to the execution plan and hover over the <em>Select<\/em> operator to display the operator\u2019s details, which are shown in Figure 2.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"261\" height=\"344\" class=\"wp-image-81109\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/10\/word-image-1.png\" \/><\/p>\n<p class=\"caption\">Figure 2. <em>Memory Grant<\/em> attribute of the <em>Select<\/em> operator<\/p>\n<p>The <em>Memory Grant<\/em> attribute indicates that 78,464 KB of memory is required for the query\u2019s row data. No matter how many times you rerun the <code>SELECT<\/code> statement, you should receive the same <em>Memory Grant<\/em> total, as long as the query plan remains cached. Even if you\u2019re receiving a different total than the one shown here, the behaviour should be the same.<\/p>\n<p>With this in mind, you can now test the memory grant feedback feature by re-enabling the feature and then re-executing the <code>SELECT<\/code> statement. To re-enable the feature, run the following <code>ALTER<\/code> <code>DATABASE<\/code> statement, which sets the <code>DISABLE_BATCH_MODE_MEMORY_GRANT_FEEDBACK<\/code> option to <code>OFF<\/code>:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">ALTER DATABASE SCOPED CONFIGURATION \r\nSET DISABLE_BATCH_MODE_MEMORY_GRANT_FEEDBACK = OFF;<\/pre>\n<p>When you turn the <code>DISABLE_BATCH_MODE_MEMORY_GRANT_FEEDBACK<\/code> option to <code>OFF<\/code>, the option is no longer listed in the <code>sys.database_scoped_configurations<\/code> table. Only when you set the option to <code>ON<\/code> is it included in the table. This is true for all the scoped configuration options specific to enabling or disabling adaptive query processing features.<\/p>\n<p>After you re-enable the memory grant feedback feature, you should rerun the example <code>SELECT<\/code> statement. Before you do that, however, clear the execution plan from cache. (If appropriate, you should clear the cache between each example to ensure you see the correct behaviour when you test these statements. But don\u2019t do this on a production server. In fact, you should never be testing new features on a production server.) One approach to clearing the cache is to run the following T-SQL statement:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">DBCC FREEPROCCACHE;<\/pre>\n<p>SQL Server provides several methods for clearing the cache, so pick whichever one works for you. The <code>DBCC<\/code> <code>FREEPROCCACHE<\/code> statement is a fairly straightforward approach, as long as it\u2019s okay for all query plans to be cleared from the cache. If it\u2019s not, you\u2019ll have to specify the specific plan you want to remove.<\/p>\n<p>After you re-enable the memory grant feedback feature and clear the cache, run the following <code>SELECT<\/code> statement two or more times (which is the same <code>SELECT<\/code> statement as above):<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT il.StockItemID, il.Quantity, il.ExtendedPrice, iv.InvoiceDate\r\nFROM Sales.InvoiceLines il INNER JOIN Sales.Invoices iv\r\n  ON il.InvoiceID = iv.InvoiceID\r\nWHERE iv.InvoiceDate BETWEEN '2013-01-01' AND '2015-12-31'\r\nORDER BY il.StockItemID, il.Quantity DESC;<\/pre>\n<p>The first time you rerun this statement, you should receive the same results as before, with the <em>Memory Grant<\/em> attribute showing a total of 78,464 KB of memory, or something close to that. However, when you then rerun the statement, the total should be much lower. On my system, the subsequent executions resulted in a <em>Memory Grant<\/em> total of 14,592 KB, as shown in Figure 3.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"261\" height=\"346\" class=\"wp-image-81110\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/10\/word-image-2.png\" \/><\/p>\n<p class=\"caption\">Figure 3. <em>Memory Grant<\/em> attribute of the <em>Select<\/em> operator<\/p>\n<p>When I tested the memory grant feedback feature on my system, I reran the above <code>SELECT<\/code> statement numerous times. Although I generally received the same <em>Memory Grant<\/em> total described here, in some cases I would get the original estimate or even another value. For the most part, however, the feature worked as advertised, despite the relatively few inconsistencies.<\/p>\n<p>You can also disable the memory grant feedback feature on a per-statement basis by including an <code>OPTION<\/code> clause that specifies the hint <code>DISABLE_BATCH_MODE_MEMORY_GRANT_FEEDBACK<\/code>, as shown in the following <code>SELECT<\/code> statement:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT il.StockItemID, il.Quantity, il.ExtendedPrice, iv.InvoiceDate\r\nFROM Sales.InvoiceLines il INNER JOIN Sales.Invoices iv\r\n  ON il.InvoiceID = iv.InvoiceID\r\nWHERE iv.InvoiceDate BETWEEN '2013-01-01' AND '2015-12-31'\r\nORDER BY il.StockItemID, il.Quantity DESC\r\nOPTION (USE HINT ('DISABLE_BATCH_MODE_MEMORY_GRANT_FEEDBACK')); <\/pre>\n<p>The <em>Memory Grant<\/em> attribute should once again show a total of 78,464 KB of memory (or something similar), no matter how often you rerun the statement, at least until the plan is recached.<\/p>\n<h2>Adaptive Joins<\/h2>\n<p>When a <code>SELECT<\/code> statement includes a join condition, the query engine attempts to determine the best join type to use based on the estimated number of rows. Prior to SQL Server 2017, if an execution plan chose a bad join type, there was little that could be done, outside of specifying a query hint or specific join type.<\/p>\n<p>The new adaptive join feature helps to remedy this situation by choosing a different join type during statement execution, if necessary. After the first input has been scanned, the execution plan determines whether to change the join type to a hash join or nested loop join based on a calculated threshold.<\/p>\n<p>You can see how this feature works by comparing the old method to the new, similar to the approach taken when testing the memory grant feedback feature. To disable the adaptive join feature, run the following <code>ALTER<\/code> <code>DATABASE<\/code> statement, setting the <code>DISABLE_BATCH_MODE_ADAPTIVE_JOINS<\/code> option to <code>ON<\/code>:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">ALTER DATABASE SCOPED CONFIGURATION \r\nSET DISABLE_BATCH_MODE_ADAPTIVE_JOINS = ON;<\/pre>\n<p>Not surprisingly, the <code>DISABLE_BATCH_MODE_ADAPTIVE_JOINS<\/code> option is specific to adaptive joins. However, the adaptive query processing options all work the same. To disable a feature, set its related option to <code>ON<\/code>, and to enable the feature, set the option to <code>OFF<\/code>.<\/p>\n<p>To verify that the option has been set to <code>ON<\/code> and the feature disabled, you can run the following <code>SELECT<\/code> statement:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT * FROM sys.database_scoped_configurations<\/pre>\n<p>The <code>SELECT<\/code> statement returns the results shown in Figure 3, which indicate that the <code>DISABLE_BATCH_MODE_ADAPTIVE_JOINS<\/code> option has been set to <code>1<\/code> (<code>ON<\/code>) and that the default is <code>0<\/code> (<code>OFF<\/code>).<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"654\" height=\"188\" class=\"wp-image-81111\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/10\/word-image-3.png\" \/><\/p>\n<p class=\"caption\">Figure 4. Disabling adaptive joins<\/p>\n<p>Next, run the following <code>SELECT<\/code> statement, only this time enable <em>Live Query Statistics<\/em>:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT iv.InvoiceID, il.InvoiceLineID, il.StockItemID, il.Quantity\r\nFROM Sales.Invoices iv INNER JOIN Sales.InvoiceLines il\r\n  ON iv.InvoiceID = il.InvoiceID\r\nWHERE il.Quantity &gt; 100;<\/pre>\n<p>The execution plan should look similar to the one shown in Figure 5, which shows a columnstore index scan, a nonclustered index scan, and a hash join.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"860\" height=\"340\" class=\"wp-image-81112\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/10\/word-image-4.png\" \/><\/p>\n<p class=\"caption\">Figure 5. Executing a non-adaptive join<\/p>\n<p>Because <em>Live Query Statistics<\/em> were enabled, the plan also shows the counts for the number of rows compared to the number of estimated rows, all of which should look fairly straightforward. In fact, I\u2019ve included this example only to compare it with the query plan when adaptive joins are enabled.<\/p>\n<p>The next step, then, is to enable adaptive joins by running the following <code>ALTER<\/code> <code>DATABASE<\/code> statement:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">ALTER DATABASE SCOPED CONFIGURATION \r\nSET DISABLE_BATCH_MODE_ADAPTIVE_JOINS = OFF;<\/pre>\n<p>After running this statement, rerun the preceding <code>SELECT<\/code> statement (again shown here for your convenience):<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT iv.InvoiceID, il.InvoiceLineID, il.StockItemID, il.Quantity\r\nFROM Sales.Invoices iv INNER JOIN Sales.InvoiceLines il\r\n  ON iv.InvoiceID = il.InvoiceID\r\nWHERE il.Quantity &gt; 100;<\/pre>\n<p>Now take a look at the execution plan. You\u2019ll find a couple additions, including a <em>Clustered Index Seek<\/em> operator and, more importantly, the new <em>Adaptive Join<\/em> operator, as shown in Figure 6.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"872\" height=\"427\" class=\"wp-image-81113\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/10\/word-image-5.png\" \/><\/p>\n<p class=\"caption\">Figure 6. Executing an adaptive join<\/p>\n<p>The <em>Clustered Index Seek<\/em> operator is included for use by a nested loop join if needed. Notice that 0 of 24370 is specified, indicating that this branch is unused, which implies that a hash join was selected for this operation.<\/p>\n<p>The <em>Adaptive Join<\/em> operator determines what join type is used by calculating a threshold that determines whether to perform a hash join or a nested loop join, based on the row count. In this case, that threshold is 159.754, and the row count is 24,459. If the row count is greater than or equal to the threshold, the query plan uses a hash join. Otherwise, the plan uses a nested loop join.<\/p>\n<p>If you hover over the <em>Adaptive<\/em> <em>Join<\/em> operator to display the details, you\u2019ll see that they include three important attributes:<\/p>\n<ul>\n<li><em>Estimated Join Type<\/em>, which is set to<em> HashMatch<\/em><\/li>\n<li><em>Adaptive Threshold Rows<\/em>, which is set to<em> 159.754<\/em><\/li>\n<li><em>Is Adaptive<\/em>, which is set to<em> True<\/em><\/li>\n<\/ul>\n<p>Figure 7 shows the details for the <em>Adaptive Join<\/em> operator after running the <code>SELECT<\/code> statement with the adaptive join feature enabled.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"321\" height=\"587\" class=\"wp-image-81114\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/10\/word-image-6.png\" \/><\/p>\n<p class=\"caption\">Figure 7. Attributes of the <em>Adaptive Join<\/em> operator<\/p>\n<p>Suppose you now run the following <code>UPDATE<\/code> statement against the <code>InvoiceLines<\/code> table:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">UPDATE Sales.InvoiceLines SET Quantity = 361\r\nWHERE InvoiceLineID = 41606;<\/pre>\n<p>Next, run the previous <code>SELECT<\/code> statement again, only this time specify a <code>Quantity<\/code> value of <code>360<\/code> in the <code>WHERE<\/code> clause:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT iv.InvoiceID, il.InvoiceLineID, il.StockItemID, il.Quantity\r\nFROM Sales.Invoices iv INNER JOIN Sales.InvoiceLines il\r\n  ON iv.InvoiceID = il.InvoiceID\r\nWHERE il.Quantity &gt; 360;<\/pre>\n<p>This time, the details for the <em>Adaptive<\/em> <em>Join<\/em> operator will show the join type as NestedLoops and the threshold as 104.24.<\/p>\n<p>If you want to return the <code>WorldWideImporters<\/code> database back to its original state, run the following <code>UPDATE<\/code> statement:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">UPDATE Sales.InvoiceLines SET Quantity = 360\r\nWHERE InvoiceLineID = 41606;<\/pre>\n<p>Be aware that the adaptive join feature comes with extra memory overhead and that it currently supports only <code>SELECT<\/code> statements (no data modification statements). In addition, the statement must be eligible for both hash joins and nested loop joins to use the adaptive join feature.<\/p>\n<p>Similar to the memory grant feedback feature, you can disable adaptive joins on a per-statement basis by including an <code>OPTION<\/code> clause and specifying the <code>DISABLE_BATCH_MODE_<\/code> <code>ADAPTIVE_JOINS<\/code> hint, as shown in the following <code>SELECT<\/code> statement:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT iv.InvoiceID, il.InvoiceLineID, il.StockItemID, il.Quantity\r\nFROM Sales.Invoices iv INNER JOIN Sales.InvoiceLines il\r\n  ON iv.InvoiceID = il.InvoiceID\r\nWHERE il.Quantity &gt; 100\r\nOPTION (USE HINT('DISABLE_BATCH_MODE_ADAPTIVE_JOINS'));<\/pre>\n<p>When you include the <code>OPTION<\/code> clause, the <code>SELECT<\/code> statement will run just like it would in a database with a compatibility level earlier than 140, but without affecting the current compatibility level.<\/p>\n<h2>Interleaved Execution<\/h2>\n<p>Prior to SQL Server 2017, when a statement included an MSTVF, the execution plan fixed the row estimate at 100, no matter how many rows the function might actually return. For small datasets, this usually wasn\u2019t a problem, but when there was a wide difference between the estimate and the actual count, performance could suffer.<\/p>\n<p>The interleaved execution feature helps address this issue by pausing execution long enough to capture a more accurate cardinality and then using that information for downstream operations. It should be noted, however, that using MSTVFs can still cause performance issues if they contain complex logic and will be joined against a large number of rows.<\/p>\n<p>To see how this feature works, start by running the following <code>CREATE<\/code> <code>FUNCTION<\/code> statement, which defines a very simple MSTVF:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk \">CREATE FUNCTION dbo.GetInvoiceLines (@qty INT)\r\nRETURNS @tbl TABLE(LineID INT, InvoiceID INT, \r\n    Quantity INT, Total DECIMAL)\r\nWITH SCHEMABINDING\r\nAS\r\nBEGIN\r\n  INSERT @tbl\r\n  SELECT InvoiceLineID, InvoiceID, Quantity, ExtendedPrice\r\n  FROM Sales.InvoiceLines\r\n  WHERE Quantity &gt; @qty\r\n  RETURN\r\nEND;\r\nGO<\/pre>\n<p>Next, disable the interleaved execution feature by running the following <code>ALTER<\/code> <code>DATABASE<\/code> statement, setting the <code>DISABLE_INTERLEAVED_EXECUTION_TVF<\/code> option to <code>ON<\/code>:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk \">ALTER DATABASE SCOPED CONFIGURATION \r\nSET DISABLE_INTERLEAVED_EXECUTION_TVF = ON;<\/pre>\n<p>This is just like you saw in the earlier examples, except that it\u2019s specific to interleaved executions. Also like before, to verify that the option has been set to <code>ON<\/code> and the feature disabled, you can run the following <code>SELECT<\/code> statement:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT * FROM sys.database_scoped_configurations;<\/pre>\n<p>The <code>SELECT<\/code> statement returns the results shown in Figure 8, which indicate that the <code>DISABLE_INTERLEAVED_EXECUTION_TVF<\/code> option has been set to <code>1<\/code> (<code>ON<\/code>) and that the default is <code>0<\/code> (<code>OFF<\/code>).<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"658\" height=\"192\" class=\"wp-image-81115\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/10\/word-image-7.png\" \/><\/p>\n<p class=\"caption\">Figure 8. Disabling interleaved execution<\/p>\n<p>Next, run the following <code>SELECT<\/code> statement with the <em>Actual Execution Plan<\/em> enabled:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT il.LineID, il.Quantity, il.Total, iv.InvoiceDate\r\nFROM dbo.GetInvoiceLines(100) il INNER JOIN Sales.Invoices iv\r\n  ON il.InvoiceID = iv.InvoiceID\r\nWHERE il.Total &gt; 1000;<\/pre>\n<p>The <code>SELECT<\/code> statement joins the <code>GetInvoiceLines<\/code> function to the <code>Sales.Invoices<\/code> table, passing in <code>100<\/code> as the function\u2019s parameter value. Next, go to the execution plan and hover over the <em>Table Valued Function<\/em> operator. The operator details should show a value of <em>100<\/em> for the <em>Estimated Number of Rows<\/em> attribute, as shown in Figure 9.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"371\" height=\"423\" class=\"wp-image-81116\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/10\/word-image-8.png\" \/><\/p>\n<p class=\"caption\">Figure 9. <em>Estimated Number of Rows<\/em> attribute of the <em>Table Valued Function<\/em> operator<\/p>\n<p>Although the <em>Table Valued Function<\/em> operator estimates 100 rows, the function actually returns 24,459 rows, a substantial difference between the two amounts. You can see this amount by viewing the details for the <em>Table Scan<\/em> operator (the <em>Number of Rows<\/em> attribute) or by querying the function directly.<\/p>\n<p>To see how interleaved execution changes this behaviour, first re-enable the feature by setting the <code>DISABLE_INTERLEAVED_EXECUTION_TVF<\/code> option to <code>OFF<\/code>:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk \">ALTER DATABASE SCOPED CONFIGURATION \r\nSET DISABLE_INTERLEAVED_EXECUTION_TVF = OFF;<\/pre>\n<p>Next, rerun the <code>SELECT<\/code> statement from above, passing in the same parameter value (<code>100<\/code>) when calling the function:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT il.LineID, il.Quantity, il.Total, iv.InvoiceDate\r\nFROM dbo.GetInvoiceLines(100) il INNER JOIN Sales.Invoices iv\r\n  ON il.InvoiceID = iv.InvoiceID\r\nWHERE il.Total &gt; 1000;<\/pre>\n<p>Finally, go to the execution plan and hover over the <em>Table Valued Function<\/em> operator. The operator details should now show a value of <em>24459<\/em> for the <em>Estimated Number of Rows<\/em> attribute, as shown in Figure 10.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"371\" height=\"440\" class=\"wp-image-81117\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/10\/word-image-9.png\" \/><\/p>\n<p class=\"caption\">Figure 10. Estimated Number of Rows attribute of the Table Valued Function operator<\/p>\n<p>Being able to return a more accurate row estimate for MSTVFs can help boost query performance, especially when the function returns a large number of rows. In some cases, however, you might want to disable this feature on a per-statement basis, as you saw with the other adaptive query processing features:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT il.LineID, il.Quantity, il.Total, iv.InvoiceDate\r\nFROM dbo.GetInvoiceLines(100) il INNER JOIN Sales.Invoices iv\r\n  ON il.InvoiceID = iv.InvoiceID\r\nWHERE il.Total &gt; 1000\r\nOPTION (USE HINT('DISABLE_INTERLEAVED_EXECUTION_TVF'));<\/pre>\n<p>When you include this <code>OPTION<\/code> clause and specify the hint <code>DISABLE_INTERLEAVED_EXECUTION_TVF<\/code>, the <em>Table Valued Function<\/em> operator will once again show an estimate of 100 rows.<\/p>\n<h2>Adaptive Query Processing<\/h2>\n<p>Depending on the type of queries you\u2019re running, the adaptive query processing capabilities can deliver a noticeable boost in query performance, especially as the size of your workloads grow. It\u2019s unclear at this point whether Microsoft will be enhancing these features anytime soon, but it seems likely we\u2019ll see some improvements. For example, Microsoft might eventually extend the adaptive join capabilities to data modification statements or extend the interleaved execution capabilities beyond MSTVFs. In fact, Microsoft has already released the public preview of the new Table Variable Deferred Compilation feature in Azure SQL Database and SQL Server 2019.<\/p>\n<p>If you\u2019re moving to SQL Server 2017, you should consider updating the compatibility levels of those databases that might benefit from adaptive query processing. Just be sure to fully test the databases to make sure you haven\u2019t introduced any new issues. If you\u2019re uncertain whether your organization will be moving to SQL Server 2017, you might try out the adaptive query processing features when testing other new features to help you determine whether an upgrade is worthwhile.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As new versions of SQL Server have been released, the Query Optimizer has undergone many improvements. Adaptive Query Processing, new in 2017, represents a new direction. This set of features provides some flexibility for varying join types, multi-statement table valued functions, and memory grants. In this article, Robert Sheldon explains the three features that make up Adaptive Query Processing &hellip;<\/p>\n","protected":false},"author":221841,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143527],"tags":[5966,4364,5842],"coauthors":[6779],"class_list":["post-81107","post","type-post","status-publish","format-standard","hentry","category-database-administration-sql-server","tag-monitor","tag-monitoring","tag-sql-monitor"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/81107","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221841"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=81107"}],"version-history":[{"count":8,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/81107\/revisions"}],"predecessor-version":[{"id":81211,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/81107\/revisions\/81211"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=81107"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=81107"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=81107"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=81107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}