<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>SQL in the Wild &#187; Execution Plans</title>
	<atom:link href="http://sqlinthewild.co.za/index.php/category/sql-server/execution-plans/feed/" rel="self" type="application/rss+xml" />
	<link>http://sqlinthewild.co.za</link>
	<description>A discussion on SQL Server</description>
	<lastBuildDate>Tue, 31 Aug 2010 17:26:19 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Come and gone</title>
		<link>http://sqlinthewild.co.za/index.php/2010/08/31/come-and-gone/</link>
		<comments>http://sqlinthewild.co.za/index.php/2010/08/31/come-and-gone/#comments</comments>
		<pubDate>Tue, 31 Aug 2010 14:00:59 +0000</pubDate>
		<dc:creator>Gail</dc:creator>
				<category><![CDATA[Execution Plans]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Syndication]]></category>

		<guid isPermaLink="false">http://sqlinthewild.co.za/?p=643</guid>
		<description><![CDATA[Or &#8220;Plan cache monitoring &#8211; insert and remove&#8221; Previously I took a look at the CacheHit and CacheMiss events to see how they behave and how to identify what&#8217;s been searched for in the cache. in this follow up, I want to take a similar look at the CacheInsert and CacheRemove events, see when they [...]]]></description>
			<content:encoded><![CDATA[<p>Or &#8220;<em>Plan cache monitoring &#8211; insert and remove</em>&#8221;</p>
<p>Previously I took a look at the <a href="http://sqlinthewild.co.za/index.php/2010/07/27/hit-and-miss/">CacheHit and CacheMiss</a> events to see how they behave and how to identify what&#8217;s been searched for in the cache. in this follow up, I want to take a similar look at the CacheInsert and CacheRemove events, see when they fire and how to identify the objects that they relate to.</p>
<p>Again, a word of caution, these can be frequently occurring events on busy servers and so traces should be kept short and to a minimum of events and columns. That said, these should occur a lot less often than the CacheHit and CacheMiss events. If they are occurring very frequently it may indicate that the SQL Server is not reusing plans efficiently.</p>
<h3>CacheInsert</h3>
<p>The CacheInsert event fires after a CacheMiss. The search for a matching plan in the cache failed, firing a CacheMiss event. Since there&#8217;s no plan, the optimiser is invoked to generate one and then that plan is inserted into the plan cache before the Query Execution engine begins execution.</p>
<p>The event is fairly simple, though of course there are a few surprises (what in SQL doesn&#8217;t have?).</p>
<pre class="brush: sql;">Exec FireCacheEvents
GO

SELECT ID, SomeDate, Status
FROM TestingCacheEvents
WHERE Status =  'C'</pre>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2010/08/CacheInsert.png"><img style="display: inline; border-width: 0px;" title="CacheInsert" src="http://sqlinthewild.co.za/wp-content/uploads/2010/08/CacheInsert_thumb.png" border="0" alt="CacheInsert" width="490" height="83" /></a></p>
<p><span id="more-643"></span></p>
<p>Three CacheInsert events for two batches. The first is simple enough, it&#8217;s the insert of the plan for the stored procedure. The procedure name is in the TextData column and the procedure&#8217;s ID is in the ObjectID column. The ObjectName column is not populated for this event.</p>
<p>The second and third Cacheinsert events are the interesting ones. The second one shows a parameterised version of the ad-hoc SQL statement, while the third shows an unparameterised version of the same ad-hoc SQL statement. Clearly this query was simple enough to qualify for auto-parameterisation. So, are there two plans for this been inserted into cache? To answer that one, I need to switch over to SSMS and query the plan cache.</p>
<pre class="brush: sql;">select usecounts, size_in_bytes, cacheobjtype, objtype, st.text,  qp.query_plan
 from sys.dm_exec_cached_plans cp
 cross apply  sys.dm_exec_sql_text(cp.plan_handle) st
 cross apply  sys.dm_exec_query_plan(cp.plan_handle) qp
 where st.text not like  '%sys.dm_exec%'</pre>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2010/08/ExecCachedPlans.png"><img style="display: inline; border-width: 0px;" title="ExecCachedPlans" src="http://sqlinthewild.co.za/wp-content/uploads/2010/08/ExecCachedPlans_thumb.png" border="0" alt="ExecCachedPlans" width="484" height="56" /></a></p>
<p>Well, there are three entries and the Cache Object Type is listed as compiled plan for all three, but there&#8217;s something different between them. Take a look at the size in bytes for the ad-hoc and prepared statements. The ad-hoc is less than half the size of the prepared. That&#8217;s not the only thing different. If I open up the execution plans, the exec plan for the prepared statement looks normal. There&#8217;s a clustered index scan (there are no nonclustered indexes on this table yet) and a select operator. The plan for the adhoc statement however…</p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2010/08/NotaPlan.png"><img style="display: inline; border-width: 0px;" title="NotaPlan" src="http://sqlinthewild.co.za/wp-content/uploads/2010/08/NotaPlan_thumb.png" border="0" alt="NotaPlan" width="475" height="100" /></a></p>
<p>There&#8217;s something missing here, like the rest of the plan. Ok, so the entry with the ad-hoc type is not a complete plan, so what is it?</p>
<p>There doesn&#8217;t seem to be anything on MSDN on this (at least nothing I could find), but two of Kalen&#8217;s books<sup>1</sup>. (Inside Microsoft SQL Server 2005: Query Tuning and Optimisation, page 283; Microsoft SQL Server 2008 Internals, page 533) mention this. These ad-hoc entries are shell queries, cached just to make the parameterised form of the query easier to find. All the execution plan contains for these shell queries is a pointer to the plan for the parameterised version of the query.</p>
<p>For the ad-hoc query that gets inserted as a shell plan and the parameterised query, the objectID is, as with the CacheHit and CacheMiss events, a hash of the query text and does not match to any object in the database. The full text of the batch is then given in the TextData column.</p>
<p>I think that&#8217;s enough for CacheInsert. On to it&#8217;s opposite.</p>
<h3>CacheRemove</h3>
<p>The CacheRemove event fires, as one would expect, when a plan is removed from cache. The interesting question is, when does that occur?</p>
<p>Is it every event that could possibly result in an inefficient or invalid plan, or just memory pressure and explicit cache flushes that trigger the CacheRemove event?</p>
<p>Let&#8217;s see…</p>
<p>As an aside, I&#8217;m going to add a nonclustered index to the table that I&#8217;m using, so that the update of statistics will have an effect. Otherwise the only execution path possible is a table scan, and changing statistics won&#8217;t affect that.</p>
<pre class="brush: sql;">CREATE NONCLUSTERED INDEX idx_TestingCacheEvents_Status
 ON  TestingCacheEvents (Status)
GO</pre>
<p>Right, onto the testing.</p>
<pre class="brush: sql;">Exec FireCacheEvents
GO
SELECT ID, SomeDate, Status
FROM  TestingCacheEvents
WHERE Status = 'C'
GO
DBCC FREEPROCCACHE -- Gone.  All gone
WAITFOR DELAY '00:00:05'
GO

Exec FireCacheEvents
GO
SELECT ID, SomeDate, Status
FROM  TestingCacheEvents
WHERE Status = 'C'
GO
DBCC  FREEPROCCACHE(0x0500060063A9355540614285000000000000000000000000) -- Just one removed, the proc
WAITFOR DELAY '00:00:05'
GO

Exec FireCacheEvents
GO
SELECT ID, SomeDate, Status
FROM  TestingCacheEvents
WHERE Status = 'C'
GO
exec sp_recompile  'TestingCacheEvents' -- recompile on the table, both proc and ah-hoc plans  invalidated.
WAITFOR DELAY '00:00:05'
GO

Exec FireCacheEvents
GO
SELECT ID, SomeDate, Status
FROM  TestingCacheEvents
WHERE Status = 'C'
GO
UPDATE TOP(1)  TestingCacheEvents
 SET Status = 'C'
 WHERE Status = 'B'
UPDATE STATISTICS TestingCacheEvents WITH FULLSCAN
WAITFOR DELAY  '00:00:05'
GO

Exec FireCacheEvents
GO
SELECT ID, SomeDate, Status
FROM  TestingCacheEvents
WHERE Status = 'C'
GO
ALTER TABLE  TestingCacheEvents ALTER COLUMN FILLER CHAR(325)
WAITFOR DELAY '00:00:05'
GO
Exec FireCacheEvents
GO
SELECT ID, SomeDate, Status
FROM  TestingCacheEvents
WHERE Status = 'C'
GO</pre>
<p>Well, this is interesting…</p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2010/08/CacheRemove.png"><img style="display: inline; border-width: 0px;" title="CacheRemove" src="http://sqlinthewild.co.za/wp-content/uploads/2010/08/CacheRemove_thumb.png" border="0" alt="CacheRemove" width="492" height="230" /></a></p>
<p>The only things that caused the CacheRemove event to fire were the two DBCC FreeProcCache, the first that, as can be seen from the EventSubClass, cleared the entire plan cache, and the second that cleared a single plan.</p>
<p>If I go back and look at the whitepaper <a href="http://msdn.microsoft.com/en-us/library/ee343986.aspx">Plan Caching in SQL Server 2008</a>, it differentiates between removing a plan from cache (which memory pressure and cache flushes do) and invalidating the plan (which recompiles, schema changes, stats changes and the like do). Based on that whitepaper and these results, I would conclude that the CacheRemove event only fires when the plan is actually removed from cache, not when it&#8217;s just invalidated. The invalidation of a plan simply results in a recompile next time the plan is needed.</p>
<p>Now that the question of when it appears is answered, let&#8217;s finish with what it looks like.</p>
<p>There are two possible formats for this event, and it&#8217;s the EventSubClass that shows which of the two the particular event is.</p>
<p>The first possibility is when all of the plan cache has been flushed, identified by a EventSubClass of &#8220;2 &#8211; ProcCacheFlush&#8221;. This will be a result of a DBCC FREEPROCCACHE and some server reconfiguration events. In this case, there will be no ObjectID and the text data will simply state that the cache has been flushed.</p>
<p>The second possibility is more interesting, especially if monitoring plans being aged out of memory. An EventSubClass of &#8220;1 &#8211; Compplan Remove&#8221; identifies a single plan being removed from cache. These are fired when either a single plan or part of the plan cache is cleared. Examples here are DBCC FREEPROCCACHE with a plan or SQL handle passed to it, DBCC FLUSHPROCINDB, database reconfigurations (restore, detach, offline)</p>
<p>With the one, the ObjectID does have a value and it&#8217;s the ObjectID of the pbject whose plan is being thrown out of cache. As with all the other cache events, for ad-hoc queries the ObjectID is just a hash of the query text and only really useful for matching to CacheMiss, CacheHit and CacheInsert events and the object name or ad-hoc batch appears in the TextData</p>
<p>That&#8217;s more than I planned to write, but CacheRemove turned out to be a little more complex than I expected. Next up, the last part of this sort series &#8211; the recompile events.</p>
]]></content:encoded>
			<wfw:commentRss>http://sqlinthewild.co.za/index.php/2010/08/31/come-and-gone/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Hit and miss</title>
		<link>http://sqlinthewild.co.za/index.php/2010/07/27/hit-and-miss/</link>
		<comments>http://sqlinthewild.co.za/index.php/2010/07/27/hit-and-miss/#comments</comments>
		<pubDate>Tue, 27 Jul 2010 14:00:10 +0000</pubDate>
		<dc:creator>Gail</dc:creator>
				<category><![CDATA[Execution Plans]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Syndication]]></category>

		<guid isPermaLink="false">http://sqlinthewild.co.za/?p=377</guid>
		<description><![CDATA[Or &#8220;Monitoring plan cache usage&#8221; For people interested in the details of how SQL is using and reusing execution plans, there are some useful events in profiler for watching this in detail, under the Stored procedure group: SP:CacheMiss SP:CacheInsert SP:CacheHit SP:CacheRemove SP:Recompile SP:StmtRecompile Additionally there&#8217;s the SQL:StmtRecompile event under the TSQL group. For now, I [...]]]></description>
			<content:encoded><![CDATA[<p>Or &#8220;<em>Monitoring plan cache usage</em>&#8221;</p>
<p>For people interested in the details of how SQL is using and reusing execution plans, there are some useful events in profiler for watching this in detail, under the Stored procedure group:</p>
<ul>
<li>SP:CacheMiss</li>
<li>SP:CacheInsert</li>
<li>SP:CacheHit</li>
<li>SP:CacheRemove</li>
<li>SP:Recompile</li>
<li>SP:StmtRecompile</li>
</ul>
<p>Additionally there&#8217;s the SQL:StmtRecompile event under the TSQL group.</p>
<p>For now, I just want to look briefly at the CacheMiss and CacheHit events.</p>
<p>One word of caution early on, these are frequently occurring events and it may not be a good idea to trace these on busy production servers. If you do need to, keep the duration of the trace short and the columns to a minimum.</p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2010/07/CacheEvents.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="CacheEvents" src="http://sqlinthewild.co.za/wp-content/uploads/2010/07/CacheEvents_thumb.png" border="0" alt="CacheEvents" width="244" height="157" /></a></p>
<h3>CacheMiss</h3>
<p>The cache miss event fires any time SQL looks for the execution plans for an object or ad-hoc batch and does not find it in the plan cache.</p>
<p>For an object (scalar function, multi-statement table-valued function, stored procedure or trigger) the match is done on the object ID (along with some of the connection&#8217;s SET options and possibly the database user and c couple other factors<sup>1</sup>). For an ad-hoc batch, the match is done on a hash of the text of the batch (along with some of the connection&#8217;s SET options and possibly the database user)</p>
<p>When testing stored procedures from Management Studio (or another SQL querying tool), two CacheMiss events will appear in the trace.</p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2010/07/CacheMiss.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="CacheMiss" src="http://sqlinthewild.co.za/wp-content/uploads/2010/07/CacheMiss_thumb.png" border="0" alt="CacheMiss" width="490" height="87" /></a></p>
<p>What&#8217;s going on here?</p>
<p><span id="more-377"></span></p>
<p>Let&#8217;s start from the bottom and work up. The last event there, the SP:Completed records the completion of the stored procedure and lists both the ObjectID and ObjectName and, if I check in the database, ObjectID 1301579675 does indeed belong to the stored procedure FireCacheEvents.</p>
<p>The second event (the second cache miss) has the same ObjectID and ObjectName. So this is the failed cache lookup for the stored procedure (failed because this is the first time I ran it and the plan was, as a result, not in cache).</p>
<p>The first entry is the one that&#8217;s curious. The ObjectName is not populated and the ObjectID doesn&#8217;t match anything in the database. So what is the cache lookup trying to find?</p>
<p>The TextData column give a hint. What it&#8217;s trying to find is a cached plan for the ad-hoc batch containing the whole of the text submitted to SQL (In this case just &#8216;EXEC FireCacheEvents&#8217;). This too could contain queries (though it doesn&#8217;t in this case) and needs a plan lookup. With just an EXEC in the batch, it won&#8217;t have a plan and hence won&#8217;t be found in cache, but there&#8217;s still a lookup for it.</p>
<p>If there was any SELECT, INSERT, UPDATE or DELETE (or MERGE on SQL 2008) statement within that ad-hoc batch, the batch would also be cached and future cache lookups would succeed but, since it&#8217;s just an EXEC, there&#8217;s no plan to cache.</p>
<p>This additional cache lookup won&#8217;t occur if the execution is via RPC (e.g. A .Net call with the SQLCommand.CommandType = CommandType.StoredProcedure) but it will any time there is an ad-hoc SQL batch submitted (e.g. from Management Studio or .Net if the SQLCommand.CommandType is set to CommandType.Text)</p>
<p>That&#8217;s ad-hoc batches and stored procs. Let&#8217;s try the third possibility &#8211; a parameterised query.</p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2010/07/CacheMissparameterised.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="CacheMiss parameterised" src="http://sqlinthewild.co.za/wp-content/uploads/2010/07/CacheMissparameterised_thumb.png" border="0" alt="CacheMiss parameterised" width="496" height="61" /></a></p>
<p>This looks semi-familiar. The first CacheMiss is again for the entire of the ad-hoc batch (consisting of the DECLARE and the EXEC in this case). The second looks similar, something in TextData and no ObjectName. This is the parameterised query, identifiable as parameterised by the (@Status Char(1)) parameter definition right at the beginning. Again here the ObjectID is a hash of the text and doesn&#8217;t match to a real object in the database.</p>
<p>It is worth noting that, if both the CacheMiss and CacheInsert events are traced, the CacheMiss events will only appear if there is no subsequent CacheInsert for the same ObjectID. If I ran the exact same script as above, but had the SP:Completed, SP:CacheMiss and SP:CacheInsert events in the trace, there would still be only three events recorded, the cache miss for the ad-hoc batch (as that&#8217;s not cached there&#8217;s no matching CacheInsert event), the CacheInsert for the stored proc and then the SP:Completed. The CacheMiss for the procedure wouldn&#8217;t appear, though it&#8217;s presence can be intuited from the presence of the CacheInsert.</p>
<p>That should about cover it for the CacheMiss. If the failed cache lookup is for is an ad-hoc batch or parameterised query, the TextData column will be populated with the contents of the batch or query and the ObjectID will be a hash of the text (and shouldn&#8217;t match any object in the database). If the failed cache lookup is for a procedure (or function or trigger), the object name column is usually populated (not always) with the name of the object, TextData is blank and the ObjectID matches the ObjectID of the object in the database</p>
<h3>CacheHit</h3>
<p>Onto the CacheHit event. This, as its name implies, is the opposite to the CacheMiss. The CacheMiss indicates that a lookup to the plan cache failed to find a matching plan. The CacheHit indicates that a lookup to the plan cache succeeded in finding a matching plan (based on object id, set options, maybe user, and various other conditions).</p>
<p>It&#8217;s not certain, even if the cache lookup succeeds, that the plan will indeed be used for the execution of the query/batch/procedure as there are a number of stability and optimality related checks that will be done before the plan is used.</p>
<p>So let&#8217;s see how this event looks.</p>
<pre class="brush: sql;">EXEC FireCacheEvents
GO

SELECT ID, SomeDate, Status
FROM dbo.TestingCacheEvents
WHERE Status = 'B'
GO </pre>
<p>Two ad-hoc batches, first with just a stored procedure call, second with just an ad-hoc SQL statement.</p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2010/07/CacheHit.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="CacheHit" src="http://sqlinthewild.co.za/wp-content/uploads/2010/07/CacheHit_thumb.png" border="0" alt="CacheHit" width="492" height="85" /></a></p>
<p>No big surprises here, not after looking at the CacheMiss events. The first is the CacheMiss for the first of the ad-hoc batches, the one with just the EXEC in it. Since that is not cached, there will be a CacheMiss every time that executes.</p>
<p>The first of the CacheHit events is for the stored procedure. As with the CacheMiss, this CacheHit for the stored procedure has an ObjectID that matches the ObjectID for that procedure in the database, and the ObjectName column is populated with (surprise) the object name while the TextData is blank.</p>
<p>The second CacheHit is for the ad-hoc batch with the SELECT statement. As is probably expected by this point, the ObjectID there is just a hash of the text, the ObjectName is blank and the TextData is populated with the full text of the batch.</p>
<h3>In Conclusion</h3>
<p>If you&#8217;re monitoring cache usage with the CacheMiss and CacheHit events, there are two different ways to identify what the lookup was looking for.</p>
<p>If the lookup was for the plan of a stored procedure, trigger or function, the ObjectID column contains a value that matches the ObjectID for that object in the database. The TextData column is blank and the ObjectName column is (usually) populated with the name of the object. I did encounter a couple of cases where the ObjectName was blank for a CacheMiss event for a stored procedure, not quite sure why. More investigation is necessary.</p>
<p>If the lookup was for the plan of an ad-hoc batch or parameterised query, the ObjectID contains a meaningless value, the ObjectName column is blank and the TextData contains the entire of the batch/query.</p>
<p>It may also be worth mentioning that the DatabaseID column is populated for all CacheMiss and CacheHit events, regardless of what the lookup is looking for. Additionally the DatabaseName column is populated for all CacheHit events (but is not an available column for the CacheMiss)</p>
<p>(1) For anyone who wants more information, there are two excellent resources available from Microsoft:</p>
<ul>
<li><a href="http://technet.microsoft.com/en-gb/library/cc966425.aspx">Batch Compilation, Recompilation, and Plan Caching Issues in SQL Server 2005</a></li>
<li><a href="http://msdn.microsoft.com/en-us/library/ee343986.aspx">Plan Caching in SQL Server 2008</a></li>
</ul>
<p>Both go extremely deep into caching, what influences matching of plans, plan reuse, recompilations and the plan cache itself.</p>
<p>Reproduction code:</p>
<pre class="brush: sql;">CREATE TABLE TestingCacheEvents (
 ID INT IDENTITY PRIMARY KEY,
 SomeDate DATETIME,
 Status CHAR(1),
 Filler CHAR(300) DEFAULT ' '
);
GO

INSERT INTO TestingCacheEvents (SomeDate, Status)
SELECT TOP (10000)
 DATEADD(dd,FLOOR(RAND(a.object_id+b.column_id*5000)*500),'2000/01/01'),
 CHAR(65+FLOOR(RAND(b.object_id+a.column_id*5000)*10))
 FROM master.sys.columns a CROSS JOIN master.sys.columns b;
GO

CREATE PROCEDURE FireCacheEvents
AS
 SELECT ID, SomeDate, Status
 FROM TestingCacheEvents
 WHERE Status = 'G'

GO</pre>
]]></content:encoded>
			<wfw:commentRss>http://sqlinthewild.co.za/index.php/2010/07/27/hit-and-miss/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Are trivial plans cached?</title>
		<link>http://sqlinthewild.co.za/index.php/2009/12/08/are-trivial-plans-cached/</link>
		<comments>http://sqlinthewild.co.za/index.php/2009/12/08/are-trivial-plans-cached/#comments</comments>
		<pubDate>Tue, 08 Dec 2009 15:00:28 +0000</pubDate>
		<dc:creator>Gail</dc:creator>
				<category><![CDATA[Execution Plans]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Syndication]]></category>

		<guid isPermaLink="false">http://sqlinthewild.co.za/?p=495</guid>
		<description><![CDATA[It is sometimes said that trivial execution plans are not cached and queries that have such plans are compiled on every execution. So is that true? To effectively answer this question, we must first establish what a trivial plan is. A trivial plan is essentially a plan for a query where a specific plan will [...]]]></description>
			<content:encoded><![CDATA[<p>It is sometimes said that trivial execution plans are not cached and queries that have such plans are compiled on every execution. So is that true? To effectively answer this question, we must first establish what a trivial plan is.</p>
<p>A trivial plan is essentially a plan for a query where a specific plan will always be the most optimal way of executing it. If we consider something like SELECT * FROM SomeTable then there&#8217;s only one real way to execute it, a scan of the cluster/heap.</p>
<p>The trivial plan is somewhat of a query optimiser optimisation. If the query qualifies for a trivial plan (and there are lots of restrictions) then the full optimisation process doesn&#8217;t need to be started and so the query&#8217;s execution plan can be generated quicker and with less overhead. The fact that a query has a trivial plan at one point doesn’t necessarily mean that it will always have a trivial plan, indexes may be added that make the selection of plan less of a sure thing and so the query must go for full optimisation, rather than getting a trivial plan</p>
<p>Nice theory, but how does one tell if a particular query has a trivial execution plan? The information is found within the execution plan, the properties of the highest-level operator has an entry &#8216;Optimisation level&#8217; For a trivial plan this will read ‘TRIVIAL’</p>
<p><img style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" title="Trivial plan" src="http://sqlinthewild.co.za/wp-content/uploads/2009/12/Trivialplan.png" border="0" alt="Trivial plan" width="270" height="362" /></p>
<p><span id="more-495"></span>It&#8217;s also, for the brave people who like XML, found within the xml form of the plan</p>
<pre class="brush: xml;">&lt;StmtSimple StatementCompId='1' StatementEstRows='11' StatementId='1' StatementOptmLevel='TRIVIAL' StatementSubTreeCost='0.0032941' StatementText='SELECT * FROM forums' StatementType='SELECT' QueryHash='0xB38EBF594006422E' QueryPlanHash='0xCC9AB99E7081C81D'&gt;
&lt;!-- most of the rest of the plan here --&gt;
&lt;/StmtSimple&gt;</pre>
<p>So, are they cached? The way to find that out is to run a variety of queries and see what&#8217;s sitting in the cache afterwards.</p>
<p>I&#8217;m going to clear the procedure cache then run a couple queries against the AdventureWorks database, checking the graphical execution plan for each one. After they&#8217;ve all been run, I&#8217;ll query the plan cache and check the optimisation level of the cached plans,</p>
<p>Query 1:</p>
<pre class="brush: sql;">SELECT FirstName, LastName
    FROM Person.Person
    WHERE BusinessEntityID = 42</pre>
<p>According to the graphical plan, this query has a trivial plan.</p>
<p>Query 2:</p>
<pre class="brush: sql;">SELECT TOP (10) Name FROM Production.Product</pre>
<p>According to the graphical plan, this query also has a trivial plan.</p>
<p>Query 3:</p>
<pre class="brush: sql;">SELECT * FROM Sales.SalesOrderHeader sh
    INNER JOIN sales.SalesOrderDetail sd
        ON sh.SalesOrderID = sd.SalesOrderID
    WHERE sh.ShipDate &gt; '2008/05/25'</pre>
<p>According to the graphical plan, this query has a non-trivial plan, the optimisation level is listed as full.</p>
<pre class="brush: sql;">SELECT st.text, qp.query_plan,
     qp.query_plan.value('
       declare default element
       namespace &quot;http://schemas.microsoft.com/sqlserver/2004/07/showplan&quot;;
       (//StmtSimple/@StatementOptmLevel)[1]','varchar(20)') AS OptimisationLevel
    FROM sys.dm_exec_cached_plans cp
        CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp
        CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
    WHERE text not like '%sys.dm_exec_cached_plans%'</pre>
<p>This returns 4 rows. All three of the queries that I ran against AdventureWorks, two with optimisation levels of Trivial, one with an optimisation level of Full, and the unparameterised ‘shell’ of the first of the queries.</p>
<p>So it appears that some trivial plans are indeed cached.</p>
]]></content:encoded>
			<wfw:commentRss>http://sqlinthewild.co.za/index.php/2009/12/08/are-trivial-plans-cached/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Estimated rows, actual rows and execution count</title>
		<link>http://sqlinthewild.co.za/index.php/2009/09/22/estimated-rows-actual-rows-and-execution-count/</link>
		<comments>http://sqlinthewild.co.za/index.php/2009/09/22/estimated-rows-actual-rows-and-execution-count/#comments</comments>
		<pubDate>Tue, 22 Sep 2009 10:00:48 +0000</pubDate>
		<dc:creator>Gail</dc:creator>
				<category><![CDATA[Execution Plans]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Syndication]]></category>

		<guid isPermaLink="false">http://sqlinthewild.co.za/?p=294</guid>
		<description><![CDATA[It&#8217;s often said that a major discrepancy between estimated and actual row counts in a query&#8217;s execution plan is a sign of inaccurate statistics or a poor cardinality estimate and that it&#8217;s a sign of a problem. This is generally true, however there are places where the estimates and actual rows will differ, often quite [...]]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s often said that a major discrepancy between estimated and actual row counts in a query&#8217;s execution plan is a sign of inaccurate statistics or a poor cardinality estimate and that it&#8217;s a sign of a problem. This is generally true, however there are places where the estimates and actual rows will differ, often quite dramatically, without it been a problem. The reason for this is that these two values show slightly different things.</p>
<p>Let&#8217;s take a look at an example. (table creation code at the end of the post)</p>
<pre class="brush: sql;">select bt.id, bt.SomeColumn, st.SomeArbDate
from dbo.BigTable bt
inner join dbo.SmallerTable st on bt.SomeColumn = st.LookupColumn
where bt.id between 5000 and 5100</pre>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2009/09/EstimatedActual.png"><img class="alignnone size-medium wp-image-338" style="border: 1px solid black;" title="Estimated Actual discrepency" src="http://sqlinthewild.co.za/wp-content/uploads/2009/09/EstimatedActual-300x213.png" alt="Estimated Actual discrepency" width="300" height="213" /></a></p>
<p>Estimated rows = 1, actual rows = 101. That&#8217;s a large discrepancy, but what caused it? It&#8217;s not out of date statistics (a usual cause) because the table has only just been created, so why is the estimation so far from the actual.</p>
<p><span id="more-294"></span>Let&#8217;s take a closer look at that seek. The seek predicate is an equality match on LookupColumn. That can only return 1 row because when the table was populated it was populated with unique values for that column (though the index on that column is not defined unique). So the estimated row count is dead-on, the index seek will return  a single row. Now the question is where that 101 for the actual comes from.</p>
<p>This seek is on the inner table of a nested loop join. The way the nested loop join works is to query the outer table of the join and then to query the inner table once for each row returned by the outer table. A look at the details of the clustered index scan that defines the outer table of the nested loop shows that it returns 101 rows.</p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2009/09/OuterTable.png"><img class="alignnone size-medium wp-image-340" style="border: 1px solid black;" title="OuterTable" src="http://sqlinthewild.co.za/wp-content/uploads/2009/09/OuterTable-300x205.png" alt="OuterTable" width="300" height="205" /><br />
</a></p>
<p>Since the outer table returns 101 rows, the seek on the inner table must be done 101 times. That&#8217;s supported by the execution count shown on the inner seek. That&#8217;s where the discrepancy between actual and estimated rows comes from.</p>
<p>When an operator is executed multiple times as part of the query execution, the estimated row count refers to the number of rows that the optimiser estimates will be affected per execution. The actual row count refers to the total number of rows that the operator affected, cumulative over all executions. So when checking to see if there&#8217;s a major discrepancy between estimated and actual rows counts, the actual row count has to be divided by the number of executions.</p>
<p>That&#8217;s fine when using SQL 2008&#8242;s management studio, which exposes the execution count of an operator in the tooltip of that operator. SQL 2005&#8242;s management studio did not display the execution count anywhere convenient, though it is present in the XML of the plan. This is purely a feature of the version of Management Studio. The SQL 2008 management studio will display the execution count regardless of whether it&#8217;s connected to SQL 2005 or to SQL 2008.</p>
<p>For those still using SQL 2005&#8242;s tools, if you want the execution count, this is where to look:</p>
<pre class="brush: xml;">&lt;RelOp AvgRowSize=&quot;15&quot; EstimateCPU=&quot;0.0001581&quot; EstimateIO=&quot;0.003125&quot; EstimateRebinds=&quot;85.432&quot; EstimateRewinds=&quot;0&quot; EstimateRows=&quot;1&quot; LogicalOp=&quot;Index Seek&quot; NodeId=&quot;2&quot; Parallel=&quot;false&quot; PhysicalOp=&quot;Index Seek&quot; EstimatedTotalSubtreeCost=&quot;0.0480212&quot; TableCardinality=&quot;3956&quot;&gt;
&lt;OutputList&gt;
&lt;ColumnReference Database=&quot;[Testing]&quot; Schema=&quot;[dbo]&quot; Table=&quot;[SmallerTable]&quot; Alias=&quot;[st]&quot; Column=&quot;SomeArbDate&quot; /&gt;
&lt;/OutputList&gt;
&lt;RunTimeInformation&gt;
&lt;RunTimeCountersPerThread Thread=&quot;0&quot; ActualRows=&quot;101&quot; ActualEndOfScans=&quot;101&quot; ActualExecutions=&quot;101&quot; /&gt;
&lt;/RunTimeInformation&gt;</pre>
<p>The number of executions, along with the actual row count is contained within the XML node RunTimeInformation. Obviously this node will not be present when looking at an estimated execution plan or an execution plan retrieved from the plan cache, as neither contains any run-time information.</p>
<p>Table creation code.</p>
<p>Edit: In the original post I left 2 indexes out of the table creation, which changed the behaviour of the query completely (hash joins instead of nested loop). If you tried to reproduce my results and couldn&#8217;t, you should be able to now with the correct indexes.</p>
<pre class="brush: sql;">Create Table BigTable (
id int identity primary key,
SomeColumn char(4),
Filler char(100)
)

Create Table SmallerTable (
id int identity primary key,
LookupColumn char(4),
SomeArbDate Datetime default getdate()
)

INSERT INTO BigTable (SomeColumn)
SELECT top 250000
char(65+FLOOR(RAND(a.column_id *5645 + b.object_id)*10)) + char(65+FLOOR(RAND(b.column_id *3784 + b.object_id)*12)) +
char(65+FLOOR(RAND(b.column_id *6841 + a.object_id)*12)) + char(65+FLOOR(RAND(a.column_id *7544 + b.object_id)*8))
from master.sys.columns a cross join master.sys.columns b

INSERT INTO SmallerTable (LookupColumn)
SELECT DISTINCT SomeColumn
FROM BigTable TABLESAMPLE (25 PERCENT)
GO

CREATE NONCLUSTERED INDEX [idx_BigTable_SomeColumn]
  ON [dbo].[BigTable] ([SomeColumn] ASC)
GO

CREATE NONCLUSTERED INDEX [idx_SmallerTable_LookupColumn]
  ON [dbo].[SmallerTable] ([LookupColumn] ASC)
  INCLUDE ( [SomeArbDate])
GO
</pre>
<div id="_mcePaste" style="overflow: hidden; position: absolute; left: -10000px; top: 535px; width: 1px; height: 1px;">From    Subject    Received    Size<br />
Gideon van Zyl &#8211; XE    C-Track info/docs 4 SAPS    15:53    24 KB</div>
]]></content:encoded>
			<wfw:commentRss>http://sqlinthewild.co.za/index.php/2009/09/22/estimated-rows-actual-rows-and-execution-count/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Estimated and Actual execution plan revisited</title>
		<link>http://sqlinthewild.co.za/index.php/2009/02/19/estimated-and-actual-execution-plan-revisited/</link>
		<comments>http://sqlinthewild.co.za/index.php/2009/02/19/estimated-and-actual-execution-plan-revisited/#comments</comments>
		<pubDate>Thu, 19 Feb 2009 17:42:01 +0000</pubDate>
		<dc:creator>Gail</dc:creator>
				<category><![CDATA[Execution Plans]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Syndication]]></category>

		<guid isPermaLink="false">http://sqlinthewild.co.za/?p=211</guid>
		<description><![CDATA[After an interesting discussion on SQLServerCentral last week, I realised that the terms &#8216;estimated execution plan&#8217; and &#8216;actual execution plan&#8217; are perhaps a little bit misleading. The only thing estimated about the estimated execution plan is the rowcounts, costs and row size. The plan itself isn&#8217;t an estimate. It&#8217;s not as if the optimiser, when [...]]]></description>
			<content:encoded><![CDATA[<p>After an <a href="http://www.sqlservercentral.com/Forums/Topic655518-360-1.aspx">interesting discussion</a> on SQLServerCentral last week, I realised that the terms &#8216;estimated execution plan&#8217; and &#8216;actual execution plan&#8217; are perhaps a little bit misleading.</p>
<p>The only thing estimated about the estimated execution plan is the rowcounts, costs and row size. The plan itself isn&#8217;t an estimate. It&#8217;s not as if the optimiser, when asked for an estimated plan, does a less thorough job than when asked to compile a plan for a query&#8217;s execution.</p>
<p>The two forms of execution plan are better described as &#8216;execution plan with run-time information&#8217; and &#8216;execution plan without run-time information&#8217;</p>
<p>When, in Management Studio, someone clicks the &#8216;display estimated execution plan&#8217; button, the query is submitted to SQL Server, parsed and bound, algebratised and optimised just as if it was going to be executed. But the query is not executed, and as such, the plan when returned contains no run time information.</p>
<p>If there is a matching cached query plan, that cached plan is what&#8217;s returned and no optimisation is done. This can be seen by using profiler with the Cache hit, cache miss and cache insert events being traced.</p>
<p>When, in Management Studio, the query is run with the execution plan option enabled, the query is submitted to SQL Server, parsed and bound, algebratised, optimised and executed. The returned plan does contain the run-time for that specific execution, hence the plan contains things like &#8216;actual row count, actual IO cost&#8217;, etc</p>
<p>If there&#8217;s a matching query plan in cache then that cached plan will be used for the query&#8217;s execution and will be the one returned, though with the run-time information added</p>
<p>When a plan is cached, only the compile-time information is cached. The detailed run-time information on the actual number of rows, costs and the like is (I believe) discarded after updating the aggregated query stats. Hence, when you retrieve a query from the plan cache, it will not contain the run-time information. Consider a plan that&#8217;s been used 20 times. Which execution&#8217;s run-time information would it contain? Remember that there&#8217;s only one plan in cache per procedure.</p>
<p>Hence, a plan fetched from will be identical to the plan returned by requesting the estimated execution plan for a specific query (Providing there&#8217;s nothing happening to invalidate the cached plan)</p>
<p>Profiler can capture (depending on event) the plan without the run-time information or the plan with the run-time information. There&#8217;s a nice table in chapter 2 of one of <a href="http://www.amazon.com/Inside-Microsoft-SQL-Server-2005/dp/0735623139/ref=sr_1_1?ie=UTF8&amp;s=books&amp;qid=1235065233&amp;sr=8-1">Itzik&#8217;s books</a> that shows the various profiler events, when they fire and what they return.</p>
]]></content:encoded>
			<wfw:commentRss>http://sqlinthewild.co.za/index.php/2009/02/19/estimated-and-actual-execution-plan-revisited/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>A Bookmark lookup, by any other name&#8230;</title>
		<link>http://sqlinthewild.co.za/index.php/2009/01/27/a-bookmark-lookup-by-any-other-name/</link>
		<comments>http://sqlinthewild.co.za/index.php/2009/01/27/a-bookmark-lookup-by-any-other-name/#comments</comments>
		<pubDate>Tue, 27 Jan 2009 16:53:52 +0000</pubDate>
		<dc:creator>Gail</dc:creator>
				<category><![CDATA[Execution Plans]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Syndication]]></category>

		<guid isPermaLink="false">http://sqlinthewild.co.za/?p=199</guid>
		<description><![CDATA[I think I may have confused some people by talking about bookmark lookups. I&#8217;ll attempt to clarify things. The operator that I&#8217;m talking about is the one that fetches extra columns from the clustered index when the nonclustered index that&#8217;s used to retrieve the rows doesn&#8217;t have all of the columns required. In SQL 2000, [...]]]></description>
			<content:encoded><![CDATA[<p>I think I may have confused some people by talking about bookmark lookups. I&#8217;ll attempt to clarify things.</p>
<p>The operator that I&#8217;m talking about is the one that fetches extra columns from the clustered index when the nonclustered index that&#8217;s used to retrieve the rows doesn&#8217;t have all of the columns required.</p>
<p>In SQL 2000, that operator appeared in the execution plan as a bookmark lookup and it appeared as follows:</p>
<p><img class="alignnone size-full wp-image-200" title="bookmark-lookup" src="http://sqlinthewild.co.za/wp-content/uploads/2009/01/bookmark-lookup.png" alt="" width="163" height="85" /></p>
<p>In SQL 2005, the name was changed, and the bookmark lookup no longer appeared in the execution plan. In it&#8217;s place appeared a clustered index seek, joined back to the original index seek by a nested loop join. It appeared as follows (the highlighted operator is the &#8216;bookmark lookup&#8217;)</p>
<p><img class="alignnone size-full wp-image-201" title="clustered-index-seek" src="http://sqlinthewild.co.za/wp-content/uploads/2009/01/clustered-index-seek.png" alt="" width="375" height="186" /></p>
<p>This change made it harder to see what was going on as clustered index seeks are usually considered &#8216;good&#8217;. The trick to see if it is actually a &#8216;bookmark lookup&#8217; is to look at the objects involved. When the nonclustered index seek and the clustered index seek are both on the same table, then it&#8217;s a &#8216;bookmark lookup&#8217;</p>
<p>I don&#8217;t recall what this appeared as when the base table was a heap, not a cluster.</p>
<p>In SQL 2005 SP2, the name of the operator was changed again, now appearing as a key lookup (when the base table has a clustered index) or a RID lookup (when the base table is a heap). It now looks like this:</p>
<p><img class="alignnone size-full wp-image-202" title="keylookup" src="http://sqlinthewild.co.za/wp-content/uploads/2009/01/keylookup.png" alt="" width="356" height="167" /></p>
<p>The thing to note is that it&#8217;s not the version of the server that&#8217;s important. The format of the XML for the exec plan has not changed since SQL 2005 RTM (I can and have created a .sqlplan file from SQL 2008 and opened that file in SQL 2005&#8242;s management studio).</p>
<p>It&#8217;s the version of management studio that affects how the execution plans are displayed. If the server is SQL 2005 SP3, but the client tools are still RTM, the bookmark lookup will appear as a clustered index seek. Another reason to patch the client as well as the server</p>
<p>I hope that clears up some of the confusion around the naming. So, in future, what should I refer to this as? A bookmark lookup? A Key lookup?</p>
]]></content:encoded>
			<wfw:commentRss>http://sqlinthewild.co.za/index.php/2009/01/27/a-bookmark-lookup-by-any-other-name/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Execution plan &#8211; more properties</title>
		<link>http://sqlinthewild.co.za/index.php/2008/10/06/execution-plan-more-properties/</link>
		<comments>http://sqlinthewild.co.za/index.php/2008/10/06/execution-plan-more-properties/#comments</comments>
		<pubDate>Mon, 06 Oct 2008 20:57:46 +0000</pubDate>
		<dc:creator>Gail</dc:creator>
				<category><![CDATA[Execution Plans]]></category>
		<category><![CDATA[SQL Server]]></category>

		<guid isPermaLink="false">http://sqlinthewild.co.za/?p=131</guid>
		<description><![CDATA[I ran across a few more properties visible in the exec plan that I thought would be useful to people. So, without further ado&#8230; Some overall properties of the entire execution can be seen by selecting the highest-level operator in the plan (the one on the left-most, typically the select, insert, update or delete operator) [...]]]></description>
			<content:encoded><![CDATA[<p>I ran across a few more properties visible in the exec plan that I thought would be useful to people. So, without further ado&#8230;</p>
<p>Some overall properties of the entire execution can be seen by selecting the highest-level operator in the plan (the one on the left-most, typically the select, insert, update or delete operator) and then opening the properties window</p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2008/09/execplan-properties.png"><img class="alignnone size-medium wp-image-132" title="execplan-properties" src="http://sqlinthewild.co.za/wp-content/uploads/2008/09/execplan-properties-300x227.png" alt="" width="300" height="227" /></a></p>
<p>The first four items, the compild plan size, compile CPU, compile Time and compile Memory all refer to the optimisation process that the query went through. They indicate how much in the way of server resources was spent compiling this query and how large the cached plan is in memory.</p>
<p>This can be important when dealing with queries that recompile often or are very seldom reused</p>
<p><span id="more-131"></span>The degree of parallelism shows the actual number of processors used to execute the query. It&#8217;s only of interest if the query runs paralleled.</p>
<p>The memory grant shows the amount of memory that the query processor needed to execute the query. This is typically for things like hashs or sorts. This may be useful for debugging memory consumption issues or insufficient memory errors</p>
<p>The Optimisation Level and Reason for Early Termination describe the level to which the query was optimised. The optimiser is only permitted a certain about of time to optimise a query. That time is based on the complexity of the query. Those two properties indicate whether the optimiser was able to find a sufficiently good plan in the time allowed, or if it was forced to bail out and pick the best plan so far found. The Reason for Early Termination property is not always present</p>
<p>The section on set options shows what various connection properties were set to when the query ran. This can be very handy for seeing why some cached plans are not getting reused.</p>
<p>Finally, on SQL 2008 there are two more properties listed. Query hash and Query plan hash. For the low down on that, see this rticle by Bard Duncan &#8211; <a href="http://blogs.msdn.com/bartd/archive/2008/09/03/Query-Fingerprints-and-Plan-Fingerprints_3A00_-The-Best-New-SQL-2008-Feature-You_2700_ve-Never-Heard-Of.aspx">Query Fingerprints and Plan Fingerprints (The Best SQL 2008 Feature That You&#8217;ve Never Heard Of)</a></p>
<p>I think that&#8217;s enough theory on execution plans (please comment if you disagree). The next couple of articles in this series will be <a href="../index.php/2008/08/28/an-example-exec-plan/">examples of reading execution plans</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://sqlinthewild.co.za/index.php/2008/10/06/execution-plan-more-properties/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>An example exec plan</title>
		<link>http://sqlinthewild.co.za/index.php/2008/08/28/an-example-exec-plan/</link>
		<comments>http://sqlinthewild.co.za/index.php/2008/08/28/an-example-exec-plan/#comments</comments>
		<pubDate>Thu, 28 Aug 2008 18:33:34 +0000</pubDate>
		<dc:creator>Gail</dc:creator>
				<category><![CDATA[Execution Plans]]></category>
		<category><![CDATA[SQL Server]]></category>

		<guid isPermaLink="false">http://sqlinthewild.co.za/?p=86</guid>
		<description><![CDATA[Back to the technical posts&#8230; I&#8217;m going to take an example of a very simple execution plan that I used in a presentation a few weeks back and go over it in some detail, pointing out information that is available and what can be read from the plan. The execution plan can be downloaded (in [...]]]></description>
			<content:encoded><![CDATA[<p>Back to the technical posts&#8230;</p>
<p>I&#8217;m going to take an example of a very simple execution plan that I used in a presentation a few weeks back and go over it in some detail, pointing out information that is available and what can be read from the plan.</p>
<p>The execution plan can be downloaded (in xml format) here &#8211; <a href="http://sqlinthewild.co.za/wp-content/uploads/2008/08/execplandemo.zip">ExecPlanDem01.zip</a></p>
<p>The query that the plan came from is a very simple two table query. It&#8217;s not a very optimal plan, but that&#8217;s because I forced an index hint in order to generate a more interesting plan. Without the hint, it&#8217;s a simple exec plan with two index seeks and a nested loop join.</p>
<p><strong>Overview</strong></p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2008/08/execplanwalkthrough1.png"><img class="alignnone size-full wp-image-117" title="execplanwalkthrough_thumb1" src="http://sqlinthewild.co.za/wp-content/uploads/2008/08/execplanwalkthrough_thumb1.png" alt="" width="320" height="78" /></a></p>
<p>The first thing that can be seen from the execution plan is that most of the cost of the query is in two operations, a key lookup (formerly called a bookmark lookup) and a clustered index seek. The high cost of the key lookup is a good sign that the query is using an inappropriate index. (in a future post I&#8217;ll discuss using the exec plan to choose indexes)</p>
<p><span id="more-86"></span>The second thing that can be seen is that there is a filter operator. This again suggests that there is not an appropriate index, as this plan means that SQL will first select rows from the table and later filter out rows that aren&#8217;t needed.</p>
<p><strong>Index seek</strong></p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2008/08/execplanwalkthrough2.png"><img class="alignleft alignnone size-full wp-image-118" style="float: left; margin-left: 10px; margin-right: 10px;" title="execplanwalkthrough_thumb2" src="http://sqlinthewild.co.za/wp-content/uploads/2008/08/execplanwalkthrough_thumb2.png" alt="" width="109" height="201" /></a></p>
<p>The first thing to notice about the index seek is that there are two seek predicates, one an equality, one an inequality. Having them both as seek predicates indicated that the columns in this index are in the correct order to support the two predicates. (I&#8217;ll do an entire post sometime on index key column ordering)</p>
<p>The second thing to note is that the estimated and actual rows are identical. This is good, it indicates that the statistics on this index are up to date and that the optimiser managed to get an accurate estimate of the rows that would be returned.</p>
<p><strong>Key Lookup</strong></p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2008/08/execplanwalkthrough3.png"><img class="alignleft alignnone size-full wp-image-119" style="float: left; margin-left: 10px; margin-right: 10px;" title="execplanwalkthrough_thumb3" src="http://sqlinthewild.co.za/wp-content/uploads/2008/08/execplanwalkthrough_thumb3.png" alt="" width="169" height="198" /></a>The presence of a key lookup operator indicates that the nonclustered index that was used to locate the rows affected by the query did not have all the columns required by the query. The missing columns must be looked up from the clustered index. The output list shows what columns were fetched by the key lookup. This can help when altering indexes to get a covering index.</p>
<p>One important thing to note is that the estimated rows for this operator is 1. That&#8217;s not a cardinality problem, it&#8217;s because the estimated rows is per execution of the operator and the actual rows is total over all executions. The number of times an operator is executed is stored within the XML, however the SQL 2005 management studio doesn&#8217;t show it. Management studio in SQL 2008 does.</p>
<p>This shows one of the reasons why the key lookup can be a bottleneck. For each row returned by the index seek, the key lookup does a clustered index seek returning a single row. For a small number of rows, that&#8217;s not too much or a problem, for hundreds of rows it can be a major bottleneck in the query.</p>
<p><strong>Filter</strong></p>
<p><a href="http://sqlinthewild.co.za/wp-content/uploads/2008/08/execplanwalkthrough4.png"><img class="alignleft size-full wp-image-120" style="margin-left: 10px; margin-right: 10px; float: left;" title="execplanwalkthrough_thumb4" src="http://sqlinthewild.co.za/wp-content/uploads/2008/08/execplanwalkthrough_thumb4.png" alt="" width="159" height="240" /></a></p>
<p>The last interesting part of the execution plan is the filter.</p>
<p>Having a filter in the exec plan indicates that there was some condition that could not be evaluated as part of the index seek/scan. This may be because in involves an aggregate, because it involves columns in multiple tables or, as in this case, because the column was not part of the index and had to be looked up separatly.</p>
<p>As with the index seek, the estimated and actual row counts are very similar, indicating that the cardinality estimate is accurate for this query.</p>
<p>So, that&#8217;s most of the interesting things that can be gleaned from the exec plan. I have a couple more posts planned in the series on reading exec plans, one showing a more complex plan and the other going into more detail on the % costs that the plan shows.</p>
<p>That&#8217;s all for now. I&#8217;ll see if &#8216;i can dig up a more complex plan to work through.</p>
]]></content:encoded>
			<wfw:commentRss>http://sqlinthewild.co.za/index.php/2008/08/28/an-example-exec-plan/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Execution plans &#8211; important properties</title>
		<link>http://sqlinthewild.co.za/index.php/2008/04/23/execution-plans-important-properties/</link>
		<comments>http://sqlinthewild.co.za/index.php/2008/04/23/execution-plans-important-properties/#comments</comments>
		<pubDate>Wed, 23 Apr 2008 19:57:28 +0000</pubDate>
		<dc:creator>Gail</dc:creator>
				<category><![CDATA[Execution Plans]]></category>
		<category><![CDATA[SQL Server]]></category>

		<guid isPermaLink="false">http://sqlinthewild.co.za/index.php/2008/04/23/execution-plans-important-properties/</guid>
		<description><![CDATA[Time I wrote another piece on execution plans (see the first post for the full list) So, now a quick look at some of the important properties of exec plan operators. Estimated number of rows &#8211; This is the number of rows that the query optimiser estimates this operator will process. The estimate is based [...]]]></description>
			<content:encoded><![CDATA[<p>Time I wrote another piece on execution plans (see the <a href="http://sqlinthewild.co.za/index.php/2007/08/20/reading-execution-plans/">first post</a> for the full list)</p>
<p>So, now a quick look at some of the important properties of exec plan operators.</p>
<p><img src="http://sqlinthewild.co.za/images/OperatorProperties1.PNG" alt="" /></p>
<ul>
<li>Estimated number of rows &#8211; This is the number of rows that the query optimiser estimates this operator will process. The estimate is based on the statistics and on other data available at time of compilation. It&#8217;s possible that this number includes a fraction, due to the way to optimiser does its estimates</li>
<li>Actual number of rows &#8211; This is the actual number of rows that were processed by the operator. This value is calculated by the query processor at execution time. An estimated execution plan will not include this.<span id="more-69"></span></li>
<li>Estimated row size &#8211; This is the estimated, average data size of each row passing through this operation. This, along with the number of rows affects the memory allocations necessary and show the size of the data chunk that the query operators are processing.</li>
<li>Seek predicates &#8211; Only present on an index seek, This shows what criteria were used in the index seek.</li>
<li>Object &#8211; Only present on seeks, scans and lookups. This shows the database and table names</li>
<li>Output list &#8211; Only for seeks, scans and lookups. This shows the columns returned by this operation. This can be very useful when trying to reduce bookmark/key/RID lookups</li>
<li>Predicate &#8211; This may be present in seeks or scans. This shows an additional filter done during the seek/scan. On a seek this is a predicate applied after the seek predicate and may indicate that the expression listed is not SARGable, or that the index collumns are not in the optimal order for this predicate.</li>
</ul>
<p><img src="http://sqlinthewild.co.za/images/OperatorProperties2.PNG" alt="" /></p>
<ul>
<li>Estimated data size &#8211; This appears as a property of the data flow between operations. It&#8217;s an estimate of the total size of the data at this point in the query execution process.</li>
</ul>
<p>Next up, a few more <a href="http://sqlinthewild.co.za/index.php/2008/10/06/execution-plan-more-properties/">exec plan properties</a></p>
]]></content:encoded>
			<wfw:commentRss>http://sqlinthewild.co.za/index.php/2008/04/23/execution-plans-important-properties/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Execution plan operations &#8211; misc</title>
		<link>http://sqlinthewild.co.za/index.php/2008/03/30/execution-plan-operations-misc/</link>
		<comments>http://sqlinthewild.co.za/index.php/2008/03/30/execution-plan-operations-misc/#comments</comments>
		<pubDate>Sun, 30 Mar 2008 18:41:33 +0000</pubDate>
		<dc:creator>Gail</dc:creator>
				<category><![CDATA[Execution Plans]]></category>
		<category><![CDATA[SQL Server]]></category>

		<guid isPermaLink="false">http://sqlinthewild.co.za/index.php/2008/03/30/execution-plan-operations-misc/</guid>
		<description><![CDATA[This is going to be the last post on the operators in an execution plan. I&#8217;m not going to cover all the other operators. There are a lot of operators, many quite specialised or only appearing in specific scenarios. Conor Cunningham&#8217;s writing a series covering the deep, dark details of some of the operations So [...]]]></description>
			<content:encoded><![CDATA[<p>This is going to be the last post on the operators in an execution plan. I&#8217;m not going to cover all the other operators. There are a lot of operators, many quite specialised or only appearing in specific scenarios. Conor Cunningham&#8217;s writing a <a href="http://www.sqlskills.com/blogs/conor/CategoryView,category,query%2Boperators.aspx">series covering the deep, dark details of some of the operations</a></p>
<p>So onto the operators. I&#8217;m going to cover Sort, Concatenate and Scalar Function.</p>
<p><strong>Sort</strong></p>
<p>Sort has three logical operators:</p>
<ul>
<li>Sort</li>
<li>Distinct Sort</li>
<li>Top-N Sort</li>
</ul>
<p>Sort is used to satisfy any ORDER BY that is specified in a query, if the index used does not naturally sort the rows in the desired way. It also may appear in the execution plan before a merge join or a stream aggregate. Sometimes SQL may decide that a sort followed by a merge join or stream aggregate is cheaper than a hash join or hash aggregate.</p>
<p>Distinct sort is used for DISTINCT, sometimes for UNION and sometimes for GROUP BY where no aggregates are specified.</p>
<p><span id="more-61"></span><strong>Concatenate</strong></p>
<p>Concatenate is used to append one rowset to another. It mainly appears in queries using UNION or UNION ALL.</p>
<p><strong>Scalar Function</strong></p>
<p>The scalar function operator is an innocuous loosing operator that can conceal a great deal of problems. It&#8217;s used for all forms of scalar functions, from UPPER, SUBSTRING, ROUND and the like, to complex user-defined scalar functions that affect a million of so records each time they&#8217;re called.</p>
<p>I&#8217;ll go into more detail on the problems of Scalar Function in another article, but in short, the relative cost displayed in the execution plan cannot be trusted, and from the exec plan there&#8217;s no way to see what the function is doing. It may be something simple, or it may be a custom function aggregating thousands of rows.</p>
<p>That&#8217;s pretty much that on the major operators of an execution plan. In the next article, I&#8217;ll discuss some of the <a href="http://sqlinthewild.co.za/index.php/2008/04/23/execution-plans-important-properties/">common properties</a> of the execution plan operators.</p>
]]></content:encoded>
			<wfw:commentRss>http://sqlinthewild.co.za/index.php/2008/03/30/execution-plan-operations-misc/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
