{"id":1254,"date":"2011-08-16T16:30:00","date_gmt":"2011-08-16T14:30:00","guid":{"rendered":"https:\/\/www.sqlinthewild.co.za\/?p=1254"},"modified":"2011-08-11T23:30:47","modified_gmt":"2011-08-11T21:30:47","slug":"compiles-and-recompiles","status":"publish","type":"post","link":"https:\/\/www.sqlinthewild.co.za\/index.php\/2011\/08\/16\/compiles-and-recompiles\/","title":{"rendered":"Compiles and recompiles"},"content":{"rendered":"<p>I want to spend some time over the next few months looking at query compilation and the plan cache, and there&#8217;s a couple concepts that I want to get cleared up beforehand. The first of those is around two terms that are often used interchangeably, compile and recompile.<\/p>\n<h3>Compile<\/h3>\n<p>A compile occurs when a query is given to the query optimiser and, when it does a lookup into the plan cache, no matching plan is found. The optimism must then compile the query, generating an execution plan, must add that plan to the plan cache (in most cases) and then must pass that plan onto the query execution engine so that the query can be executed. (<a href=\"http:\/\/technet.microsoft.com\/en-us\/library\/Cc966425\">http:\/\/technet.microsoft.com\/en-us\/library\/Cc966425<\/a>)<\/p>\n<h3>Recompile<\/h3>\n<p>A recompile is something slightly different. For a recompile, the optimiser must find a matching plan when it queries the plan cache, must hand that cached plan over to the query execution engine and then while doing validation checks the execution engine must determine that then query plan is no longer valid and request the optimiser to partially or completely recompile the query. (<a href=\"http:\/\/technet.microsoft.com\/en-us\/library\/Cc966425\">http:\/\/technet.microsoft.com\/en-us\/library\/Cc966425<\/a>)<\/p>\n<p>Subtle difference. Both cases result in the optimiser generating an execution plan, but the reasons can be different. Also worth noting is that a compile results in a new plan in the cache, a recompile simply replaces an existing plan.<\/p>\n<p>Another difference since SQL 2005 &#8211; a compile is for the entire batch, but a recompile can be for just a single statement within the batch.<\/p>\n<hr \/>\n<p>Now the theory&#8217;s dealt with, let&#8217;s look at some examples and see how we can track these two events and try and get a better understanding of which occurs when and how they look.<\/p>\n<p>The tools I&#8217;m going to use to track these are performance monitor with the compiles\/sec and recompiles\/sec counters and SQL Profiler with the event SP:StmtRecompile event (there&#8217;s no profiler event for compilation). I&#8217;ll also check what&#8217;s in the plan cache after each test.<\/p>\n<p>The first one\u2019s going to be very simplistic, a query run against an empty plan cache.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">DBCC FREEPROCCACHE\r\nGO\r\n\r\nEXEC dbo.OutStandingTotalByStatus\r\nGO<\/pre>\n<p>What we get from that is a non-zero value for SQL Compilations\/sec (perfmon) and the following from profiler (The SQL Recompiles\/sec remains 0)<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles1.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"Compiles1\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles1_thumb.png\" border=\"0\" alt=\"Compiles1\" width=\"484\" height=\"52\" \/><\/a><\/p>\n<p>and the plan cache now contains one plan with one use. (for more info on how the CacheMiss and CacheInsert events work, see <a href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2010\/07\/27\/hit-and-miss\/\">https:\/\/www.sqlinthewild.co.za\/index.php\/2010\/07\/27\/hit-and-miss\/<\/a> and <a href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2010\/08\/31\/come-and-gone\/\">https:\/\/www.sqlinthewild.co.za\/index.php\/2010\/08\/31\/come-and-gone\/<\/a>)<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles2.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"Compiles2\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles2_thumb.png\" border=\"0\" alt=\"Compiles2\" width=\"484\" height=\"24\" \/><\/a><\/p>\n<p>In this case, I hope it was clear, we had a compile occur (empty plan cache before, new plan added to cache).<\/p>\n<p>Now what happens if, with no clearing of the cache nor anything else being done, I mark that procedure for recompile and run it again?<\/p>\n<p><!--more--><\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">EXEC sp_recompile OutStandingTotalByStatus\r\nGO\r\n\r\nEXEC dbo.OutStandingTotalByStatus\r\nGO<\/pre>\n<p>This is interesting. We don\u2019t see a non-zero value for SQLRecompiles\/sec (as might have been expected), instead, as with the previous case we get a spike for SQLCompiles\/sec. The profiler output shows why<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles3.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"Compiles3\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles3_thumb.png\" border=\"0\" alt=\"Compiles3\" width=\"484\" height=\"122\" \/><\/a><\/p>\n<p>Note the SP:CacheRemove being run for the stored procedure\u2019s plan. So sp_recompile does not mark a plan as invalid and needing recompilation. It removes it from the cache entirely. The next time that procedure runs SQL does a cache lookup, doesn\u2019t find a matching plan and compiles a new one (compile, not recompile)<\/p>\n<p>Onwards\u2026 What about if I alter the procedure?<\/p>\n<p>I\u2019m not going to clear the cache first, the procedure has a plan in there from the last test, we can use that to see the effects.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">ALTER PROCEDURE OutStandingTotalByStatus\r\nAS\r\n-- This has changed!\r\nSELECT 1;\r\n\r\nSELECT o.OrderStatus, SUM(UnitPrice*Quantity) AS TotalOutstanding\r\nFROM dbo.Orders o\r\nINNER JOIN dbo.OrderDetails od ON o.OrderID = od.OrderID\r\nWHERE ShippingDate IS NULL\r\nGROUP BY OrderStatus;\r\nGO\r\n\r\nEXEC dbo.OutStandingTotalByStatus\r\nGO<\/pre>\n<p>Again perfmon shows a bump in Compiles\/sec and the recompiles\/sec is still at 0. Again, profiler shows why.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles4.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"Compiles4\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles4_thumb.png\" border=\"0\" alt=\"Compiles4\" width=\"484\" height=\"116\" \/><\/a><\/p>\n<p>Same as when I ran the sp_recompile, the ALTER PROCEDURE triggered a SP:CacheRemove. The plan was gone from cache at the point the stored procedure ran again, so this was considered a compile, not a recompile.<\/p>\n<p>btw, for those curious, if I query the plan cache between altering the procedure and running it again, the procedure\u2019s plan really has gone.<\/p>\n<p>Let\u2019s try something more complex, an alteration of one of the base tables. I\u2019m going to clear the cache first, for reasons that will later be clear<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">DBCC FREEPROCCACHE\r\nGO\r\n\r\nEXEC dbo.OutStandingTotalByStatus\r\nGO\r\n\r\nALTER TABLE dbo.OrderDetails ADD Filler CHAR(10)\r\nGO\r\n\r\nEXEC dbo.OutStandingTotalByStatus\r\nGO<\/pre>\n<p>Ha! This time perfmon shows a non-zero value for SQLRecompiles\/sec. The profiler trace has a different form too<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles5.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"Compiles5\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles5_thumb.png\" border=\"0\" alt=\"Compiles5\" width=\"484\" height=\"92\" \/><\/a><\/p>\n<p>There\u2019s no SP:CacheRemove. No SP:CacheInsert either.<\/p>\n<p>The alteration of the table did not result in SQL going through the plan cache and removing plans for dependant objects. That makes sense if you think about it. There could be millions of plans in cache, for SQL to check each and every one to see if it has a dependency on the table just changed would be terribly time consuming, so it\u2019s not done. What happens instead is that the next time the procedure runs SQL looks in the cache, finds the procedure\u2019s plan (the SP:CacheHit), does the pre-execution validations and finds that one of the base objects has changed since that plan was compiled. The plan is then sent to the optimiser to recompile that plan (the SP:Recompile event)<\/p>\n<p>Here\u2019s the other interesting thing, from a query of the plan cache<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles6.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"Compiles6\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles6_thumb.png\" border=\"0\" alt=\"Compiles6\" width=\"484\" height=\"38\" \/><\/a><\/p>\n<p>The usecounts is 2, not 1. I ran that procedure once before altering the table, once after. The alter of the table forced SQL to recompile the plan on the second execution, but the usecount for the plan reflects both executions, the one from before the recompile and the one from after.<\/p>\n<p>In all the previous cases, when the plan was actually removed from cache and then compiled and inserted the usecount was only 1 because when the plan was removed all related information was removed with it. With a recompile it\u2019s not removed, so the usecounts aren\u2019t reset to 0.<\/p>\n<p>This is important to note. There are lots of articles (and books even) that say to check plan usecounts as a low usecount is a sign of frequent recompiles. As we can now see, it\u2019s not. It\u2019s a sign of frequent compiles, of the plans being removed from cache and later re-added, recompiles don\u2019t affect the usecount (well, at least not in this case, there may be cases where it does)<\/p>\n<p>Now try a couple more common cases, see what they show, starting with rebuilding an index.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">DBCC FREEPROCCACHE\r\nGO\r\n\r\nEXEC dbo.OutStandingTotalByStatus\r\nGO\r\n\r\nALTER INDEX PK_OrderDetails ON dbo.OrderDetails REBUILD\r\nGO\r\n\r\nEXEC dbo.OutStandingTotalByStatus\r\nGO<\/pre>\n<p>Any guesses?<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles7.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"Compiles7\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles7_thumb.png\" border=\"0\" alt=\"Compiles7\" width=\"484\" height=\"78\" \/><\/a><\/p>\n<p>It\u2019s a recompile (and the perfmon counters confirm that). As with the previous case, the usecount shown in sys.dm_exec_cached_plans is 2 for the procedure\u2019s plan, not 1<\/p>\n<p>Lastly, a statistics update. Can anyone guess what\u2019s going to happen here? Anyone?<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">DBCC FREEPROCCACHE\r\nGO\r\n\r\nEXEC dbo.OutStandingTotalByStatus\r\nGO\r\n\r\n-- Insert a few more rows into OrderDetails at this point\r\n\r\nUPDATE STATISTICS dbo.OrderDetails WITH FULLSCAN\r\nGO\r\n\r\nEXEC dbo.OutStandingTotalByStatus\r\nGO<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles9.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"Compiles9\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/08\/Compiles9_thumb.png\" border=\"0\" alt=\"Compiles9\" width=\"484\" height=\"90\" \/><\/a><\/p>\n<p>Yup, it\u2019s a recompile.<\/p>\n<p>It is worth nothing that (in my tests anyway), if at least one row isn\u2019t changed, the stats update has no effect. Perhaps an optimisation that checks whether there have been modifications or not before updating? I\u2019m not sure what\u2019s causing that.<\/p>\n<p>There is a lot more I could test, SET option changes, memory pressure, configuration changes, etc, but I think I\u2019ve done enough that anyone interested can play themselves for further info.<\/p>\n<p>Ok, but what\u2019s the use? Was this just a dive into internals with no practical usage? I don\u2019t think so.<\/p>\n<p>One thing that\u2019s important when investigating SQL problems is to understand what the various counters, events and columns are saying and what they mean. If one is faced with a server with very high SQLCompiles\/sec, it\u2019s a waste of time to go looking at whether automatic or manual stats updates are happening too often. Similarly if the SQLRecompiles\/sec is high, investigating the usage of WITH RECOMPILE and OPTION(RECOMPILE) is completely off track and is going to waste time and not help with solving the problem.<\/p>\n<p>That\u2019s why I believe it\u2019s important to understand what the various counters, events and columns actually means and what occurrences within SQL affects them. None of us have time to spend a couple days busy investigating something that\u2019s not related to what\u2019s actually wrong with the server based on an incorrect understanding of what SQL is showing us.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I want to spend some time over the next few months looking at query compilation and the plan cache, and there&#8217;s a couple concepts that I want to get cleared up beforehand. The first of those is around two terms&#8230; <a class=\"read-more-button\" href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2011\/08\/16\/compiles-and-recompiles\/\">(Read more)<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[23,15,16],"tags":[],"class_list":["post-1254","post","type-post","status-publish","format-standard","hentry","category-execution-plans","category-sql-server","category-syndication"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p7h6n-ke","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/1254","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/comments?post=1254"}],"version-history":[{"count":0,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/1254\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/media?parent=1254"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/categories?post=1254"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/tags?post=1254"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}