This question came up in a webcast recently: if a filtered index is causing data modifications to fail, does disabling the filtered index fix the problem?
I wasn’t 100% sure — I couldn’t remember the last time I’d tried, if I ever had. So….
Let’s test it!
First, let’s reproduce the problem: we’ll create a filtered index on a table, then set up a session who can’t read from it due to an ANSI_SETTINGS conflict. (A list of required SET OPTIONS for filtered indexes is documented here.)
CREATE INDEX ix_SalesOrders_filtered_IsUndersupplyBackordered_OrderDate_INCLUDES ON
Sales.Orders ( OrderDate )
INCLUDE ( OrderID, CustomerID, SalespersonPersonID, PickedByPersonID, ContactPersonID, BackorderOrderID, ExpectedDeliveryDate, CustomerPurchaseOrderNumber, IsUndersupplyBackordered, Comments, DeliveryInstructions, InternalComments, PickingCompletedWhen, LastEditedBy, LastEditedWhen)
WHERE (IsUndersupplyBackordered = 0);
Now, let’s make our session incompatible with the index
All I have to do to make an insert fail is violate one of those required ANSI settings in my session.
Here we go!
SET ANSI_NULLS OFF;
INSERT Sales.Orders ( [CustomerID], [SalespersonPersonID], [PickedByPersonID], [ContactPersonID], [BackorderOrderID], [OrderDate], [ExpectedDeliveryDate], [CustomerPurchaseOrderNumber], [IsUndersupplyBackordered], [Comments], [DeliveryInstructions], [InternalComments], [PickingCompletedWhen], [LastEditedBy], [LastEditedWhen])
SELECT TOP (1) [CustomerID], [SalespersonPersonID], [PickedByPersonID], [ContactPersonID], [BackorderOrderID], [OrderDate], [ExpectedDeliveryDate], [CustomerPurchaseOrderNumber], [IsUndersupplyBackordered], [Comments], [DeliveryInstructions], [InternalComments], [PickingCompletedWhen], [LastEditedBy], [LastEditedWhen]
This results in the Error 1934
Msg 1934, Level 16, State 1, Line 25
INSERT failed because the following SET options have incorrect settings: 'ANSI_NULLS'. Verify that SET options are correct for use with indexed views and/or indexes on computed columns and/or filtered indexes and/or query notifications and/or XML data type methods and/or spatial index operations.
OK! Now to disable the filtered index
I suck all the pages out of the filtered index, leaving only the metadata behind:
ALTER INDEX ix_SalesOrders_filtered_IsUndersupplyBackordered_OrderDate_INCLUDES ON
And now, when I retry my insert….
(1 row affected)
It worked! As long as this filtered index is disabled, the insert works again for the session with the incompatible ANSI_NULLS setting.
Personally, I’d rather drop the filtered index
Rebuilding the filtered index will cause this problem to happen again — until all sessions modifying the table have the required se ssion settings in place.
So to prevent actual rebuilds, I’d rather drop the index until everything is in order for the index to be recreated.
In general SQL Server only auto parameterizes your SQL statements if you deal with a so-called Safe Execution Plan: regardless of the provided input parameter values, the query must always lead to the same execution plan.
There are times when SQL Server just doesn’t think it’s safe to simply parameterize your query, and that can be SUPER confusing when you’re looking at queries.
Here’s what simple parameterization looks like when it works
I run two these two statements against the BabbyNames database…
SELECT FirstName FROM ref.FirstName where FirstNameId=76682;
SELECT FirstName FROM ref.FirstName where FirstNameId=86055;
In the actual execution plan, I see that the literal FirstNameId values have been replaced with @1.
Further, in the properties of the leftmost operator on the plan, I see that StatementParameterization type = 2.
In Query Store and the plan cache, the text for my query gets recorded as:
(@1 int)SELECT [FirstName] FROM [ref].[FirstName] WHERE [FirstNameId][email protected]
That single plan is show as having two executions. In other words, it’s re-used.
Simple parameterization sometimes doesn’t happen… but looks kinda like it did in your execution plan
Let’s change our query a little:
SELECT FirstNameId FROM ref.FirstName where FirstName='Grant';
SELECT FirstNameId FROM ref.FirstName where FirstName='Kendra';
When I run these new queries, here’s what my actual execution plan looks like:
Hmmmm… this is different! I’ve got the @1 again in my query text, and it’s even in the parameter list on the left.
But notice that this time, StatementParameterizationType is 0.
Last time, that was set to 2!
This didn’t really get parameterized
If I look in Query Store and my query plan cache, I find two queries and two plans.
The queries are formatted differently, they look like this:
SELECT FirstNameId FROM ref.FirstName where FirstName='Grant'
SELECT FirstNameId FROM ref.FirstName where FirstName='Kendra'
The parameter didn’t make it in there at all.
TLDR: Sometimes, it looks like something has been simple parameterized in an actual execution plan, but it hasn’t!
Just because I see an @1 being substituted in for a literal value in that text hint at the top of a plan doesn’t mean that simple parameterization has actually happened.
And for the record, I’m not sure that this is the exact scenario Grant was asking about, because my second example here isn’t technically a ‘trivial’ plan. But I do think that this is one of many reasons to use explicit parameterization practices, and not to rely on simple or auto-parameterization, because it just doesn’t work in many cases.
Session 40 shows no sql_text, but it appears when I run the QUERY_STORE CLEAN all command, and disappears when I cancel it. It also shows not “sql_command” if I run sp_WhoIsActive with @get_outer_command=1, and it shows no lock information if I use @get_locks=1.
It’s clearly getting locks, because it’s blocking its little friend, session 74, but it’s just not showing what it’s doing.
Meanwhile, session 74 is waiting on a lock on sys.sysschobjs
Although we can’t see the locks directly on session 40, I can see that session 74 (who is blocked by session 40, who is in turn locked by the alter table) is waiting for a shared key lock on the cost index on the sys.sysschobjs system table.
Sys.sysschobjs is documented, it “Exists in every database. Each row represents an object in the database.”
If I connect to the Dedicated Admin connection, I can query the sys.syssschobjs table – and I can read it if I allow dirty reads (seeing the uncommitted data):
select * from sys.sysschobjs (NOLOCK)
where name = 'FirstName';
There is a modified date on this table (column name modified), which was updated around when I started the modification on the table.
I don’t think this is a bad thing – I’m writing this post just to document it
One cool thing in Query Store is that it has some knowledge of the objects in a database. It will tell you if a query is part of a procedure, for example.
That’s a good thing, but it is probably one of the reasons that Query Store reads from the same system tables that may be locked if we modify the schema of objects.
Hopefully, most folks don’t:
Have long running transactions that modify objects in production
Clear out Query Store data often in production (it’s useful information, and the basis for some cool features)
If you are unfortunate enough to have BOTH of these patterns, you may run into blocking.
This could also occur if you are trying to clear out Query Store when an offline index rebuild is running.
I checked, and sure enough, my little cloud VM was on SQL Server 2017 CU3, and that fix is in CU4.
I patched to CU5 (latest and greatest for my little test instance, ya know), redid all my testing twice, and admired my now-more-consistent charts.
The second weird thing: inconsistent query duration
I was getting consistent results for query duration when I ran the query in the script above, which started at a high degree of parallelism, and then went to low.
But I found that for one of my test queries, when I took the query and ran it in my own session to look at Query Time Stats in the execution plan, it took much longer.
For example, at MAXDOP 13:
Duration was consistently 55-65 seconds in the looping script
Running it in a session by itself, I was seeing durations of 2 minutes to 2 minutes 30 seconds
More than twice as long.
Why was it so much slower when I ran it in another session?
It wasn’t about cache/buffer pool reuse
I had plenty of memory on this instance, and I’d made sure to set ‘max server memory (mb)’ plenty high, but could something be causing me to do physical reads when I ran the query in my own session?
This made me realize that my test script wasn’t quite fair to the DOP that ran first — it might have to do physical reads where following queries made use of data in memory. This is a private test instance, so I changed the script to run everything with cold cache, by dropping clean buffers between runs.
I wasn’t getting a different query execution plan
I was getting the same plan with the same cost both in my “slow session” and in the script.
It wasn’t ASYNC_NETWORK_IO_WAITS
I was running everything from an SSMS instance on my laptop against a SQL Server on a VM in the Azure Cloud. Could I be getting some weird kind of latency on one session?
Nope. And my query wasn’t even returning a result set to my SSMS window — it was putting the results into a table in the cloud.
Was it the Dynamic SQL?
Could something about the Dynamic SQL be making it faster? Or had I made some sort of typo and the Dynamic SQL query was a little bit different in a way that made it faster?
Nope, it really was the same query, and the same plan. When I copied the longer script and adjusted the variables to only run for DOP 13 in my “slow” session, it was also slow.
Was it using STATISTICS TIME or STATISTICS IO?
I sometimes use these to measure queries, but in this case I wasn’t using them in either session.
Was it having Actual Execution Plans on?
Yes, it was.
This query was a reminder of the observer effect: watching something can change its behavior. In this case, “watching” the query by enabling actual execution plans made it take twice the duration.
I had actual plans enabled in my “slow” session, and I didn’t have them on in the session where I was measuring duration and wait statistics in the loop. (If you’ve ever accidentally left actual plans enabled when you’re running TSQL in a loop, you know how much SSMS doesn’t love that.)
I tested this a few different ways, including in another instance of SSMS local to the VM itself, and sure enough – actual plan drags this puppy down, although it’s a relatively simple query.
Was it only graphical plans?
I disabled graphical execution plans in Management Studio, then tested a few varieties of outputting actual plan info:
SET STATISTICS XML ON – Actual plan information output as XML in a column named “Microsoft SQL Server 2005 XML Showplan”
SET STATISTICS PROFILE ON – Actual plan information output as text across a variety of columns (screenshot below)
I tested these independently. Both of these had the same impact on the query as using the graphic execution plan – the query took twice as long.
I did feel kind of fancy looking at actual plan info in text format, though. We all know that hackers ALWAYS use text interfaces.
What about ‘Live Query Statistics’?
I expected this to take a full three minutes, but it only took a bit longer than the ‘Actual Plan’ run in this case: 2 minutes 5 seconds.
Does this happen against different instances? At different DOPs?
I ran the same query against the same data on a local test VM on my MacBook Pro, which has 4 cores.
The duration at MAXDOP 4 on the MacBook Pro:
No actual execution plans = 2:41
Actual execution plans enabled = 2:50
Hmm, the discrepancy is much smaller on the small instance.
So I retested at MAXDOP 4 on the cloud VM. Note that this gets a different plan – similar shapes, but different costs (much more memory on that instance).
No actual execution plans = 2:20
Actual execution plans enabled = 3:07
To recap what I mentioned above, MAXDOP 13 on the cloud VM:
No actual execution plans = 0:55
Actual execution plans enabled = 2:00
Just for fun, MAXDOP 16 on the cloud VM (just in case you thought the issue was 13 being unlucky)
No actual execution plans = 1:00
Actual execution plans enabled = 2:14
Did I mention I’m compulsive about investigating this? MAXDOP 20 on the cloud VM (all its cores):
No actual execution plans = 1:12
Actual execution plans enabled = 2:10
Innnnnteresting. The impact of having actual plans enabled for this query is more pronounced on the cloud VM. The skew is greater at a higher DOP, although it’s not linear.
Actual plans are awesome, but make sure they don’t confuse your tuning process by secretly skewing your execution times!
There’s always overhead to using an actual plan.
Frequently it’s minimal, but sometimes it’s really significant – and the amount it varies may differ on different hardware, and at different degrees of parallelism.
It’s tough to keep track of which features work in each version of SQL Server, and which Editions support them.
My memory told me that the new Adaptive Joins feature in SQL Server 2017 was Enterprise Edition only… and that’s correct, but I didn’t realize that the fancy new feature to make Multi-Statement TVFs smarter has much wider licensing.
Interleaved Execution for Multi-Statement Table Valued Functions works in Enterprise, Standard, Web, and Express Editions
I discovered this by accident when I was doing some testing on my Standard Edition instance. I was looking at an execution plan, and I checked the properties of a TVF and …
First, I double-checked and made sure I really was connected to my Standard Edition instance. YEP.
Sure enough, ‘Batch Mode Adaptive Joins’ and ‘Batch Mode Memory Grant Feedback’ are Enterprise Edition features.
But ‘Interleaved Execution for Multi-Statement Table Valued Functions’ is available in all the editions.
What does ‘Interleaved Execution for Multi-Statement TVFs’ do for you?
Multi-statement table valued functions are difficult to optimize as part of a query: they run multiple statements, so how the heck do you estimate how many rows are going to come out of that?
This new feature lets the optimizer stop and take a peek!
When the optimizer finds a multi-statement TVF that qualifies*, it pauses the optimization process (badass!), and then goes briefly into the execution phase.
(This is the interleaving part.) It can run through that little part of the plan to get a good estimate, instead of making a blind guess.
Good estimates means a much better chance that you’ll get the right joins, memory grant, and qualify for parallelism when needed.
That usually adds up to faster query execution.
*Which Multi-Statement TVFs qualify?
Not all multi-statement TVFs are created equal. If your TVF is part of a data modification, or if your TVF is inside a CROSS APPLY, it doesn’t qualify for this magic — at least not in SQL Server 2017. That may change in the future, this is just the V1.
How do I turn this on?
If you’re on SQL Server 2017, your database compatibility level controls whether or not this feature is available.
If your compat mode is 140, your multi-statement table valued functions are very likely to speed up.
I say ‘very likely’ because there can be some edge cases where better estimates lead to worse performance.
Want to learn more?
If you agree with Function Beaver that this is the best thing since CREATE FUNCTION…
The new Enterprise Automatic Tuning feature in SQL Server 2017 may sound intimidating at first — one question I get a lot lately is whether or not there’s a future for DBAs. Will Auto-Tune mean we don’t need any more human tuning?
Well, not anytime super soon.
I’m a big fan of the Automatic Plan Correction feature in SQL Server 2017, but it reminds me a bit of the missing index DMVs we got in SQL Server 2005: the suggestions are a fantastic indication of where you need to look and use your powerful human brain.
Automatic plan correction is temporary — and that’s a good thing
One of the things I love about this feature is that it wisely includes some caution.
If you’ve enabled automatic tuning and it finds what looks like a plan regression, it won’t just force a plan and forget about it.
Let’s say we have a query where the average CPU time with Plan A is 2 seconds, and the average CPU time for Plan B is 10 seconds. If Automatic Plan Correction kicks in, we go into a state where Plan A is being verified.
Trust, but verify
Is Plan A really always faster? It may be that after Plan A is being ever so gently forced (ok, that’s not gentle), the query is run with different parameters, that make Plan A skew horribly wrong, and our average CPU time goes to 12 seconds. SQL Server is looking for that.
Un-force when things change
Automatic Plan Correction is also pretty skittish when it comes to change.
Change indexes on the table? Whoops, we better un-force everything in case there’s something that could be faster! Update statistics on the table? Same thing!
Automatic Plan Corrections don’t persist over restarts
I did a little demo of this, just to prove it to myself.
I have an Automatic Plan Correction in verification state. Here’s a view of it in sys.dm_db_tuning_recommendations:
But then, I restart my SQL Server instance:
Once the instance comes back up, Automatic Plan correction is no longer in place.
Here’s a different view, after restart. Looking at Query Id 25 in the Queries with High Variation report, there are NO check boxes in those bubbles to the right. Nothing is being forced.
Everything is also gone from view in the sys.dm_db_tuning_recommendations. That data isn’t persisted after restarts.
The good news: we don’t completely start from scratch
My Query Store still has data about past performance of the plan.
After restart, if my query happens to compile with the “slow plan”, the fast plan can be identified from the history capture in Query Store before the restart.
In other words, Automatic Plan Correction doesn’t only consider information since the last restart when making its suggestions. I tested, and just by repeatedly running the “slow plan” for Query Id 25, a recommendation popped up. Since I have automatic tuning enabled for this database, it was automatically applied.
Think of Automatic Tuning as an assistant DBA
And it’s better than just a random assistant! It’s an assistant that can help point you in the direction of problems like bad parameter sniffing, which are really hard to track down.
If your environment is so sensitive that you don’t trust your assistant to just go tuning things, that’s fine! You don’t have to enable Automatic Tuning, and you can still look at the suggestions manually (as long as you’ve got Enterprise Edition).
But at this point, and for the foreseeable future, your assistant still needs you to dig into the code, indexes, and architecture to find a better long term solution than just freezing the plan. Because if just freezing the plan was enough, this feature would be a whole lot simpler.
Just spent 90 minutes of my life figuring out a detail about https://t.co/p6hhqM41pX_exec_query_stats which I’m pretty sure I figured out five years ago, but didn’t write a blog post about.#blogforfutureyou
It’s time to write a blog post, so I can save time when I go searching for this in a couple years!
Functions can be tricksy in SQL Server
It can be difficult to see functions in execution plans, and to figure out when they’re eating a lot of CPU time on your SQL Server.
One way that’s handy to see if functions are being executed frequently is to check out the sys.dm_exec_query_stats Dynamic Management View and look at the execution count, CPU time, and logical reads of the ‘CREATE FUNCTION’ statement for a given function, to estimate its impact.
But recently, I was working on a project and…
I couldn’t find my functions in sys.dm_exec_query_stats! I also couldn’t find them in Query Store.
Or, rather, I couldn’t find SOME of my functions
Some of my functions in the demo code were showing up just fine. I was really puzzled by that. I thought …
Maybe this is a bug with ‘CREATE OR ALTER’? A sign of some weird memory pressure? Something introduced in SQL Server 2017? A buggy side effect of implicit conversions in some of the functions? A problem with the queries I was using? A weird setting on the database? (Also: about 100 other things that didn’t turn out to be the case.)
I finally wrote up some simple demo code, tested it against a SQL Server 2008 R2 instance (omitting the Query Store components), compared it with SQL Server 2017, and found it to be consistent.
Some functions just don’t accrue information in sys.dm_exec_query_stats, and that’s been true for quite a while.
I tested six types of functions:
Scalar functions that do, and do not do data access
Multi-statement table valued functions that do, and do not do data access
Single statement table valued functions that do, and do not do data access
Which of these functions appear in sys.dm_exec_query_stats and sys.query_store_query?
To be clear, the queries run which call the functions all appear in sys.dm_exec_query_stats and sys.query_store_query. There are no missing calling queries.
However, the calling query only shows things like how many times it was executed. It doesn’t show how many times the function was executed (the function may be executed many times per query). Sometimes that’s handy to see when you’re troubleshooting.
CREATE FUNCTION entries with per-function-execution data only appeared for my:
Scalar function that does data access
Multi-statement table valued function that does data access
Multi-statement table valued function that does NOT do data access
It makes sense to me that my single statement table valued functions are missing from the list, because those functions can be “inlined” into their outer queries. They don’t really run on their own.
However, it is a bit confusing that the scalar function that does not do data access is missing from the list. (I don’t think it’s a huge tragedy, but it makes it look like you’re missing something — hence me spending a while on this!)
Which of these functions appear in sys.dm_exec_function_stats?
We got sys.dm_exec_function_stats in SQL Server 2016, so you can’t use this on Ye Older SQL Instances. This view recorded information for my:
Scalar function that does data access
Scalar function that does NOT do data access
Why no table valued functions? This one is documented! The DMV simply doesn’t have information about Table Valued Functions.
What does this all mean?
If you have a lot of scalar functions that don’t do data access, and you’re on SQL Server 2016 and higher, check out sys.dm_exec_function_stats for aggregate information on how they are running.
And if you’re just confused about why not all your functions are showing up in sys.dm_exec_query_stats or Query Store, hopefully this post saves you some time!
I host live technical webcasts and SQLChallenges for subscribers. Register here!
✅ Thu, May 24, 9AM Pacific – RCSI and Snapshot Isolation in SQL Server
✅ Thu, May 31, 9AM Pacific – SQLChallenge: Write a Query That…
✅ Thu, Jun 7, 9AM Pacific – SQLChallenge: Design the Best Index For..