© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
B. WardSQL Server 2022 Revealedhttps://doi.org/10.1007/978-1-4842-8894-8_4

4. Built-In Query Intelligence

Bob Ward1  
(1)
North Richland Hills, TX, USA
 

For as long as I’ve been at Microsoft, we have attempted to put our heart and soul into one of the best query processors (QPs) in the industry. But as I saw in technical support for so many years, there were some scenarios where the QP struggled. Back in 2016, our team started a journey to make the query processor adaptable to a variety of workloads, helping users achieve consistent or improved performance with no code changes.

We first called this effort Adaptive Query Processing (AQP). I first heard of this term from Joe Sack, who was the lead program manager for AQP in SQL Server 2017. AQP added the ability to respond to memory grants and support adaptive joins and introduced batch mode for analytic queries. These were all enabled by changing the database compatibility (dbcompat) level to 140 (the default for SQL Server 2017) with no code changes required. Read more about dbcompat levels at https://aka.ms/dbcompat. This was just the beginning.

Note

Have fun and go back in time. See Conor Cunningham and me present AQP at SQLBits in 2018: https://sqlbits.com/Sessions/Event17/Adaptive_query_processing_in_SQL_databases?msclkid=2b8c0902c4ca11ecb9e5158053df6fa9.

We doubled our efforts and rebranded this concept Intelligent Query Processing (IQP) in SQL Server 2019. IQP included new and powerful scenarios like table variable deferred compilation and expanded memory grants and batch mode to row-based tables. You can read a detailed coverage of IQP in Chapter 2 of my book SQL Server 2019 Revealed, or you can dive into our documentation at https://aka.ms/iqp. These scenarios were all enabled with no code changes by simply changing to dbcompat level 150.

Note

Approximate Query Processing did not require dbcompat 150, just an upgrade to SQL Server 2019.

As we rolled into SQL Server 2022, we decided to keep the IQP branding but also roll up the entire new set of scenarios into the term built-in query intelligence. This includes the next generation (nextgen) of IQP, new enhancements to Query Store, and these scenarios working together.

How good is built-in query intelligence in SQL Server 2022? I broke up these capabilities into two chapters. This chapter will include an introduction to all the built-in query intelligence concepts and then dive into Query Store and Intelligent Query Processing (IQP) features that don’t require dbcompat 160. Chapter 5 will dive into three new IQP capabilities that are available when you enable dbcompat 160 (but again, no code changes are required).

Why so much content for just one part of SQL Server 2022? Consider these points:
  • This is one of the richest areas of investment for SQL Server 2022.

  • Almost every scenario in these two chapters has a story behind it. Not only do I like telling stories but I have firsthand experiences behind many of them.

  • While the entire concept of built-in query intelligence is to gain performance and insights with little effort, I take time in both chapters to explain details behind each of them.

  • There are many examples in both chapters for you to try out these new capabilities.

As you read these next two chapters, keep in mind the following principles our team has always used in this area:
  • As much as possible, “do no harm.” In other words, make sure not to break existing workloads and not make them run slower than they would if the feature were not available.

  • Require no code changes to take advantage of these features (with exceptions like Query Store hints and approximate percentile).

  • Allow any of these features to be individually turned off. This allows you to take advantage of ones you like and disable others that may not be the best for your workload.

Does our innovation really work? Our partner group engineering manager for the engine, Naveen Prakash, thinks so: “Unpredictable query performance has resulted in people unable to get in their cars or trucks failing to leave the warehouse. SQL Server 2022 continues to raise the bar for transparent, predictable, and efficient query processing through intelligent monitoring and adaptive execution.”

So sit back and read on. Pick sections you want to read now and maybe come back later for more. Imagine yourself over the next two chapters in a past “brain melting” session I’ve presented.

Let’s look at the overall lineup of new capabilities, dive into the new Query Store, and then go step-by-step to see how you can take advantage of some of the capabilities in the nextgen IQP.

Built-In Query Intelligence in SQL Server 2022

Early in the planning for SQL Server 2022, Pedro Lopes and I came up with the term built-in query intelligence. We did this to represent the innovation that is new in SQL Server 2022 to gain and maintain consistent performance with no code changes. In addition, these changes allow you to gain new insights into query performance and tune queries faster and in new ways with enhancements to Query Store. These innovations are “built in” to the engine itself and use “data” to act intelligently. That data is based on statistics or execution information from data sources like Query Store.

This collection of new capabilities includes the following:
  • Query Store enhancements such as being on by default for new databases, support for queries executed against read replicas, and Query Store hints.

  • New IQP scenarios including some amazing solutions to “age-old” problems for SQL Server query performance; in fact, some of the new capabilities use Query Store.

Figure 4-1 shows a visualization of all the new IQP features and notes which ones use Query Store.

A screenshot titled intelligent query processing I Q P next gen. It has 3 columns titled upgrade to S Q L server 2022, dbcompat 140 plus, and dbcompat 160 with features underneath each.

Figure 4-1

SQL Server 2022 IQP new features

This chapter will cover capabilities you can get without dbcompat 160 (the left two columns). Chapter 5 will dive into IQP capabilities if you enable dbcompat 160 including Parameter-Sensitive Plan (PSP) optimization, cardinality estimation (CE) model feedback, and degree of parallelism (DOP) feedback.

Note in the bottom-right corner a box describing that “IQP Gen1” is enabled with dbcompat 140. This represents the IQP capabilities we introduced in SQL Server 2017. “IQP Gen1+2” represents the IQP capabilities we introduced in SQL Server 2019 plus SQL Server 2017. It is important to know this because you might be upgrading from SQL Server 2016 or earlier to SQL Server 2022 and not realize all the previous IQP capabilities you could be using. You can always keep up with all IQP features at https://aka.ms/iqp.

Let’s start by looking at enhancements for Query Store. There are enough new enhancements to SQL Server 2022 I call it the new Query Store.

The New Query Store

Query Store in my opinion is simply one of the coolest features we have ever put in the SQL Server engine. As far back as the mid-2000s, the famous Conor Cunningham approached several of us in customer support about a new way to track query performance. Up to this point, we were all “polling” Dynamic Management Views to persist query performance execution data. Conor had the idea baking into the query processor itself telemetry about query performance execution. This telemetry would be persisted in the user database in the form of system tables. He called it Query Disk Store (QDS), which is now known today as Query Store.

It took several iterations, but Conor’s dream of this innovation was realized in SQL Server 2016. It was also a key piece for success of Azure SQL Database as we turned it on by default for all new databases.

I still find today many customers I talk to do not use Query Store. Some don’t know exactly what it is, some have heard of it but haven’t tried to turn it on, and others have used it but had issues.

By SQL Server 2019, we completed new enhancements to fix some performance issues, added wait stats information (added in SQL Server 2017), and introduced extra controls to help with query capture and better storage management of the data. All of this has set us up to move forward with a new Query Store for everyone.

If you are new to Query Store, I recommend you read over our documentation first at https://aka.ms/querystore before you read our SQL Server 2022 enhancements. With that in mind, there are a few fundamental principles about Query Store you need to keep in mind as you use new capabilities in SQL Server 2022:
  • Query Store has an option called capture mode. Capture mode controls what type of queries are kept in Query Store. A value of ALL means all queries are collected (well, not exactly all since some SQL statements are never candidates for Query Store like CREATE TABLE). A value of AUTO means only meaningful queries are collected, which effectively means only queries that you care about collecting performance information are kept. A value of CUSTOM allows you to configure a more granular control of what is kept vs. ALL or AUTO. The reason this concept is important is that you may trying to use a feature that relies on Query Store but it doesn’t appear to be working. It could be that the query was not eligible to be kept in Query Store based on capture policies. You can read more about Query Store capture mode at https://docs.microsoft.com/sql/relational-databases/performance/best-practice-with-the-query-store?#set-the-optimal-query-store-capture-mode.

  • Query Store keeps all data within system tables within the context of a user database. This extends the storage required for the database. There are options (with defaults) that control what is the maximum size of the space used for Query Store and cleanup policies. In addition, there are ways to manually remove all data, or subsets of data, within Query Store. You can read more about Query Store maintenance at https://docs.microsoft.com/sql/relational-databases/performance/monitoring-performance-by-using-the-query-store?#Scenarios.

Armed with this knowledge, in this section I will show you why I call Query Store in SQL Server 2022 new including the following:
  • Query Store is now on by default for new databases.

  • Query Store hints to shape query plans with no code changes.

  • Query Store supports telemetry for queries against read-only replicas.

  • Some new IQP features use Query Store to improve performance with no code changes.

On by Default

I remember vividly when we were talking about new features for project Dallas when Pedro Lopes and Joe Sack said to me, “Bob, we are going to turn on Query Store by default.” My reaction was “Hey, we don’t do that for SQL Server. We are pretty conservative when it comes to turning things like this on by default.” But their explanation as to why we were doing this was a good one.

Here is a summary of the facts they presented:
  • Query Store has been around for several releases since SQL Server 2016.

  • We have this on by default for Azure SQL for millions of databases. And we have been doing this for several years.

  • We changed the default on how to capture queries to a mode to ease burden on applications with ad hoc workloads (QUERY_CAPTURE_MODE to AUTO).

  • We added a new CUSTOM option in SQL Server 2019 for QUERY_CAPTURE_MODE to give more control on what was captured by Query Store using the QUERY_CAPTURE_POLICY options.

  • We plan to add Query Store hints and support for read replicas in SQL Server 2022.

  • Finally, and the most important reason, some new IQP scenarios would require Query Store. This statement really caught my eye. I remember saying something like “Say that again?” You will see what we mean by requiring Query Store when you read about new IQP scenarios for SQL Server 2022 in this and the next chapter.

This is all nice, but I’m sure you are reading this and still may be asking, “Is there any impact to my application?”

The answer as with any general question like this is “It depends.” All the reasons Joe and Pedro gave me previously make me feel comfortable telling anyone that the impact to any application with Query Store enabled could be negligible.

Having said that here are a few thoughts in my personal opinion to consider:
  • We only enable Query Store for newly created databases in SQL Server 2022. Any databases restored from a previous version of SQL Server will only have Query Store enabled if it was already turned on.

  • I always recommend to any customer to test their application with any system or program that is capturing telemetry. This includes third-party applications, use of Dynamic Management Views (DMVs), Extended Events, or Query Store.

  • We have had Query Store on by default for Azure SQL Database for several years with no report of customer workloads being impacted.

  • New capture policies allow you to “capture what you need,” therefore decreasing overall impact to the system.

  • We still recommend for some production workloads the use of trace flag 7745 to avoid impact of shutdown. You can read more about this trace flag and all the best practices for Query Store at https://docs.microsoft.com/sql/relational-databases/performance/best-practice-with-the-query-store.

Query Store Hints

In the summer of 2021, we introduced the concept of Query Store hints for Azure SQL Database. The concept of Query Store hints is to shape query execution plans without changing application code.

Note

You can read about the original launch of Query Store hints in this episode of Data Exposed with Anna Hoffman and Joe Sack: https://youtu.be/pYB6Uik_Q7A.

The concept of a query hint is not new, and you may use them already today. By adding an OPTION clause to your T-SQL statement, you can influence the query optimizer on the building of a query plan. Two common query hints I’ve seen used are RECOMPILE (for a compilation each time a query is executed) and MAXOP (control the max degree of parallelism at the query level). You can read about all the possible query hints and examples at https://docs.microsoft.com/sql/t-sql/queries/hints-transact-sql-query.

If you are not using query hints, don’t be concerned. Query hints are designed for scenarios where for some reason your application is not working well with the query plan compiled by the query processor by default.

However, there are practical reasons when these hints are needed for production workloads. Query hints require the ability to change the T-SQL statement. But what if the T-SQL statement is called in application code and it is not practical or not possible to change application code to apply the hint? This is where Query Store hints can be beneficial because the query optimizer can apply the hint based on what is stored in Query Store without changing application code.

How Do You Use Query Store Hints?

Here is how you would use a Query Store hint assuming Query Store is enabled for read/write:
  • A query is executed, and the plan is stored in Query Store.

  • You find the query_id using Query Store catalog views for the query to which you want to apply the hint.

  • You execute the stored procedure sys.sp_query_store_set_hints specifying the query_id and the hint you want to apply.

  • You can view existing Query Store hints with the view sys.query_store_query_hints.

We save the hint in Query Store so the query processor will use the hint provided for the next execution of the query. Because the hint is persisted, it will survive restarts and plan cache eviction.

Not all query hints are supported with Query Store. For a complete list of supported query hints, check the documentation at https://docs.microsoft.com/sql/relational-databases/system-stored-procedures/sys-sp-query-store-set-hints-transact-sql. There are also some scenarios where the query optimizer cannot use a query hint you specify based on the compilation rules for a given query. This is called a Query Store hint failure and will be logged in the view sys.query_store_query_hints.

The documentation has a very nice end-to-end example for using Query Store hints at https://docs.microsoft.com/sql/relational-databases/performance/query-store-hints?#examples. I’ll show you an example of a system-generated Query Store hint in Chapter 5 called cardinality estimation (CE) model feedback.

When Should I Use a Query Store Hint?

While query hints could be a useful feature for you to use long-term with your application, most of the uses of query hints I’ve seen are a temporary measure. Therefore, Query Store hints can be an extremely useful feature when you want to temporarily apply a query hint and not change (or can’t change) application code. Query Store hints are also nice in situations where you want to override a query hint in a query from the application code.

Note

There are some aspects of using Query Store hints you should understand before using them, which you can read about at https://docs.microsoft.com/sql/relational-databases/performance/query-store-hints?#query-store-hints-and-feature-interoperability.

Before you use a Query Store hint, confirm that the problem you are trying to solve can’t be done another way. For example, many have used the RECOMPLIE or OPTIMIZE FOR hint to solve the classic problem of Parameter-Sensitive Plans. In SQL Server 2022, we have a new solution for this problem that may allow you to avoid the use of those query hints. You can read more about Parameter-Sensitive Plan (PSP) optimization in Chapter 5.

How Is This Different Than Plan Guides?

Like Query Store hints, plan guides provide a method to shape query plans without changing application code. Plan guides have been in SQL Server for many versions. Query store hints provide a much simpler interface to shape query plans mainly because you only need to add a query hint(s) to an existing query stored in Query Store.

Plan guides can be created by providing the original SQL text of the query from the application or by using a plan handle from a plan in cache. Therefore, Query Store hints have the advantage of being a simpler method for implementation and can be used for queries persisted over time. One advantage plan guides have over Query Store hints is that they do not require Query Store to be enabled.

Query Store Support for Secondary Replicas

While Query Store has been a great innovation for SQL Server, a frequently requested enhancement is to support collecting performance execution statistics for queries run against secondary read replicas for Always On Availability Groups.

With SQL Server 2022, you can now configure an Always On Availability Group to support capturing statistics if queries are directed at a read replica.

I think the best way to dive deeper into this new capability is to answer some “how” questions.

How to Do You Configure It?

Once Query Store is enabled on the primary replica of the Always On Availability Group, you can run the following T-SQL in the context of the primary database:
ALTER DATABASE CURRENT FOR SECONDARY SET QUERY_STORE = ON ( OPERATION_MODE = READ_WRITE);
Note

At the time of the writing of the book before General Availability, trace flag 12606 was required to enable this feature. Please consult the documentation at General Availability to see if this trace flag is still required. When you enable this feature, Query Store information is captured for all secondary replicas in the availability group. There is no method to control this per secondary replica.

Once you execute this statement, any query executed against any secondary replica will be captured in Query Store.

But if the query is run against a secondary replica, how can Query Store capture information about the query if the replica database is read-only? Let’s see how it works.

How Does It Work?

When you enable Query Store and performance information is captured for a query, it is first recorded in a set of memory structures. Asynchronously, Query Store information is flushed to disk (to a collection of system tables in the user database). Runtime statistics are flushed to disk based on a configuration value called DATA_FLUSH_INTERVAL_SECONDS. You can read more about how Query Store collects information at https://docs.microsoft.com/sql/relational-databases/performance/how-query-store-collects-data.

If you have enabled Query Store capture for secondary replicas, Query Store information is captured in memory structures on the secondary replica, but not flushed to the disk for the secondary replica database (because the database is read-only). Instead, the information is sent back to the primary replica where it is flushed to disk. Always On Availability Group replicas have a channel to communicate with each other, and we simply use that channel to send data back to the primary. The SQL Server 2022 engine has been enhanced to understand this new type of information sent back to the primary and then adds the data to Query Store on the primary replica as seen in Figure 4-2.

A flowchart of query store and stats. The flow starts with queries and ends at query store queries for S Q L.

Figure 4-2

Query Store and secondary replicas

Let’s explore more the flow of how this works. Let’s assume you have an Always On Availability Group set up and configured for Query Store on secondaries. When a user issues a query against the secondary read replica, the engine will collect Query Store stats in memory. These stats are not persisted on the secondary because the database is read-only.
  • Background tasks will queue these stats in memory in the secondary replica engine. At some point (an internal implementation detail), these stats will be sent on the same communication channel used for availability group log changes (the dbm endpoint). We try to make sure that these performance stats have a lower priority than log changes so as not to disrupt replica of log changes.

Note

As I was writing this chapter, our team was investigating whether we could enable the system stored procedure sp_query_store_flush_db to force the sending of stats from the secondary to the primary.

  • On the primary replica, background tasks will receive these Query Store stats and persist them to disk, tracking the replica details along with the stats. Since these stats are stored in the database and are logged changes, they are also sent to secondary replicas like all logged changes.

  • Now, any user can run queries or reports for Query Store stats against the primary replica or secondary replicas.

In order to distinguish the difference between queries on the primary and secondary replicas, a new column has been added to Query Store view sys.query_store_runtime_stats called replica_group_id. This maps to a new view called sys.query_store_replicas, which contains all the replica names, roles, and their ID value.

There are a few things I think you should know about the expected behavior of this capability:
  • If you enable this feature, you may need to increase the value of MAX_STORAGE_SIZE_MB, especially if you have multiple secondary replicas where you are executing queries.

  • The results do not show up on the primary replica immediately. Since we use the same channel for replicating changes to replicas, we don’t want to negatively impact transaction changes. Therefore, we send back data to the primary in a delayed fashion. Today there is no configuration value to adjust this time. In my experience, it can take several minutes for query performance information to appear on the primary after executing a query on a secondary replica.

  • Even with this delay, if your application is sensitive to changes to be replicated in your Always On Availability Group, you should test this option carefully to ensure the use of the standard communication channel for replica changes is not impacted. Throughput can also depend on how often you execute queries on secondary replicas and how many secondary replicas are in the availability group.

  • The only Query Store views that capture the replica_group_id are sys.query_store_runtime_stats and sys.query_store_wait_stats. If you want to find out information in the plan store (i.e., Sys.query_store_query) specific to a replica, you will need to join to one of these views (using the plan_id as the join column). In other words, if you only query sys.query_store_plan, you will not be able to tell whether plans were captured from the primary or secondary replicas.

  • At the time of the writing of this chapter, we were still working out what features that use Query Store may or may not be supported for read replicas. This includes Query Store hints, optimized plan forcing, DOP feedback, and CE feedback. You can keep track of all the latest updates with this feature in the documentation at https://docs.microsoft.com/sql/relational-databases/performance/monitoring-performance-by-using-the-query-store?#query-store-for-secondary-replicas.

How Do You Use It?

Since we are just enhancing the existing Query Store functionality, you can now use Query Store like you do today to analyze query performance. And because all the data is in the primary replica, any backup of the primary replica database includes all the information from all replicas.

Store for IQP

Since Query Store data is persisted in the database, it survives restarts, contains important telemetry information, and therefore serves as a perfect “store” for new IQP features.

In addition, since Query Store has persistent storage, the query processor can save specific information in Query Store to enable new capabilities. Furthermore, Query Store implementation in the engine has background tasks, which can be used to perform analysis to avoid impacting query execution.

For example, Query Store background tasks can be used to record query performance statistics and feedback data for queries that are eligible for degree of parallelism (DOP) feedback. This data is persisted in Query Store tables and can be used by the query processor when determining the degree of parallelism (DOP) for a query. You can read more about the details of DOP feedback in Chapter 5.

Because specific IQP features rely on Query Store, it is even more important to maintain and configure Query Store properly. You should treat Query Store as a production set of data (even though it is a collection of system tables within the user database).

IQP Nextgen Defaults

When you upgrade to SQL Server 2022, several IQP features are simply “turned on” without any dbcompat change required. This means that you could be using a dbcompat level from versions of SQL Server prior to when IQP “was born,” like SQL Server 2016 (dbcompat 130), and take advantage of new IQP features. This includes approximate percentile and optimized plan forcing. Let’s explain how each of these features works and how you can take advantage of them.

Approximate Percentile

In SQL Server 2019, we introduced a new T-SQL function called APPROX_COUNT_DISTINCT to allow you to get an approximation for the number of distinct values for a query. Even though this is not as accurate as using a SELECT COUNT DISTINCT, it can perform much faster and have an accuracy of around 97%. We have found that this type of function can greatly improve the performance of analytic workloads. You can read more about APPROX_COUNT_DISTINCT at https://docs.microsoft.com/sql/t-sql/functions/approx-count-distinct-transact-sql.

SQL Server includes two functions to help an analytic workload calculate a percentile of a range of values: PERCENTILE_CONT (which you can read about at https://docs.microsoft.com/sql/t-sql/functions/percentile-cont-transact-sql) and PERCENTILE_DISC (which you can read about at https://docs.microsoft.com/sql/t-sql/functions/percentile-disc-transact-sql).

SQL Server 2022 provides approximate equivalent to these two functions: APPROX_PERCENTILE_CONT and APPROX_PERCENTILE_DISC. These can be extremely useful for analytic workloads with exceptionally large sets of data. These functions will perform faster, and the implementation guarantees up to a 1.33% error rate within a 99% probability. Here is a T-SQL example for APPROX_PERCENTILE_CONT:
SELECT DeptId,
APPROX_PERCENTILE_CONT(0.10) WITHIN GROUP(ORDER BY Salary) AS 'P10',
APPROX_PERCENTILE_CONT(0.90) WITHIN GROUP(ORDER BY Salary) AS 'P90'
FROM tblEmployee
GROUP BY DeptId;

The argument for APPROX_PERCENTILE_CONT is the percentile to compute for the range of data.

You can read more about how to use APPROX_PERCENTILE_CONT at https://docs.microsoft.com/sql/t-sql/functions/approx-percentile-cont-transact-sql and APPROX_PERCENTILE_DISC at https://docs.microsoft.com/sql/t-sql/functions/approx-percentile-disc-transact-sql.

I spoke with Balmukund Lakhani, Senior Program Manager at Microsoft, about why T-SQL users should consider using these new functions:

In SQL Server 2022, we have the newest member in approximate query processing family: approximate percentile functions (APPROX_PERCENTILE_CONT for continuous and APPROX_PERCENTILE_DISC for discrete). These are aggregate functions which can be used in place of earlier analytic percentile functions (PERCENTILE_CONT for continuous and PERCENTILE_DISC for discrete) when faster performance is desirable at the cost of minimal error in output. Imagine a table of temperature sensor data which has millions of rows and you want to find 99th percentile quickly with acceptable rank based error bound to take quick decisions. These functions can give output in a single pass with less CPU and memory usage.

Optimized Plan Forcing

Optimized plan forcing is a new capability in SQL Server 2022 intended to reduce the time it takes to compile certain queries if the query plan is forced in Query Store.

Note

Forcing a query plan in Query Store is different than using query plan hints. Forcing a query plan allows you to take a specific query plan in Query Store and “tell” the optimizer to use the plan vs. creating a new plan on compilation. You use the system procedure sp_query_store_force_plan to force a query plan.

The Background

By 2019, our engineering team along with CSS had noticed a pattern of performance problems for some customers called a compile storm. In these situations, all query plans were evicted from cache due to some unforeseen event (e.g., failover, restart, memory pressure). Then the application would immediately generate a large number of concurrent connections, each with queries that required compilation in a truly brief period.

The typical symptom of this problem was a significant amount of CPU utilization across all processors (e.g., 100% CPU utilization or “pegged”). Applications were effectively halted as all users were running queries that required query compilation of significant duration. Query compilation is almost an entirely CPU-bound operation, and certain query patterns can take a prolonged period of time to compile and generate a query plan.

For several of these examples, the problem could occur even if the customer were using Query Store to force a specific query plan. Even when a query plan is forced in Query Store, the query still must go through compilation when the plan is not in cache.

How Does It Work?

In SQL Server 2022, we have enhanced Query Store and the query processor to shortcut the time of compilation for forced query plans if we believe compilation of the query can take a significant amount of time. We call this concept optimized plan forcing.

Here is how optimized plan forcing works:
  • If Query Store is enabled when a query is compiled, it is eligible to have a compilation script generated if query compilation takes a significant amount of time. What is significant? We don’t document the threshold for this or allow you to change it, but it effectively means the compilation phase for query execution is affecting the overall query duration. You will see in the next section titled “See It in Action” an example. In addition, only queries that require “full optimization” (i.e., queries that don’t have trivial plans) are eligible. You can review all the different considerations that determine what queries are eligible for optimized plan forcing at https://docs.microsoft.com/sql/relational-databases/performance/optimized-plan-forcing-query-store?#considerations.

  • The compilation script is binary data encoded into the binary representation of the XML query plan stored in Query Store as represented by the query_plan column from the sys.query_store_plan view. You will not see this information if you “cast” the query_plan column to XML. It is a binary format that is not documented and not part of the showplan XML schema.

  • We also mark the query plan as having a compilation script by setting the column has_compile_replay_script to 1 as seen in the sys.query_store_plan view.

All of this happens by default in SQL Server 2022 for any dbcompat level if Query Store is enabled with READ_WRITE for the database. If you use sp_query_store_force_plan to force a plan that has a compilation script, the next time we need to compile the query, we will use the compilation script to shorten compile time. This will occur as long as the query plan is marked as forced in Query Store.

When I’ve presented this concept, I have been asked, “Why would we need this feature if the query plan is forced?” A forced query plan simply “locks in” a specific query plan when a query is compiled. It doesn’t mean we don’t have to compile the query’s plan. If we can shortcut the time to compile the plan, it can end up saving a lot of CPU cycles, especially during these “compile storm” scenarios.

See It in Action

I must admit I was struggling to produce scenarios to trigger a compilation script. When I researched some of the background of our design for this feature, we sometimes used queries from the TPC-H benchmark to test it. As it turns out, anyone can run a workload that simulates TPC-H by using HammerDB (www.hammerdb.com). I followed the instructions in the HammerDB documentation for TPC-H for SQL Server (using the smallest size possible). I then enabled Query Store (capture mode = ALL) and ran a sample workload. Several queries showed up in sys.query_store_plan with a column value of 1 for has_compile_replay_script.

I looked over several of these queries and noticed this pattern:
  • Most of these queries had fairly larger SQL text than most other queries.

  • Many of these queries involved joins for many tables.

I was able then to construct my own demonstration using the WideWorldImporters sample database.

Here are the prerequisites to run this example:
  • SQL Server 2022 Evaluation Edition.

  • Virtual machine or computer with minimum of four CPUs and 8Gb RAM (it may be possible to see this in action with fewer CPUs, but this is the minimum I used for the test).

  • SQL Server Management Studio (SSMS). The latest 18.x or 19.x build will work.

  • A copy of the scripts from the GitHub repo of this book from the ch4_builtinqueryintelligenceopf directory.

Here are the steps you can use to see this for yourself:
  1. 1.

    Copy the sample backup for WideWorldImporters from https://aka.ms/WideWorldImporters. The restore script in the next steps assumes a location for the backup in c:sql_sample_databases.

     
  2. 2.
    Execute the script restorewwi.sql. Edit the file locations per your needs. The script executes the following T-SQL statements:
    USE master;
    GO
    DROP DATABASE IF EXISTS WideWorldImporters;
    GO
    RESTORE DATABASE WideWorldImporters FROM DISK = 'c:sql_sample_databasesWideWorldImporters-Full.bak' with
    MOVE 'WWI_Primary' TO 'c:sql_sample_databasesWideWorldImporters.mdf',
    MOVE 'WWI_UserData' TO 'c:sql_sample_databasesWideWorldImporters_UserData.ndf',
    MOVE 'WWI_Log' TO 'c:sql_sample_databasesWideWorldImporters.ldf',
    MOVE 'WWI_InMemory_Data_1' TO 'c:sql_sample_databasesWideWorldImporters_InMemory_Data_1',
    stats=5;
    GO
    ALTER DATABASE WideWorldImporters SET QUERY_STORE CLEAR ALL;
    GO
     
  3. 3.
    Execute the script bigjoin.sql. The query should take around ~7–10 seconds to complete. The script executes the following T-SQL:
    USE WideWorldImporters;
    GO
    ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
    GO
    SET STATISTICS TIME ON
    GO
    SELECT o.OrderID, ol.OrderLineID, c.CustomerName, cc.CustomerCategoryName, p.FullName, city.CityName, sp.StateProvinceName, country.CountryName, si.StockItemName
    FROM Sales.Orders o
    JOIN Sales.Customers c
    ON o.CustomerID = c.CustomerID
    JOIN Sales.CustomerCategories cc
    ON c.CustomerCategoryID = cc.CustomerCategoryID
    JOIN Application.People p
    ON o.ContactPersonID = p.PersonID
    JOIN Application.Cities city
    ON city.CityID = c.DeliveryCityID
    JOIN Application.StateProvinces sp
    ON city.StateProvinceID = sp.StateProvinceID
    JOIN Application.Countries country
    ON sp.CountryID = country.CountryID
    JOIN Sales.OrderLines owl
    ON ol.OrderID = o.OrderID
    JOIN Warehouse.StockItems si
    ON ol.StockItemID = si.StockItemID
    JOIN Warehouse.StockItemStockGroups sisg
    ON si.StockItemID = sisg.StockItemID
    UNION ALL
    SELECT o.OrderID, ol.OrderLineID, c.CustomerName, cc.CustomerCategoryName, p.FullName, city.CityName, sp.StateProvinceName, country.CountryName, si.StockItemName
    FROM Sales.Orders o
    JOIN Sales.Customers c
    ON o.CustomerID = c.CustomerID
    JOIN Sales.CustomerCategories cc
    ON c.CustomerCategoryID = cc.CustomerCategoryID
    JOIN Application.People p
    ON o.ContactPersonID = p.PersonID
    JOIN Application.Cities city
    ON city.CityID = c.DeliveryCityID
    JOIN Application.StateProvinces sp
    ON city.StateProvinceID = sp.StateProvinceID
    JOIN Application.Countries country
    ON sp.CountryID = country.CountryID
    JOIN Sales.OrderLines ol
    ON ol.OrderID = o.OrderID
    JOIN Warehouse.StockItems si
    ON ol.StockItemID = si.StockItemID
    JOIN Warehouse.StockItemStockGroups sisg
    ON si.StockItemID = sisg.StockItemID
    ORDER BY OrderID;
    GO

    Notice the amount of SQL text, multiple joins, and UNION ALL clause. The procedure cache for the database is cleared to make sure the query is compiled for each execution. This may not be a beautifully written query, but it represents an example of a query where the compile time can represent a sizable percentage of the CPU time required to execute the query.

    Use the Messages tab in the SSMS results window and scroll down to the bottom. Your result should look similar to the following:
    SQL Server parse and compile time:
       CPU time = 353 ms, elapsed time = 353 ms.
    (916540 rows affected)
     SQL Server Execution Times:
       CPU time = 2531 ms,  elapsed time = 7827 ms.

    You can see in this example that the compile time is ~13% of the overall CPU time for query execution.

     
  4. 4.
    Now let’s see if this query is a candidate for optimized plan forcing. Execute the script find_query_in_query_store.sql. The script uses the following T-SQL:
    USE WideWorldImporters;
    GO
    SELECT query_id, plan_id, avg_compile_duration/1000 as avg_compile_ms,
    last_compile_duration/1000 as last_compile_ms, is_forced_plan,
    has_compile_replay_script,
    cast(query_plan as xml) query_plan_xml
    FROM sys.query_store_plan;
    GO
    The results should look like the following (I flipped the results to show each field vertically):
    query_id                        2
    plan_id                         1
    avg_compile_ms                  353.088
    last_compile_ms                 353
    is_forced_plan                  0
    has_compile_replay_script       1
    query_plan_xml                  <ShowPlanXML…>

    The query_id and plan_id could be different, and your compile times could vary. However, they should be somewhere around 10–15% of the overall CPU time for the execution of the query. The key is the value of has_compile_replay_script is 1.

    If you “click” in SSMS on the query_plan_xml output, SSMS will open a new window and show XML text. If you look closely at the line starting with <StmtSimple… and scroll to the right, you will see these two values:

     
QueryCompilationReplay="1" StatementOptmLevel="FULL"
This means that this query is a candidate for optimized plan forcing. The “script” or “compile steps” have been baked into the plan XML in Query Store. These steps will be used the next time this query is compiled, but only if the query plan is forced in Query Store.
  1. 5.
    Let’s try it. Load the script forceplan.sql. Edit the script to put in the query_id and plan_id values from the results in step 4. Execute the script. The script uses the following T-SQL:
    EXEC sp_query_store_force_plan @query_id = <n>, @plan_id = <n>;
    GO
     
  2. 6.

    Verify the query plan is forced by executing the script find_query_in_query_store.sql again. You should see a value for is_forced_plan of 1.

     
  3. 7.
    The next time this query is compiled, we should see a significant reduction in compilation time. Run the script bigjoin.sql again (be sure to run the entire script as it forces a new compile). Using the Messages tab again, observe the compile time vs. overall CPU time. Your result should look similar to the following:
    SQL Server parse and compile time:
       CPU time = 38 ms, elapsed time = 38 ms.
    (916540 rows affected)
     SQL Server Execution Times:
       CPU time = 2672 ms,  elapsed time = 7636 ms.

    You can see from these results that the time to compile the query is now ~1% of the overall CPU time.

     
  4. 8.
    To see the new values in Query Store views, first, flush data to Query Store so we can see all the recent statistics using the script flush_query_store.sql. This script uses the following T-SQL:
    USE WideWorldImporters;
    GO
    EXEC sys.sp_query_store_flush_db;
    GO
     
  5. 9.

    Now look at the latest statistics in Query Store by executing the script find_query_in_query_store.sql again. The last_compile_ms should match the value of the latest “SQL Server parse and compile time” from step 7.

     

Considerations for Using Optimized Plan Forcing

We enabled this feature by default for new databases in SQL Server 2022 because there is really no harm in using it. It does require Query Store to be enabled. If you are forcing query plans, any of the plans that are eligible for a compilation script will simply compile faster. There is no real overhead in using it.

Having said that, you can disable optimized plan forcing using the following methods:
  • Set the ALTER DATABASE SCOPED CONFIGURATION statement using the option OPTIMIZED_PLAN_FORCING to OFF. (You can also use this option to turn it back on.)

  • You can disable optimized plan forcing when you force a query plan with the system stored procedure sp.query_store_force_plan by using the @disable_optimized_plan_forcing option.

  • You can use a query hint called DISABLE_OPTIMIZED_PLAN_FORCING.

Tip

When you want to get a list of all possible query hints (i.e., USE HINT), execute the system procedure sys.dm_exec_valid_use_hints.

Keep up with all the latest information on optimized plan forcing in our documentation at https://docs.microsoft.com/sql/relational-databases/performance/optimized-plan-forcing-query-store.

IQP Nextgen with dbcompat 140+

Based on the previous section, you can see that if you upgrade to SQL Server 2022 with any supported dbcompat level, you are eligible to gain performance from these features:
  • Approximate percentile

  • Optimized plan forcing

For example, if you upgraded from SQL Server 2016 and kept the dbcompat level at 130, you would able to take advantage of these features (provided you meet the criteria for each).

But let’s say you upgrade from SQL Server 2017 or 2019 and have dbcompat set at 140 or 150. What additional features can you use to improve performance? First, remember that any IQP features that shipped in 2017 and 2019 are available to you depending on dbcompat 140 or 150.

Note

Approximate distinct was added to SQL Server 2019 independent of dbcompat level. You can read more at https://docs.microsoft.com/sql/t-sql/functions/approx-count-distinct-transact-sql.

In SQL Server 2022, if you are using dbcompat 140 or greater, then you can take advantage of enhancements for memory grant feedback.

In Chapter 2 of SQL Server 2019 Revealed, I provided a detailed background and explanation of memory grant feedback. Let me summarize here the key points I made in that chapter about memory grant feedback.

There are certain operators in a query plan that require their own memory allocation such as hash joins and sorts. When a query is compiled, SQL Server will decide how much memory is needed for these types of operators; this is called a memory grant. I love this detailed blog post by one of our longtime developers Jay Choe on memory grants: https://docs.microsoft.com/en-us/archive/blogs/sqlqueryprocessing/understanding-sql-server-memory-grant. Memory grants are also known as query memory.

A frequent problem I saw in support over the years occurred when the memory grant was not accurate compared with the memory actually required for the operator. This problem could come in two forms:
  • If the memory grant was too small for query execution, then a spill could occur. Effectively, if during query execution the memory grant allocated up front at the start of the query was not enough, the engine would have to use tempdb as a paging file. Even the fastest disk for tempdb is not as fast as if the entire memory required was in RAM. As a result, a spill would result in slower performance of the query.

  • If the memory grant was too large for query execution, other queries could be forced to wait, especially if multiple queries needed memory grants of a certain size. The symptom would be a wait_type = RESOURCE_SEMAPHORE.

Inaccurate memory grants can occur for many reasons. One of the most common is inaccurate cardinality estimations for operators that feed something like a hash join or sort. It could simply be that statistics are out of date.

Like any other problem, our support team would work with customers to provide workarounds. There were many options such as query hints or resource governor settings. But nothing really solved the underlying problem. Users would struggle to do a deep investigation on why the memory grant was not accurate.

In SQL Server 2017, we created a feedback model for memory grants for batch-mode operations (only columnstore at the time) if you used dbcompat 140. In SQL Server 2019, we enhanced the feedback system if dbcompat 150 was enabled to include row-mode operations (but batch mode was also introduced in SQL Server 2019 to be used without columnstore).

Note

Read through the following about batch- and row-mode operations for SQL Server at https://techcommunity.microsoft.com/t5/azure-sql-blog/introducing-batch-mode-on-rowstore/ba-p/386256.

The concept of the feedback system was to evaluate how memory was actually used compared with the initial memory grant at the start of execution. On the next execution, if the memory used was significantly higher than the initial grant, the memory grant would be increased. If memory used was lower, the grant would be decreased.

SQL Server effectively learns from previous executions to make future executions more efficient. This innovation for me personally was groundbreaking. All those support cases over the years where customers struggled with spills or blocking problems could be solved automatically by the engine.

However, we discovered two areas of improvement from what we shipped in SQL Server 2017 and 2019:
  • Volatility

  • We baked into the initial design the ability to disable memory grant feedback if we discovered we were constantly having to adjust the memory grant for the same query over time. Parameter-Sensitive Plans (PSPs) are a notable example of this type of scenario. You could see if we needed to disable memory grant feedback by looking at the IsMemoryGrantFeedbackAdjusted attribute in the query plan and discovering a value of No: Feedback disabled.

  • Persistence

  • The feedback system works for most workloads but unfortunately is not persisted to disk. Therefore, if the server is restarted or the query plan is evicted from cache, all feedback is lost.

Memory Grant Percentiles

To improve the volatility problem with the initial design, memory grant feedback in SQL Server 2022 uses a percentile-based approach instead of disabling memory grant feedback. Rather than using the previous used memory from query execution, we consider a history of previous memory used to adjust a memory grant that is applicable across many query executions.

This approach favors avoiding spills, so it is possible that more memory may be granted than is required. If we use this approach for memory grants, you can observe this in the IsMemoryGrantFeedbackAdjusted attribute in the query plan with a value of Yes: Percentile Adjusting.

This behavior is on by default for SQL Server 2022 if you are using dbcompat 140 or greater. If you want to disable this feature, set the database-scoped configuration option MEMORY_GRANT_FEEDBACK_PERCENTILE to OFF.

Memory Grant Feedback Persistence

If you are using dbcompat 140 or greater with SQL Server 2022 and Query Store is enabled for READ_WRITE, we will persist memory grant feedback details to Query Store. This allows any memory grant feedback to survive server restart or plan cache eviction.

What better way to learn than through an exercise? I’m all about efficiency, so I’ll reuse the example for memory grant feedback I introduced in SQL Server 2019 Revealed in Chapter 2 with a few additions.

A quick comment about this example before you go through it: I’m inducing the conditions where memory grant feedback is needed by forcing a hash join on a query and simulating cardinality problems. But the innovation here is real. This capability is solving real-world problems faced by applications every day that are not worried about whether a hash join is used by the query processor or don’t even know what a memory grant is. That is the intention of IQP. Just make it work!

Prerequisites

The prerequisites for using this example are as follows:
  • SQL Server 2022 Evaluation Edition.

  • Virtual machine or computer with minimum of two CPUs with 8Gb RAM.

  • SQL Server Management Studio (SSMS). The latest 18.x or 19.x build will work.

  • A copy of the scripts from the GitHub repo of this book from the ch4_builtinqueryintelligencepersistedmgf directory.

Follow These Steps for the Exercise

Follow these steps to see memory grant feedback with persistence on SQL Server 2022:
  1. 1.

    Download the WideWorldImportersDW database backup from https://github.com/Microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/WideWorldImportersDW-Full.bak. The restore script in the next steps assumes the backup is located in c:sql_sample_databases. There is a customized backup available with extended data preloaded you can use at https://github.com/microsoft/sqlworkshops-sql2022workshop/releases.

     
  2. 2.
    Restore this database to your SQL Server 2022 instance. Execute the script restorewwidw.sql. You may need to change the directory paths for the location of your backup and where you will restore the database files. This script runs the following T-SQL statements:
    USE master;
    GO
    DROP DATABASE IF EXISTS WideWorldImportersDW;
    GO
    RESTORE DATABASE WideWorldImportersDW FROM DISK = 'c:sql_sample_databaseswideworldimportersdw-full.bak'
    WITH MOVE 'wwi_primary' TO 'c:sql_sample_databaseswideworldimportersdw.mdf',
    MOVE 'wwi_userdata' TO 'c:sql_sample_databaseswideworldimportersdw_userdata.ndf',
    MOVE 'wwi_log' TO 'c:sql_sample_databaseswideworldimportersdw.ldf',
    MOVE 'wwidw_inmemory_data_1' TO 'c:sql_sample_databaseswideworldimportersdw_inmemory_data'
    GO
     
  3. 3.
    In order to run some of the examples, you will need a larger table than what exists by default in WideWorldImportersDW that does not have a columnstore index. Therefore, execute the script extendwwidw.sql to create a larger table. Extending this database will increase its size, including the transaction log, to about 8Gb overall. This script runs the following T-SQL statements:
    USE WideWorldImportersDW;
    GO
    -- Build a new rowmode table called OrderHistory based off of Orders
    --
    DROP TABLE IF EXISTS Fact.OrderHistory;
    GO
    SELECT 'Buliding OrderHistory from Orders...'
    GO
    SELECT [Order Key], [City Key], [Customer Key], [Stock Item Key], [Order Date Key], [Picked Date Key], [Salesperson Key], [Picker Key], [WWI Order ID], [WWI Backorder ID], Description, Package, Quantity, [Unit Price], [Tax Rate], [Total Excluding Tax], [Tax Amount], [Total Including Tax], [Lineage Key]
    INTO Fact.OrderHistory
    FROM Fact.[Order];
    GO
    ALTER TABLE Fact.OrderHistory
    ADD CONSTRAINT PK_Fact_OrderHistory PRIMARY KEY NONCLUSTERED([Order Key] ASC, [Order Date Key] ASC)WITH(DATA_COMPRESSION=PAGE);
    GO
    CREATE INDEX IX_Stock_Item_Key
    ON Fact.OrderHistory([Stock Item Key])
    INCLUDE(Quantity)
    WITH(DATA_COMPRESSION=PAGE);
    GO
    CREATE INDEX IX_OrderHistory_Quantity
    ON Fact.OrderHistory([Quantity])
    INCLUDE([Order Key])
    WITH(DATA_COMPRESSION=PAGE);
    GO
    -- Table should have 231,412 rows
    SELECT 'Number of rows in Fact.OrderHistory = ', COUNT(*) FROM Fact.OrderHistory;
    GO
    SELECT 'Increasing number of rows for OrderHistory...';
    GO
    -- Make the table bigger
    INSERT Fact.OrderHistory([City Key], [Customer Key], [Stock Item Key], [Order Date Key], [Picked Date Key], [Salesperson Key], [Picker Key], [WWI Order ID], [WWI Backorder ID], Description, Package, Quantity, [Unit Price], [Tax Rate], [Total Excluding Tax], [Tax Amount], [Total Including Tax], [Lineage Key])
    SELECT [City Key], [Customer Key], [Stock Item Key], [Order Date Key], [Picked Date Key], [Salesperson Key], [Picker Key], [WWI Order ID], [WWI Backorder ID], Description, Package, Quantity, [Unit Price], [Tax Rate], [Total Excluding Tax], [Tax Amount], [Total Including Tax], [Lineage Key]
    FROM Fact.OrderHistory;
    GO 4
    -- Table should have 3,702,592 rows
    SELECT 'Number of rows in Fact.OrderHistory = ', COUNT(*) FROM Fact.OrderHistory;
    GO
     
  4. 4.
    Set up the demo by executing the script setup.sql. This script runs the following T-SQL statements:
    USE [WideWorldImportersDW];
    GO
    ALTER DATABASE [WideWorldImportersDW] SET COMPATIBILITY_LEVEL = 150;
    GO
    ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
    GO
    ALTER DATABASE WideWorldImportersDW SET QUERY_STORE CLEAR ALL;
    GO

    This script will set the dbcompat to 150 to allow memory grant feedback for row mode to be enabled. Query Store is already enabled for this database, so we simply need to clear the procedure cache (to ensure we get a new fresh compile) and Query Store statistics so we can focus only on this query.

     
  5. 5.
    To easily simulate a problem where a memory grant is not accurate based on incorrect cardinality, execute the script set_stats.sql. This script executes the following T-SQL statements:
    USE WideWorldImportersDW;
    GO
    UPDATE STATISTICS Fact.OrderHistory
    WITH ROWCOUNT = 1000;
    GO
     
  6. 6.
    In a query editor in SSMS, select Query ➤ Include Actual Execution Plan (you can also enable it with Ctrl+M). Then execute the script execute_query.sql, which will take ~30 seconds to complete (the row results are irrelevant to this example). This script executes the following T-SQL statements:
    USE WideWorldImportersDW;
    GO
    SELECT fo.[Order Key], fo.Description, si.[Lead Time Days]
    FROM  Fact.OrderHistory AS fo
    INNER HASH JOIN Dimension.[Stock Item] AS si
    ON fo.[Stock Item Key] = si.[Stock Item Key]
    WHERE fo.[Lineage Key] = 9
    AND si.[Lead Time Days] > 19;
    GO
     
  7. 7.

    Select the Execution Plan tab from the results window. You will see a graphical showplan output that looks like Figure 4-3.

     

A flowchart of the query plan. The flow starts with table scan and clustered index scan, and goes through hash match to end with the select cost of 0%.

Figure 4-3

Query plan with spill

Notice the yellow warning on top of the Hash Match operator. If you move your cursor over this operator, you can see a warning about a spill to tempdb that looks like Figure 4-4.

A screenshot of an overlapping tab titled hash match. The tab has 2 columns and 18 rows. The cursor points to estimated execution mode.

Figure 4-4

Spill warning in the query plan

You can see that the hash join required ~426Mb (52024*8192) of memory and the original grant was only for ~1.4Mb.
  1. 8.

    If you move your cursor over the SELECT operator, you can confirm the memory grant was ~1.4Mb as seen in Figure 4-5.

     

A screenshot of an overlapping tab for select cost. The tab titled select has 2 columns and 7 rows. An arrow points to memory grant 1424 K B from the fifth row.

Figure 4-5

Memory grant for spill

If you right-click the SELECT operator and select Properties, you can expand the MemoryGrantInfo property as shown in Figure 4-6.

A screenshot of a table with 2 columns for memory grant and 1424 K B. Memory grant info is selected in memory grant. An arrow points to no first execution.

Figure 4-6

Memory grant feedback on first execution

IsMemoryGrantFeedbackAdjusted = NoFirstExecution means that the query has been executed for the first time, so no feedback adjustment occurred. Note the MaxUsedMemory value doesn’t reflect the tempdb spill of pages.
  1. 9.
    Since we are using SQL Server 2022, after the execution of this query, feedback is stored in Query Store. You can see this feedback by executing the script get_plan_feedback.sql. This script executes the following T-SQL statements:
    USE WideWorldImportersDW;
    GO
    SELECT qpf.feature_desc, qpf.feedback_data, qpf.state_desc, qt.query_sql_text, (qrs.last_query_max_used_memory * 8192)/1024 as last_query_memory_kb
    FROM sys.query_store_plan_feedback qpf
    JOIN sys.query_store_plan qp
    ON qpf.plan_id = qp.plan_id
    JOIN sys.query_store_query qq
    ON qp.query_id = qq.query_id
    JOIN sys.query_store_query_text qt
    ON qq.query_text_id = qt.query_text_id
    JOIN sys.query_store_runtime_stats qrs
    ON qp.plan_id = qrs.plan_id;
    GO
    Your results should look like the following (I flipped the results vertically so you could see them line up for each column):
    feature_desc      Memory Grant Feedback
    feedback_data
    [{"NodeId":"0","AdditionalMemoryKB":"624504"}]
    state_desc        FEEDBACK_VALID
    query_sql_text
    SELECT fo.[Order Key], fo.Description, si.[Lead Time Days]  FROM  Fact.OrderHistory AS fo  INNER HASH JOIN Dimension.[Stock Item] AS si   ON fo.[Stock Item Key] = si.[Stock Item Key]  WHERE fo.[Lineage Key] = 9  AND si.[Lead Time Days] > 19
    last_query_memory_kb      1424

    The feedback_data column shows the new memory grant that will be used for the next execution of the same query, which should be plenty based on the tempdb spill from the first execution.

     
  2. 10.

    Execute the execute_query.sql script again. You should see the query complete now in just a few seconds.

    If you look at the graphical query plan, you will notice there is no warning for the hash join operator. If you move your cursor over the SELECT operator, you should see a memory grant like Figure 4-7.

     

A screenshot of an overlapping tab for select cost. The tab titled select has 2 columns and 7 rows. An arrow points to memory grant 611 M B from the fifth row.

Figure 4-7

Memory grant after feedback

If you right-click the SELECT operator and select Properties, you can expand the MemoryGrantInfo and see details like Figure 4-8.

A screenshot of a table with 2 columns for memory grant and 611 M B. Memory grant info is selected in memory grant. 2 arrows point to 6 2 5 5 2 8 and yes adjusting in 611 M B.

Figure 4-8

Memory grant feedback adjusted

IsMemoryGrantFeedbackAdjusted = YesAdjusting means that feedback has been applied from the previous execution to adjust the memory grant. No tempdb spill results in a significantly faster execution time.
  1. 11.

    Execute the script get_plan_feedback.sql again. You will see in the results the value for last_query_memory_kb will reflect the new, larger memory grant.

     
  2. 12.
    At this point, the behavior of this scenario is exactly like SQL Server 2019, except previously the memory grant feedback was only stored in the cached query plan. Now you can see that the feedback is persisted in Query Store. Execute the script clear_proc_cache.sql to clear the plan cache. This script uses the following T-SQL statements:
    USE [WideWorldImportersDW];
    GO
    ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
    GO
     
  3. 13.

    Execute the script execute_query.sql again. You will see the query finishes in a few seconds and uses the same memory grant as stored in the feedback_data column in Query Store.

     
You can disable memory grant feedback persistence by using the following database-scoped configuration option:
ALTER DATABASE SCOPED CONFIGURATION SET MEMORY_GRANT_FEEDBACK_PERSISTENCE = OFF

You can also disable memory grant feedback persistence at the query hint level using the option DISABLE_MEMORY_GRANT_FEEDBACK_PERSISTENCE.

Now memory grant feedback will support a wider variety of workloads using percentiles and persist feedback to Query Store to survive server restarts and plan cache eviction.

Keep track of all the latest information about memory grant enhancements for SQL Server 2022 at https://docs.microsoft.com/sql/relational-databases/performance/intelligent-query-processing-details?#percentile-and-persistence-mode-memory-grant-feedback.

The Intelligent Query Processor

In this chapter you explored what built-in query intelligence means. Simply put, it’s faster queries with no code changes. It leverages enhancements to Query Store, with the built-in store for query performance statistics now on by default for new databases and enhancements for hints and read replicas. It is about nextgen IQP capabilities to get you better performance using approximate percentile, optimized plan forcing, and enhancements to memory grant feedback. You can get all of these improvements even without using the new dbcompat level 160 for SQL Server 2022.

I asked Kate Smith, Senior Program Manager over the IQP space, about her thoughts on the significance of built-in query intelligence in SQL Server 2022:

In my opinion, query processing is the beating heart at the center of SQL Server’s capabilities. All queries leverage it, and all benefit from our efforts there. The latest work in Intelligent Query Processing continues this trend – by allowing the optimizer to self-tune to a customer’s specific needs, we expand the scope of what is possible from requiring manual intervention and move towards a self-regulating system. The features released in SQL 22 expand the capabilities in many ways – addressing previous challenges in memory grant feedback, introducing the ability for the optimizer to use different CE models with cardinality estimation feedback, creating parameter sensitive plans, and even adjusting the degree of parallelism for a query without user input. What’s more, these features can all work together, seamlessly. So while before SQL 22 a problematic query might have required multiple iterations of manual tuning, that same query can now be adjusted, on the fly, across many dimensions, without any user action. These adjustments can be persisted across restarts and can apply across different replicas. Taken together, the latest IQP features will greatly reduce headaches for SQL Server DBAs everywhere. I’m thrilled to see these features land, and I’m excited for users to start seeing their benefit. Be warned though: we are not done! This team is always looking to the future, and what more we can deliver. So, stay tuned.

If you are ready for more, the story gets better in the next chapter. If you are ready to move to dbcompat 160, read on to the next chapter about how built-in query intelligence evolves even further with solutions to three age-old problems: Parameter-Sensitive Plans, cardinality estimation model issues, and degree of parallelism.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.165.66