{"id":69162,"date":"2016-11-29T13:40:00","date_gmt":"2016-11-29T13:40:00","guid":{"rendered":"https:\/\/www.simple-talk.com\/?p=69162"},"modified":"2021-08-24T13:39:27","modified_gmt":"2021-08-24T13:39:27","slug":"determining-happens-force-execution-plan-via-query-store","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/determining-happens-force-execution-plan-via-query-store\/","title":{"rendered":"Determining What Happens When You Force an Execution Plan via the Query Store"},"content":{"rendered":"<p>In <a href=\"https:\/\/www.simple-talk.com\/sql\/database-administration\/the-sql-server-2016-query-store-forcing-execution-plans-using-the-query-store\/\">one of my previous articles about the Query Store<\/a> I briefly touched on the subject of forcing an execution plan via the Query Store. There is, however, much more to relate than I could fit in the previous introduction article. In this article, I\u2019ll be explaining how you can set up a test to find out what happens when you force a plan, both through the Query Store built-in reports and via Query Store\u2019s own DMVs. I\u2019ll then use these to demonstrate what happens when plan-forcing fails, and what is causing the failure.<\/p>\n<h2>Forcing execution plans<\/h2>\n<p>In my \u201cForcing Execution Plans using the Query Store\u201d article I described how it was easier to force a specific execution plan than it was to manually build a plan guide: However, I didn\u2019t go into more detail about the way that the Query Store records forced plans or what happens when execution plans can no longer be forced.<\/p>\n<p>There are several ways in which we can force specific execution plans for a query through the Query Store. You have the alternatives of forcing execution plans through the various built-in reports that are provided through the Query Store or by using the <strong>sp_query_store_force_plan<\/strong> stored procedure.<\/p>\n<p>In order to get several plans for a single query inside the Query Store, I created a stored procedure that retrieves data from the <strong>SalesOrderDetail<\/strong> table based on the <strong>ProductID<\/strong> inside the <strong>AdventureWorks<\/strong> database.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql decode:true\">CREATE PROCEDURE sp_SalesbyProduct\r\n\t@ProductID INT\r\nAS\r\nSELECT \r\n  SalesOrderID, \r\n  OrderQty,\r\n  UnitPrice\r\nFROM Sales.SalesOrderDetail\r\nWHERE ProductID = @ProductID\r\nGO\r\n<\/pre>\n<p>The next step is to execute the <strong>sp_SalesbyProduct<\/strong> stored procedure. I am executing the stored procedure twice with different parameters, 710 and 870. I picked both these parameter values deliberately, basing my choice on how the table data is distributed inside the statistics of the table. Because there are only 44 sales inside the <strong>SalesOrderDetail<\/strong> table for Product ID 710, the Query Optimizer resorts to using an \u2018Index Seek\u2019 operation. There are a lot more sales for <strong>Product ID<\/strong> 870, 4688 to be exact. For such a high number of rows the Query Optimizer chooses to scan the entire table and uses an Index Scan operation.<\/p>\n<p>Between executions of the stored procedure I am forcing a clearance of the plan cache. The reason for this is that SQL Server always prefers to avoid the cost of (re)compilation of execution plans. This is especially true for stored procedures that can easily be parameterized. If I didn\u2019t clear the plan cache between both executions, the first execution would result in a parameterized execution plan inside the plan cache. The second time the stored procedure would be executed the Query Optimizer would detect there already is an execution plan in the plan cache that could be used for the execution of the stored procedure. Even though the execution plan in the plan cache was compiled for a parameter value of 710, SQL Server would execute the exact same plan but then use the parameter value of 870. This way we would never get a different plan based on the parameter value unless we either trigger an execution plan recompilation or remove the plan from the plan cache.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql decode:true\">DBCC FREEPROCCACHE\r\nGO\r\nEXEC sp_SalesbyProduct @ProductID = 710\r\nGO\r\nDBCC FREEPROCCACHE\r\nGO\r\nEXEC sp_SalesbyProduct @ProductID = 870\r\nGO\r\n<\/pre>\n<p>Through the Query Store built-in reports, I can see that two different plans were used when executing the T-SQL above. One of the execution plans uses an Index Seek operation to retrieve the data, whereas the other uses an Index Scan.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1472\" height=\"657\" class=\"wp-image-69163\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-50.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 1:<\/strong> different execution plans for the same query<\/p>\n<p>Let\u2019s force one of the plans for the query. In this case I want to force the execution plan that uses an Index Seek operation. From figure 1 above, we can see that our query received a Query Store query ID of 1375, and the execution plan we want to use has an execution plan ID of 1459.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql decode:true\">  EXEC sp_query_store_force_plan 1375, 1459;\r\n<\/pre>\n<p>By running the stored procedure above, the execution plan is forced as shown by the little \u201cV\u201d on top of the circle depicting the specific execution plan in figure 2 below.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"652\" height=\"367\" class=\"wp-image-69164\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-51.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 2:<\/strong> execution plan 1459 forced on query 1375<\/p>\n<p>So besides showing a \u201cV\u201d on the execution plan inside the graphical Query Store reports, how does the Query Store record what execution plan is forced, and for what query?<\/p>\n<p>Before we were able to use the Query Store, we would query the <strong>sys.plan_guides<\/strong> DMV when we were dealing with forced execution plans, so this seems the first logical place to look for forced plans. But, as you can see in figure 3 below, the Query Store doesn\u2019t record information about the forced-plan inside the <strong>sys.plan_guides<\/strong> DMV.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"886\" height=\"86\" class=\"wp-image-69165\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-52.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 3:<\/strong> sys.plan_guides results<\/p>\n<p>Instead, the Query Store records those plans that are forced inside one of its own DMVs: <strong>sys.query_store_plan<\/strong>. By running the following query, we can retrieve all the plans that were forced using the Query Store including some additional information such as the number of occasions that the plan-forcing failed.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql decode:true\">SELECT\r\n  query_id,\r\n  plan_id,\r\n  is_forced_plan,\r\n  force_failure_count,\r\n  last_force_failure_reason_desc\r\nFROM sys.query_store_plan\r\nWHERE is_forced_plan = 1\r\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"499\" height=\"67\" class=\"wp-image-69166\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-53.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 4:<\/strong> forced plan information inside sys.query_store_plan.<\/p>\n<p>To easily identify all the queries that have an execution plan forced, you can also use the (custom) Query Store Database Dashboard. The Query Store Database Dashboard is an open-source and free SSMS report that returns additional information that is stored inside the Query Store, and that isn\u2019t available through the built-in reports. It also includes a section that shows all the forced plans for a database that has the Query Store enabled. You can download the Query Store Database Dashboard through its GitHub page: <a href=\"https:\/\/github.com\/Evdlaar\/QueryStoreDashboard\">https:\/\/github.com\/Evdlaar\/QueryStoreDashboard<\/a>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1365\" height=\"84\" class=\"wp-image-69167\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-54.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 5:<\/strong> Forced Execution Plans section inside the Query Store Database Dashboard<\/p>\n<h2>When plan forcing fails<\/h2>\n<p>As you have seen in figure 4, the Query Store also records information when it isn\u2019t able to force the execution plan, such as the number of times that it failed to force the plan and the last reason for failure.<\/p>\n<p>As it turns out, there are various reasons why the Query Store is unable to force the execution plan that we want. Let\u2019s take a look at some of those reasons for failure through some examples.<\/p>\n<p>For starters, what would happen if you remove an index that the forced execution plan uses? The plan we forced earlier happens to use the <strong>IX_SalesOrderDetail_ProductID<\/strong> index on the <strong>SalesOrderDetail<\/strong> table for an Index Seek operation, so let\u2019s disable the index!<\/p>\n<pre class=\"theme:ssms2012 lang:tsql decode:true\">  ALTER INDEX IX_SalesOrderDetail_ProductID\r\n ON Sales.SalesOrderDetail DISABLE\r\n<\/pre>\n<p>We can show this by running our stored procedure again, this time with a product ID of 870. Without plan forcing, this would result in an Index Scan operation and as you can see in figure 6 below we do not get an execution plan forced on the query.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"381\" height=\"170\" class=\"wp-image-69168\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-55.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 6:\u00a0<\/strong> the old plan instead of the forced plan is returned after disabling the index it used<\/p>\n<p>If we run our query to grab forced plan information from the Query Store we can see if, and why, it failed to force the execution plan.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql decode:true\">SELECT\r\n  query_id,\r\n  plan_id,\r\n  is_forced_plan,\r\n  force_failure_count,\r\n  last_force_failure_reason_desc\r\nFROM sys.query_store_plan\r\nWHERE is_forced_plan = 1\r\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"498\" height=\"71\" class=\"wp-image-69169\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-56.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 7:<\/strong> NO_INDEX execution plan force failure<\/p>\n<p>As you can see in figure 7 above, the Query Store couldn\u2019t force the execution plan because one of the operators inside the execution plan depended on the index we disabled. Hence the NO_INDEX inside the <strong>last_force_failure_reason_desc<\/strong> column.<\/p>\n<p>So what happens when we enable our index again? Would the Query Store go back to forcing the execution plan again?<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"545\" height=\"259\" class=\"wp-image-69170\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-57.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 8:<\/strong> execution plan is forced again<\/p>\n<p>Yes, it does! <br \/> Directly after rebuilding the index (a rebuild is required to enable the index) the Query Store is able to force the execution plan again.<\/p>\n<p>Let\u2019s look at another example. What would happen if we were to remove the execution plan we want to force from the Query Store? There are two ways we can use to remove execution plans that are stored inside the Query Store:<\/p>\n<ul>\n<li>Purge all Query Data<\/li>\n<li>Remove a single execution plan based on its ID.<\/li>\n<\/ul>\n<p>If we are to purge the Query Store by using the \u201cPurge Query Data\u201d button on the Query Store properties page or by running the ALTER DATABASE [db_name] SET QUERY_STORE CLEAR command we would remove all data inside the Query Store. This means we don\u2019t just remove all the execution plans and runtime statistics, but we will also remove the plan forcing rules we created. The result is simple, since we removed the plan forcing rules by purging the Query Store nothing gets forced.<\/p>\n<p>We can also choose for a more elegant method, removing a specific execution plan from the Query Store. For this we can use the stored procedure <strong>sys.query_store_remove_plan<\/strong> followed with the execution plan ID.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql decode:true\">  EXEC sp_query_store_remove_plan 46\r\n<\/pre>\n<p>However, if we run the command above we are confronted by the error message shown below in figure 9.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"678\" height=\"36\" class=\"wp-image-69171\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-58.png\" \/><\/p>\n<p>The message is pretty clear, as long as the execution plan is being forced for a query it can only be deleted by purging the entire Query Store.<\/p>\n<p>Next to the example where we removed the index to make the plan forcing fail, there are a few more reasons why an execution plan couldn\u2019t be forced through the Query Store:<\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<p>ONLINE_INDEX_BUILD<\/p>\n<\/td>\n<td>\n<p>Occurs when a forced plan tries to modify data inside an index that is rebuild online.<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>INVALID_STARJOIN<\/p>\n<\/td>\n<td>\n<p>Plan contains invalid StarJoin specification<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>TIME_OUT<\/p>\n<\/td>\n<td>\n<p>Optimizer exceeded number of allowed operations while searching for plan specified by forced plan<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>NO_DB<\/p>\n<\/td>\n<td>\n<p>A database specified in the plan does not exist<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>HINT_CONFLICT<\/p>\n<\/td>\n<td>\n<p>Query cannot be compiled because plan conflicts with a query hint<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>DQ_NO_FORCING_SUPPORTED<\/p>\n<\/td>\n<td>\n<p>Cannot execute query because plan conflicts with use of distributed query or full-text operations<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>NO_PLAN<\/p>\n<\/td>\n<td>\n<p>Query processor could not produce query plan because forced plan could not be verified to be valid for the query<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>VIEW_COMPILE_FAILED<\/p>\n<\/td>\n<td>\n<p>Could not force query plan because of a problem in an indexed view referenced in the plan<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p>GENERAL_FAILURE<\/p>\n<\/td>\n<td>\n<p>General forcing error (not covered with reasons above)<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Source: <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/dn818155.aspx\">https:\/\/msdn.microsoft.com\/en-us\/library\/dn818155.aspx<\/a><\/p>\n<h2>Detecting Query Store plan force failure through Extended Events<\/h2>\n<p>A drawback of using the plan force failure information inside the <strong>sys.query_store_plan<\/strong> DMV is that you cannot see when a plan force failure occurred. As we have seen before, the Query Store only records the total amount of times a plan force failure occurred and what the last plan force failure was. Thankfully there is an Extended Event we can use to track when a plan force failure occurred and why it occurred.<\/p>\n<p>Let\u2019s set up an Extended Event session to track plan force failures using the example where we removed the index from the table that we are querying.<\/p>\n<p>The Extended Event that we need is called <strong>query_store_plan_forcing_failed<\/strong> and it records the error number, message, query ID and the plan ID.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"445\" height=\"490\" class=\"wp-image-69172\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-59.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 9:<\/strong> query_store_plan_forcing_failed extended event<\/p>\n<p>To keep things quick and simple, I scripted out the creation of the Extended Event session:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql decode:true\">CREATE EVENT SESSION [QueryStore_Plan_Force_Failure] ON SERVER \r\nADD EVENT qds.query_store_plan_forcing_failed\r\nADD TARGET package0.ring_buffer WITH \r\n  (\r\n  MAX_MEMORY=4096 KB,\r\n  EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,\r\n  MAX_DISPATCH_LATENCY=30 SECONDS,\r\n  MAX_EVENT_SIZE=0 KB,\r\n  MEMORY_PARTITION_MODE=NONE,\r\n  TRACK_CAUSALITY=OFF,\r\n  STARTUP_STATE=OFF\r\n)\r\nGO\r\n<\/pre>\n<p>After starting the Extended Event session, I disabled the <strong>IX_SalesOrderDetail_ProductID<\/strong> index on the <strong>SalesOrderDetail<\/strong> table again and executed the <strong>sp_SalesbyProduct <\/strong>stored procedure:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql decode:true\">  EXEC sp_SalesbyProduct @ProductID = 870\r\n<\/pre>\n<p>As before, we didn\u2019t get the execution plan we forced through the Query Store. Instead, we see the execution plan with the Index Scan instead of the Index Seek.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"381\" height=\"170\" class=\"wp-image-69173\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-60.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 10:<\/strong> Index Scan execution plan is used instead of the forced plan<\/p>\n<p>There is a ring buffer for the session that we created in order to monitor the Query Store plan force failures. If we take a look at the Extended Event data collected inside the ring buffer, we get to see a lot more information than was returned by the Query Store DMV.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1042\" height=\"327\" class=\"wp-image-69174\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2016\/11\/word-image-61.png\" \/><\/p>\n<p class=\"caption\"><strong>Figure 11:<\/strong> query_store_plan_forcing_failed extended event data<\/p>\n<p>As you can see in figure 11 we can see when the extended event triggered, in this case 2016-11-23 at 19:19:06. We can also see the query ID with a value of \u201c1\u201d and the plan ID, also a value of \u201c1\u201d in this case. The error_number of \u201c8712\u201d corresponds with the error description \u201cNO_INDEX\u201d we also see in the <strong>sys.query_store_plan<\/strong> DMV. Interestingly enough, the \u201cmessage\u201d we see recorded in the extended event session indicates we used a plan hint that specified an index that no longer exists. We didn\u2019t use any plan hints inside the stored procedure we used for testing so it seems that this error message is reused.<\/p>\n<h2>Summary<\/h2>\n<p>In this article we took a look at what happens when you force an execution plan via the Query Store and how you can identify plans that were forced. Through an example we took a look at what happens when the Query Store is unable to force an execution plan and how we can find out why plan-forcing failed through the <strong>sys.query_store_plan<\/strong> DMV. Finally, we used an Extended Events session to gather additional information that isn\u2019t available through the Query Store DMVs, including a date\/time of the plan force failure and an error message.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When you force a query plan via the Query Store, you will need to  track what happens: Sometimes  the request to force a plan will fail, and you will want to know when and why. There are several ways of getting feedback, ranging from  the built-in reports to using extended events. Enrico  explains the details.&hellip;<\/p>\n","protected":false},"author":221970,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143527,143531],"tags":[],"coauthors":[35212],"class_list":["post-69162","post","type-post","status-publish","format-standard","hentry","category-database-administration-sql-server","category-t-sql-programming-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/69162","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221970"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=69162"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/69162\/revisions"}],"predecessor-version":[{"id":91258,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/69162\/revisions\/91258"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=69162"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=69162"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=69162"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=69162"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}