Getting started with Query Store feature in SQL Server 2016 – Part 3

Introduction

Query Store is a new feature in SQL Server 2016, which once enabled, automatically captures a history of queries, execution plans, and runtime statistics, retaining them for your troubleshooting performance problems caused by query plan changes. This new feature greatly simplifies performance troubleshooting by helping you quickly find performance differences, even after server restart or upgrade.

In “Getting Started with the Query Store Feature in SQL Server 2016 – Part 1” I talked about this new feature and its advantages, then explained some of the scenarios where you can use it. Finally, I talked about data capture processing, including what gets captured, and how it is retrieved when you enable Query Store feature. In “Getting started with Query Store feature in SQL Server 2016 – Part 2” I explained query execution flow when using Query Store and how it differs from regular query execution flow, and I talked about typical performance troubleshooting workflow with Query Store and how to get started with it. In this third article of the series, I am going to demonstrate how you can analyze the collected data by Query Store either with T-SQL scripting or with the graphical user interface in SSMS with built-in reports\dashboards.

Query Store – Analyzing Collected Data with T-SQL

With SQL Server 2016 RC0, there are seven new dynamic management catalog views and 6 new extended stored procedures to work with Query Store. To learn more about each of these catalog views refer to the MSDN article, “Query Store Catalog Views (Transact-SQL)” and for stored procedures refer to “Query Store Stored Procedures (Transact-SQL)”.

Query Store
Query Store

Based on these views, I am providing you some of the pre-written scripts (which of course you can modify based on your specific need) which you can use for some common scenario. For example, the first query gives you the last ten recent queries that were executed in the current database; likewise, the second query gives the count of executions for each query ranked by number of executions in descending order. For each of these queries, I have put comments prior to it so that you can easily identify and make use of it:

--Query last 10 recent queries that were executed 
--in the current database
SELECT
       TOP 10 qt.query_sql_text, q.query_id, qt.query_text_id, 
       p.plan_id, rs.last_execution_time
FROM sys.query_store_query_text   qt 
JOIN sys.query_store_query   q ON qt.query_text_id   = q.query_text_id   
JOIN sys.query_store_plan   p ON q.query_id   = p.query_id 
JOIN sys.query_store_runtime_stats   rs ON p.plan_id   = rs.plan_id
ORDER BY rs.last_execution_time   DESC
 
--Query to get count of executions for each query
--ranked by number of executions in descending
SELECT
       q.query_id, qt.query_text_id, 
       qt.query_sql_text, 
       SUM(rs.count_executions) AS total_execution_count
FROM sys.query_store_query_text   qt 
JOIN sys.query_store_query   q ON qt.query_text_id   = q.query_text_id   
JOIN sys.query_store_plan   p ON q.query_id   = p.query_id 
JOIN sys.query_store_runtime_stats   rs ON p.plan_id   = rs.plan_id
GROUP BY q.query_id, qt.query_text_id, qt.query_sql_text
ORDER BY total_execution_count DESC
 
--Query to get 10 queries with the longest average 
--execution time within last one hour, 
--ranked by query duration
SELECT
       TOP 10 qt.query_sql_text, 
       q.query_id, qt.query_text_id, p.plan_id, 
       GETUTCDATE() AS CurrentUTCTime, 
       rs.last_execution_time,   rs.avg_duration
FROM sys.query_store_query_text   qt 
JOIN sys.query_store_query   q ON qt.query_text_id   = q.query_text_id   
JOIN sys.query_store_plan   p ON q.query_id   = p.query_id 
JOIN sys.query_store_runtime_stats   rs ON p.plan_id   = rs.plan_id
WHERE   rs.last_execution_time > DATEADD(HOUR, -1, GETUTCDATE())
ORDER BY rs.avg_duration desc
 
--Query to get 10 queries that had the biggest 
--average physical IO reads in last 24 hours, 
--ranked by IO cost
SELECT
       TOP 10 qt.query_sql_text, 
       q.query_id, qt.query_text_id, 
       p.plan_id, rs.runtime_stats_id, 
       rsi.start_time, rsi.end_time, 
       rs.avg_physical_io_reads,   
       rs.avg_rowcount, rs.count_executions
FROM sys.query_store_query_text   qt 
JOIN sys.query_store_query   q ON qt.query_text_id   = q.query_text_id   
JOIN sys.query_store_plan   p ON q.query_id   = p.query_id 
JOIN sys.query_store_runtime_stats   rs ON p.plan_id   = rs.plan_id 
JOIN sys.query_store_runtime_stats_interval   rsi 
ON rsi.runtime_stats_interval_id =   rs.runtime_stats_interval_id
WHERE   rsi.start_time >=   DATEADD(HOUR, -24, GETUTCDATE()) 
ORDER BY rs.avg_physical_io_reads   desc
 
--Query to get queries that recently got regressed in the 
--performance (execution time got doubled within last 48 hours)
SELECT
       qt.query_sql_text, 
       q.query_id, 
       p1.plan_id AS plan1, 
       rs2.avg_duration AS   plan2
FROM sys.query_store_query_text   qt 
JOIN sys.query_store_query   q ON qt.query_text_id   = q.query_text_id   
JOIN sys.query_store_plan   p1 ON q.query_id   = p1.query_id   
JOIN sys.query_store_runtime_stats   rs1 ON p1.plan_id   = rs1.plan_id   
JOIN sys.query_store_runtime_stats_interval   rsi1 ON rsi1.runtime_stats_interval_id   = rs1.runtime_stats_interval_id   
JOIN sys.query_store_plan   p2 ON q.query_id   = p2.query_id   
JOIN sys.query_store_runtime_stats   rs2 ON p2.plan_id   = rs2.plan_id   
JOIN sys.query_store_runtime_stats_interval   rsi2 ON rsi2.runtime_stats_interval_id   = rs2.runtime_stats_interval_id
WHERE   rsi1.start_time >   DATEADD(HOUR, -48, GETUTCDATE()) AND
rsi2.start_time >   rsi1.start_time AND
rs2.avg_duration >   2*rs1.avg_duration
 
--Query to get queries with multiple plans
WITH   QueryWithMultiplePlans 
AS
(
       SELECT COUNT(*) AS cnt, q.query_id 
       FROM sys.query_store_query_text qt 
       JOIN sys.query_store_query q ON   qt.query_text_id =   q.query_text_id 
       JOIN sys.query_store_plan p ON   p.query_id =   q.query_id 
       GROUP BY q.query_id HAVING COUNT(DISTINCT plan_id) > 1 
)
SELECT   q.query_id, OBJECT_NAME(object_id) AS ContainingObject,   
query_sql_text, plan_id, p.query_plan AS   plan_xml, p.last_compile_start_time, 
p.last_execution_time 
FROM QueryWithMultiplePlans   qm 
JOIN sys.query_store_query   q ON qm.query_id   = q.query_id 
JOIN sys.query_store_plan   p ON q.query_id   = p.query_id 
JOIN sys.query_store_query_text   qt ON qt.query_text_id   = q.query_text_id   
ORDER BY query_id,   plan_id

Query Store – Analyzing Collected Data within SSMS

As mentioned earlier, T-SQL scripting is not the only way to look into data captured by Query Store. SQL Server 2016 includes several inbuilt reports and dashboards to quickly analyze and get insight in to the captured data. These reports and dashboards also allow you take actions like comparing two plans, forcing plans, etc.

To explore the data captured by Query Store, you can expand the database for the Query Store feature that has been enabled in the Object Explorer of SSMS and expand the Query Store node as shown in the figure below:

Expand the Query Store Node
Expand the Query Store Node

As you can see in the figure above, there are four dashboards/reports available to make use of immediately. For example, the dashboard displayed below (one of the above shown dashboards) shows the last 25 queries that are most expensive in terms of resource consumptions in the last hour. As I mentioned earlier, Query Store captures different metrics (Duration, CPU Time, Logical Reads, Logical Writes, Physical Reads and Memory Consumption) with different statistics (Total, Average, Maximum, Minimum and Standard Deviation) and you refresh the view for each of the metrics in terms of its highest consumers. Also, you can hover your mouse over a query in the top pane with bar chart and get more details about the query ID, average duration, number of execution counts, and number of plans generated, etc.

As you can see in the other dashboard below, it allows you to compare different plans for a query, and allows you to force a plan if you think that plan is more appropriate. You can unforce a plan for the query that has an earlier forced plan (if you want to later allow SQL Server to choose between recent plans then forced ones, you will need to click on the Unforce Plan button to the right of the disabled Force Plan button as shown in the figure below).

TIP: SQL Server Query Optimizer can generate multiple execution plans over time for a given query, which also gets retained in the Query Store (assuming you have enabled it for the database), in that case you can enforce policies to direct the SQL Server Query Processor to use a specific execution plan for that query (you do it normally in case you conclude that a specific plan performs better than others). This is referred to as plan forcing. This was possible earlier also, but you were limited to making use of execution plans available in the current plan cache only; with Query Store these execution plans are persisted and hence you can still get access to it even after a restart.

Top 25 Resource Consumers During the Last Hour
Top 25 Resource Consumers During the Last Hour

The next dashboard shows overall resource consumption for the last month for the selected database. 

Overall Resources Consumption During the Last Month
Overall Resources Consumption During the Last Month

There are two more dashboards. The Regressed Queries dashboard shows the top 25 regressed queries in the last hour; the Tracked Queries report allows you to specify a Query ID and find out details of it.

TIP: If SQL Server Query Optimizer generates a new plan for a query, which performs worse than the previous execution plans for the same query, then the query is said to be a “Regressed Query”.

All these dashboards show the result for standard default parameters, but if needed you can click on the Configure button in the top right corner of the dashboard to change different configurations related to metrics, statistics, durations, etc., as shown below and refresh your dashboard by clicking on the Refresh button:

Configure Top Resource Consumers
Configure Top Resource Consumers

Note: The feature mentioned and demonstrated in this article is based on SQL Server 2016 Release Candidate 0 and might change when RTM is available or in future releases.

Conclusion

Query Store is a new feature in SQL Server 2016, which once enabled automatically captures a history of queries, execution plans, and runtime statistics, retaining them for your troubleshooting performance problems caused by query plan changes. This new feature greatly simplifies performance troubleshooting by helping you quickly find performance differences, even after a server restart or upgrade.

In this article I demonstrated how you can analyze the collected data by Query Store either with T-SQL scripting or with the graphical user interface in SSMS with built-in reports\dashboards.

Resources

Getting started with Query Store feature in SQL Server 2016 – Part 1

Getting started with Query Store feature in SQL Server 2016 – Part 2

What’s New in Database Engine

Monitoring Performance By Using the Query Store

Best Practice with the Query Store

See all articles by Arshad Ali

Arshad Ali
Arshad Ali
Arshad Ali works with Microsoft India R&D Pvt Ltd. He has 8+ years of experience, mostly on Microsoft Technologies. Most recently, as a SQL Developer and BI Developer he has been working on a Data Warehousing project. Arshad is an MCSD, MCITP: Business Intelligence, MCITP: Database Developer 2008 and MCITP: Database Administrator 2008 certified and has presented at several technical events including SQL-School. On an educational front, he has an MCA (Master in Computer Applications) and MBA-IT. Disclaimer : I work for Microsoft and help people and businesses make better use of technology to realize their full potential. The opinions mentioned herein are solely mine and do not reflect those of my current employer or previous employers.

Get the Free Newsletter!

Subscribe to Cloud Insider for top news, trends & analysis

Latest Articles