Introduction to SQL Server Execution Plans

Published on: 2019-07-23

Watch this week’s episode on YouTube!

I nearly always use execution plans as the starting point for SQL query performance troubleshooting. In this multi-part series, I plan to review the basics of execution plans and how you can use them to improve the performance of your own queries.

Execution Plans

SQL is a declarative language: instead of programming the details of how data should be retrieved, we describe what data we want and SQL Server figures out how it should return it to us.

For most queries that we want to performance tune, the SQL Server Query Optimizer considers multiple options for returning the data. It estimates the costs of these various approaches and lands on one that it thinks will return the data in an efficient manner. All of these options for retrieving the data are what are known as query execution plans.

Most of the time, this process works well and the Query Optimizer ends up choosing a query plan that returns the requested data quickly. This however does not mean that SQL Server chooses the “most efficient” query plan. Besides debating what “most efficient” means in different scenarios, SQL Server doesn’t want to spend hours calculating every possible way to return your data when it only takes a fraction of that time to find a plan that is “good enough”.

Problems arise when the Query Optimizer selects a query plan that isn’t very good at all. This can happen due to a number of issues that we’ll look at in future parts of this series.

Today we’ll focus on learning how we can obtain execution plans for our queries.

Viewing Execution Plans

To see an execution plan for your query, you can run SET SHOWPLAN_ALL ON. This will provide a text-based tree representation of the plan:

SET SHOWPLAN_ALL ON
GO
--Query
GO
SET SHOWPLAN_ALL OFF
GO
Text-based tree query plan

This may look familiar to you if you are used to looking at plans in other relational database environment that use similar tree-based plan explanations. In SSMS we have a a more visual option available to though. If we click the “Display Estimated Execution Plan” button or run SET SHOWPLAN_XML ON and execute our query, we will get our graphical execution plan:

the Display Estimated Execution Plan button
Graphical estimated execution plan

This graphical representation is my personal preference (and what we will be mostly focusing on in this series) but it’s important to know that as hinted by the last command, you can also right click on this graphical execution plan and choose “Show Execution Plan XML” to see the XML that is driving all of the visuals:

Show execution plan XML
XML execution plan

While most people don’t find the XML as easy to digest as the graphical execution plan, it’s worth knowing it is there: sometimes you will have to dive into the XML to find properties that don’t get displayed in the graphical version.

Actual Plans

So far, every plan we’ve looked at is what’s known as an “estimated” execution plan. The “estimated” name means it only contains “estimates” of how many rows will be processed based on internal meta data that SQL Server has available. You can view the “actual” execution plan by selecting the appropriate icon in SSMS:

Actual execution plan button
Actual execution plan

There often confusion that occurs due to the naming of “estimated” vs “actual” execution plans. The difference is that the estimated plan is calculated before executing the SQL statement so it only has estimated meta data available for it to display, whereas the actual execution plan is that same estimated execution plan overlayed with runtime information like how many rows were processed, how much memory was used, etc…

Live Query Statistics

Live Query statistics give you the best of both estimated and actual execution plans. With Live Query Statistics enabled, SQL Server provides the estimated execution plan but overlays live runtime statistics on top of the plan as the query is executing in real-time.

Live query statistics button
Live query statistics running

Live Query Statistics are nice because they allow you to often see “where” in the execution plan a query is experiencing a performance bottleneck. This is particularly helpful if you are new to analyzing execution plans and haven’t yet learned all of the common signs and operators that might indicate poor performance. It is also helpful when your query is performing so poorly that you are unable to retrieve an actual plan for it (since it executes for what seems like forever).

Historical Plans

Calculating query plans isn’t free, so SQL Server caches query plans for reuse. These cached plans can be viewed in the sys.dm_exec_query_plan DMV:

SELECT 
    *
FROM 
    sys.dm_exec_cached_plans      
    CROSS APPLY sys.dm_exec_query_plan(plan_handle)
    CROSS APPLY  sys.dm_exec_sql_text(plan_handle) 
Cached query plans

It’s worth noting that this set of DMVs only show plans that are still in the cache and do not show actual plan statistics in them (plus some other limitations).

If you have Query Store enabled on your database, you can also access the query plans stored in the Query Store DMVs (or via the Query Store GUI):

SELECT 
    CAST(p.query_plan AS XML), 
    *
FROM 
    sys.query_store_query AS q
    INNER JOIN sys.query_store_plan AS p
        ON q.query_id = p.query_id

Conclusion

Regardless of how and where you decide to retrieve your execution plan from, all of the above techniques will help provide insight into how SQL Server is obtaining the data that you specified in your query.

Thanks for reading. You might also enjoy following me on Twitter.

Want to learn even more SQL?

Sign up for my newsletter to receive weekly SQL tips!

How I Continuously Learn About SQL Server

Published on: 2019-07-16

Watch this week’s episode on YouTube.

In order to stay current in a technology you have to immerse yourself in community content.

Documentation is good for knowing the specification of a language or tool and books or training courses are great for when you want to dive deep on a particular topic. However in many technology fields, including SQL Server, one of the best ways to stay up to date is by following blogs.

Not only are blogs posts typically showing newer features and techniques that haven’t been able to make their way into books and training courses yet, they often will cover more specialized scenarios and edge cases than you’ll encounter in the traditional learning sources. Following a broad user base of SQL Server professionals who blog solutions to problems they encounter and will often open your eyes to features of the tools you use everyday that you weren’t even aware of.

This is especially nice for times when I’m not actively learning a specific topic. Reading a variety of blogs on various SQL Server subjects often reminds me how much I don’t know and how many features I don’t use during my day-to-day work. These blogs also serve as inspiration to investigate ideas further.

RSS Feeds

My favorite way to follow other SQL Server blogs is by subscribing to RSS feeds. I use Feedly as my RSS feed reader of choice (RIP Google Reader) and browse through all of my feeds whenever I have downtime. Inspired by Brent Ozar’s post last year, I’m sharing all of the RSS feeds I follow in this OPML file.

You can download the OPML file and import it into your RSS reader as a starting point, or go through the list of websites and see if you find something new. I decided to also include my non-SQL feeds in the above link too (they are categorized in appropriate sections) in case you are curious about what sites I follow for other programming and career topics.

It’s impossible to stay up to date with all the information that I want to, but staying up to date on RSS feeds gets me close.

I’m always looking for more sources – RSS is so easy to consume – so if you have any suggestions please let me know!

And if you want to save the click-through to GitHub, here are all of the sources embedded:

Thanks for reading. You might also enjoy following me on Twitter.

Want to learn even more SQL?

Sign up for my newsletter to receive weekly SQL tips!

SELECT Expression Execution Order

Published on: 2019-07-09

Watch this week’s episode on YouTube!

Today I want to share with you an interesting observation I made about SELECT expression execution order.

I was working on writing a dynamic SQL query that would transform the following piece of JSON:

{"data":[["a","b","c"],["d","e","f"]]}

Into a query that looked like this:

SELECT 'a' AS Row0Column0, 'b' AS Row0Column1, 'c' AS Row0Column2 
UNION ALL
SELECT 'd' AS Row1Column0, 'e' AS Row1Column1, 'f' AS Row1Column2 

Normally I would use something like OPENJSON and PIVOT to transform the original data into a table result set, but in this instance I my requirements dictated that I needed to build the code as a series of SELECT and UNION ALL statements.

The first step in building this query was using OPENJSON to parse the JSON data into rows and value arrays:

SELECT
	*
FROM
(
SELECT
	rows.[key] AS RowNumber,
	rows.[Value] AS RowArray
FROM 
	OPENJSON(N'{"data":[["a","b","c"],["d","e","f"]]}','$.data') rows
) r
CROSS APPLY OPENJSON(r.RowArray) v
ORDER BY
	r.RowNumber,
	v.[key]
Results of OPENJSON

This first query was a good start. I then added a variable @RowQuery and started building my dynamic SQL code to generate my SELECT and UNION ALL statements:

DECLARE 
	@RowQuery varchar(max)

/* TOP is here to get the ORDER BY to work as expected */
SELECT TOP 134960239460263
	@RowQuery =  COALESCE (@RowQuery + ' ','') + '''' + v.[value] + ''' as [Row'+r.RowNumber+'Column'+v.[key]+']'
FROM
(
SELECT
	rows.[key] AS RowNumber,
	rows.[Value] AS RowArray
FROM 
	OPENJSON(N'{"data":[["a","b","c"],["d","e","f"]]}','$.data') rows
) r
CROSS APPLY OPENJSON(r.RowArray) v
ORDER BY
	r.RowNumber,
	v.[key]

PRINT 'SELECT ' + @RowQuery;
Printed results of @RowQuery

At this point I had the row/column numbering correct, but I still needed to add a UNION ALL SELECT before the start of each row.

I thought, “Oh, this is easy. Since the dynamic SQL I’m building is basically a loop, I need to check for a change in the RowNumber column’s value to identify I’m on a new row. If I am, I can insert the UNION ALL SELECT text and I’ll be all set”:

DECLARE 
	@RowQuery varchar(max),
	@CurrentRow int = 0;

/* TOP is here to get the ORDER BY to work as expected */
SELECT TOP 134960239460263
	@RowQuery =  COALESCE (@RowQuery + '','') + IIF(r.RowNumber > @CurrentRow, CHAR(10)+'UNION ALL'+CHAR(10)+'SELECT ', ', ')+'''' + v.[value] + ''' as [Row'+r.RowNumber+'Column'+v.[key]+']',
	@CurrentRow = IIF(r.RowNumber > @CurrentRow, r.RowNumber, @CurrentRow)
FROM
(
SELECT
	rows.[key] AS RowNumber,
	rows.[Value] AS RowArray
FROM 
	OPENJSON(N'{"data":[["a","b","c"],["d","e","f"]]}','$.data') rows
) r
CROSS APPLY OPENJSON(r.RowArray) v
ORDER BY
	r.RowNumber,
	v.[key]

/*remove the first comma and add an initial SELECT */
PRINT STUFF(@RowQuery,1,1,'SELECT'); 
Final results of building a SELECT UNION ALL query

Success! But as I was celebrating my dynamic SQL victory, I realized I was making an assumption about SQL Server that I had never thought about before:

The above query only works because SQL Server is executing the variables in the SELECT list sequentially. I’m incrementing @CurrentRow only after processing my @RowQuery variable, and this logic only works correctly if SQL Server executes the variable expressions in the order they appear in the SELECT list. If SQL Server was executing items in the SELECT list in reverse or random order, @CurrentRow could potentially get set BEFORE @RowQuery was evaluated, causing the logic of adding “UNION ALL SELECT” in the right location to fail.

This surprised me because I don’t usually think about the column execution order of a query. Normally column expressions in the SELECT statement are independent of each other so the order that the columns are executed in doesn’t really matter. But in this example, the column execution order does matter and it’s reassuring to see SQL Server do what I assumed it was doing.

Now, I can’t guarantee this always works. I tried but failed to think of a scenario where SQL Server wouldn’t execute the columns in sequential order. While the query seemed to work as expected in all of the tests I ran, I’ll leave this observation open ended in case anyone has ever encountered a scenario or has any ideas of when SQL Server doesn’t process SELECT statement expressions in the order they are listed.

Thanks for reading. You might also enjoy following me on Twitter.

Want to learn even more SQL?

Sign up for my newsletter to receive weekly SQL tips!