<?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>It Depends</title>
	<atom:link href="http://www.barkingaardvark.com/sqlblog/?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://www.barkingaardvark.com/sqlblog</link>
	<description>A blog about SQL and MS SQL Server</description>
	<lastBuildDate>Thu, 01 Dec 2011 16:05:02 +0000</lastBuildDate>
	
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>The Perils of User Defined Functions</title>
		<link>http://www.barkingaardvark.com/sqlblog/?p=108</link>
		<comments>http://www.barkingaardvark.com/sqlblog/?p=108#comments</comments>
		<pubDate>Thu, 01 Dec 2011 16:05:02 +0000</pubDate>
		<dc:creator>dean</dc:creator>
				<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://www.barkingaardvark.com/sqlblog/?p=108</guid>
		<description><![CDATA[As of this writing, I am working in an environment in which things are much degraded. The company grew very fast, grew too big, didn&#8217;t have processes in place but instead threw money at problems in the form of hiring more developers to write more undocumented code&#8230;
Sound familiar?
Then the shit hit the fan and growth [...]]]></description>
			<content:encoded><![CDATA[<p>As of this writing, I am working in an environment in which things are much degraded. The company grew very fast, grew too big, didn&#8217;t have processes in place but instead threw money at problems in the form of hiring more developers to write more undocumented code&#8230;</p>
<p>Sound familiar?</p>
<p>Then the shit hit the fan and growth flattened. Typically when that happens the inefficiencies catch up with you, and they did in this case. There was a massive series of layoffs and the company now employs only about 20% of the headcount it did 4 years ago.</p>
<p>Sound familiar? </p>
<p>If so, you know the sort of mess that resulted. For example, as part of a cleanup, I found a process that dutifully loaded data into a table every day. It had been doing so, without error, for 10 years. That data had never been read. Never been cleaned out. Nobody used it. For 10 years.</p>
<p>There were, and are, some brutally inefficient queries running. Really serious beat-the-shit-out-of-the-server stuff, including one that averaged 1.3 BILLION page reads per execution.</p>
<p>Over time, we have improved things, chopping the heads off the hydra one at a time. </p>
<p>I came to this one last week. After kicking off, it runs for an average of 28 minutes on a server with 384 Gb of RAM and 4 &#8211; 8 core processors. It doesn&#8217;t read all that many pages (considering) &#8211; only 60 million pages, which, believe it or not, isn&#8217;t all that bad compared to others that are running.</p>
<p>Digging down into the code, I found this:</p>
<div class="dean_ch" style="white-space: wrap;">
<span class="kw1">FROM</span> &nbsp; dbo.<span class="me1">factObscuredTbl</span> f<br />
<span class="kw1">WHERE</span> &nbsp;Warehouse.<span class="me1">dbo</span>.<span class="me1">UTCTimeConvertToPT</span><span class="br0">&#40;</span>f.<span class="me1">CreatedUTCDtm</span><span class="br0">&#41;</span> &gt;= @BeginDate<br />
AND &nbsp; &nbsp;Warehouse.<span class="me1">dbo</span>.<span class="me1">UTCTimeConvertToPT</span><span class="br0">&#40;</span>f.<span class="me1">CreatedUTCDtm</span><span class="br0">&#41;</span> &lt; &nbsp;@EndDate<br />
&nbsp;</div>
<p>So what a developer had done was to use a UDF to convert a UTC Datetime to local so that this time could be compared to the calculated start and end dates. </p>
<p>This is bad in two ways &#8211; first, when you wrap a column reference in a function, the engine cannot know what the result of that function will be until run time. This means that it cannot make use of an index, should one exist. In this case it didn&#8217;t, and the query was performing a table scan (because there is no clustered index on this table, which is a separate issue).</p>
<p>But that&#8217;s not the worst part of this. The query must run the function for each row it encounters, then compare that result to the two variables. </p>
<p>There are 45 million rows in this table. That means that in order to find all the rows that qualify, the engine must scan the entire table and apply the date conversion function 45 million times. This, somewhat understandably, takes time, and that is why the stored procedure that this is part of was taking 25 minutes to execute every day.</p>
<p>I hope that the solution is obvious, although none of the developers here seemed to have stumbled on it. Calculate @BeginDate and @EndDates in UTC and then use those dates to compare to the native column:</p>
<div class="dean_ch" style="white-space: wrap;">
<span class="kw1">FROM</span> &nbsp; dbo.<span class="me1">factObscuredTbl</span> f<br />
<span class="kw1">WHERE</span> &nbsp;f.<span class="me1">CreatedUTCDtm</span> &gt;= @BeginDate<br />
AND &nbsp; &nbsp; &nbsp;f.<span class="me1">CreatedUTCDtm</span> &lt; &nbsp;@EndDate<br />
&nbsp;</div>
<p>Et voila. The engine can use an index because it has stats on what sort of values the table contains, and it knows what values it will be looking for. And it doesn&#8217;t have to call a function 45 million times.</p>
<p>After this simple change, and the addition of an index on CreatedUTCDtm, execution time time on the procedure in question dropped from 25 minutes to a minute and a half &#8211; still too long (there are other inefficiencies to kill off) but a net saving of more than 23 minutes. Which I like.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.barkingaardvark.com/sqlblog/?feed=rss2&amp;p=108</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Overheard</title>
		<link>http://www.barkingaardvark.com/sqlblog/?p=106</link>
		<comments>http://www.barkingaardvark.com/sqlblog/?p=106#comments</comments>
		<pubDate>Wed, 16 Mar 2011 15:47:56 +0000</pubDate>
		<dc:creator>dean</dc:creator>
				<category><![CDATA[IT Follies]]></category>

		<guid isPermaLink="false">http://www.barkingaardvark.com/sqlblog/?p=106</guid>
		<description><![CDATA[Across the cubicle wall.
I&#8217;ve seen some databases that were so normalized&#8230; you couldn&#8217;t work with them. You know what was a good database? One the users could work with? Paradox. It had this great Query By Example. I wish SQL Server had that.
If you can&#8217;t work with a database because it is too normalized, perhaps [...]]]></description>
			<content:encoded><![CDATA[<p>Across the cubicle wall.</p>
<blockquote><p>I&#8217;ve seen some databases that were so normalized&#8230; you couldn&#8217;t work with them. You know what was a good database? One the users could work with? Paradox. It had this great Query By Example. I wish SQL Server had that.</p></blockquote>
<p>If you can&#8217;t work with a database because it is too normalized, perhaps you need to work in a different field.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.barkingaardvark.com/sqlblog/?feed=rss2&amp;p=106</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>This Might Be Useful</title>
		<link>http://www.barkingaardvark.com/sqlblog/?p=102</link>
		<comments>http://www.barkingaardvark.com/sqlblog/?p=102#comments</comments>
		<pubDate>Wed, 16 Feb 2011 05:17:37 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Indexing]]></category>

		<guid isPermaLink="false">http://www.barkingaardvark.com/sqlblog/?p=102</guid>
		<description><![CDATA[In SQL Server 2005 and up, Microsoft provides a set of immensely useful views and functions that let you see how and what the engine is doing.
They are colloquially known as the DMVs, &#8216;Dynamic Management Views&#8217;. I cannot stress how useful they are.
Here is an example, and a very useful query. This uses the DMVs [...]]]></description>
			<content:encoded><![CDATA[<p>In SQL Server 2005 and up, Microsoft provides a set of immensely useful views and functions that let you see how and what the engine is doing.</p>
<p>They are colloquially known as the DMVs, &#8216;Dynamic Management Views&#8217;. I cannot stress how useful they are.</p>
<p>Here is an example, and a very useful query. This uses the DMVs to show you the general state of the indexes in a database. It shows you the table name, the number of indexes on that table, how many of those indexes have been unused since either the last restart or the index was (re)created, and how many indexes SQL Server thought it would like to have on the table. In a well tuned database, the unused and missing counts would both be 0. If they are, you have exactly the right number of indexes for the workloads hitting your tables.</p>
<p>If they aren&#8217;t, though, this query will show you where you need to start.</p>
<p>First you&#8217;ll need to zero on on those unused indexes. They do nothing but slow down inserts, updates, and deletes, and take space. They are pure drag.</p>
<p>In a future post, I will post a query that will show you all unused indexes on a table and their costs.</p>
<div class="dean_ch" style="white-space: wrap;"><span class="kw1">SET</span> <span class="kw1">TRANSACTION</span> <span class="kw1">ISOLATION</span> <span class="kw1">LEVEL</span> <span class="kw1">READ</span> <span class="kw1">UNCOMMITTED</span></p>
<p><span class="kw1">SELECT</span>&nbsp; <span class="kw1">CONVERT</span><span class="br0">&#40;</span><span class="kw1">VARCHAR</span><span class="br0">&#40;</span><span class="nu0">30</span><span class="br0">&#41;</span>,so.<span class="me1">name</span><span class="br0">&#41;</span>&nbsp; &nbsp; <span class="kw1">AS</span> TABLE_NAME,<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">COALESCE</span><span class="br0">&#40;</span>Unused.<span class="me1">IdxCount</span>, <span class="nu0">0</span><span class="br0">&#41;</span>&nbsp; &nbsp; <span class="kw1">AS</span> IDXCOUNT,<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">COALESCE</span><span class="br0">&#40;</span>Unused.<span class="me1">UnusedCount</span>, <span class="nu0">0</span><span class="br0">&#41;</span> <span class="kw1">AS</span> UNUSEDCOUNT,<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">COALESCE</span><span class="br0">&#40;</span>Missing.<span class="me1">MICOUNT</span>, <span class="nu0">0</span><span class="br0">&#41;</span>&nbsp; &nbsp; <span class="kw1">AS</span> MISSINGCOUNT,<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">COALESCE</span><span class="br0">&#40;</span><span class="kw1">CONVERT</span><span class="br0">&#40;</span><span class="kw1">DECIMAL</span><span class="br0">&#40;</span><span class="nu0">6</span>,<span class="nu0">1</span><span class="br0">&#41;</span>, <span class="br0">&#40;</span><span class="kw1">CONVERT</span><span class="br0">&#40;</span><span class="kw1">DECIMAL</span><span class="br0">&#40;</span><span class="nu0">10</span>,<span class="nu0">2</span><span class="br0">&#41;</span>,Unused.<span class="me1">UnusedCount</span><span class="br0">&#41;</span>/<span class="kw1">CONVERT</span><span class="br0">&#40;</span><span class="kw1">DECIMAL</span><span class="br0">&#40;</span><span class="nu0">10</span>,<span class="nu0">2</span><span class="br0">&#41;</span>,Unused.<span class="me1">IdxCount</span><span class="br0">&#41;</span><span class="br0">&#41;</span> * <span class="nu0">100</span><span class="br0">&#41;</span>, <span class="nu0">0</span><span class="br0">&#41;</span> <span class="kw1">AS</span> UnusedPct</p>
<p><span class="kw1">FROM</span>&nbsp; &nbsp; sys.<span class="me1">objects</span> so<br />
<span class="kw1">LEFT</span> JOIN</p>
<p>&nbsp; &nbsp; <span class="br0">&#40;</span>&nbsp; &nbsp;<span class="kw1">SELECT</span>&nbsp; &nbsp; &nbsp; s.<span class="kw2">OBJECT_ID</span>,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">COUNT</span><span class="br0">&#40;</span>*<span class="br0">&#41;</span>&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">AS</span> idxcount,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">SUM</span><span class="br0">&#40;</span><span class="kw1">CASE</span> <span class="kw1">WHEN</span>&nbsp; &nbsp;s.<span class="me1">user_seeks</span>&nbsp; &nbsp; = <span class="nu0">0</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AND s.<span class="me1">user_scans</span>&nbsp; &nbsp; = <span class="nu0">0</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AND&nbsp;s.<span class="me1">user_lookups</span>&nbsp; = <span class="nu0">0</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">THEN</span>&nbsp; &nbsp; <span class="nu0">1</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">ELSE</span> <span class="nu0">0</span> <span class="kw1">END</span><span class="br0">&#41;</span> <span class="kw1">AS</span> UnusedCount</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">FROM</span>&nbsp; &nbsp; sys.<span class="me1">dm_db_index_usage_stats</span> s<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; JOIN&nbsp; &nbsp; sys.<span class="me1">indexes</span>&nbsp;&nbsp; &nbsp; i<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">ON</span>&nbsp; &nbsp; &nbsp; s.<span class="kw2">OBJECT_ID</span>&nbsp;&nbsp; &nbsp; = i.<span class="kw2">OBJECT_ID</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AND&nbsp;&nbsp; &nbsp; s.<span class="me1">index_id</span>&nbsp; &nbsp; &nbsp; = i.<span class="me1">index_id</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">WHERE</span>&nbsp; &nbsp;s.<span class="me1">database_id</span>&nbsp; &nbsp;= <span class="kw2">DB_ID</span><span class="br0">&#40;</span><span class="br0">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AND&nbsp;&nbsp; &nbsp; <span class="kw2">OBJECTPROPERTY</span><span class="br0">&#40;</span>s.<span class="kw2">OBJECT_ID</span>, <span class="st0">&#8216;IsMsShipped&#8217;</span><span class="br0">&#41;</span> = <span class="nu0">0</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">GROUP</span> <span class="kw1">BY</span>&nbsp; &nbsp; s.<span class="kw2">OBJECT_ID</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="br0">&#41;</span> Unused<br />
<span class="kw1">ON</span>&nbsp; &nbsp; &nbsp; Unused.<span class="kw2">OBJECT_ID</span> = so.<span class="kw2">OBJECT_ID</span><br />
<span class="kw1">LEFT</span> JOIN <span class="br0">&#40;</span>&nbsp;<span class="kw1">SELECT</span>&nbsp; d.<span class="kw2">OBJECT_ID</span>,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw2">COUNT</span><span class="br0">&#40;</span>*<span class="br0">&#41;</span> <span class="kw1">AS</span> MICOUNT<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">FROM</span> &nbsp; &nbsp;sys.<span class="me1">dm_db_missing_index_groups</span>&nbsp; g<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; JOIN &nbsp; &nbsp;sys.<span class="me1">dm_db_missing_index_group_stats</span> s<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">ON</span>&nbsp; &nbsp; &nbsp; s.<span class="me1">group_handle</span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = g.<span class="me1">index_group_handle</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; JOIN &nbsp; &nbsp;sys.<span class="me1">dm_db_missing_index_details</span> d<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">ON</span>&nbsp; &nbsp; &nbsp; d.<span class="me1">index_handle</span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = g.<span class="me1">index_handle</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">WHERE</span>&nbsp; &nbsp;d.<span class="me1">database_id</span> = <span class="kw2">DB_ID</span><span class="br0">&#40;</span><span class="br0">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">GROUP</span> <span class="kw1">BY</span> d.<span class="kw2">OBJECT_ID</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="br0">&#41;</span>Missing<br />
<span class="kw1">ON</span>&nbsp; &nbsp; &nbsp; Missing.<span class="kw2">OBJECT_ID</span> = so.<span class="kw2">OBJECT_ID</span><br />
<span class="kw1">WHERE</span>&nbsp; &nbsp;so.<span class="me1">type_desc</span> = <span class="st0">&#8216;USER_TABLE&#8217;</span><br />
AND&nbsp;&nbsp; &nbsp; <span class="br0">&#40;</span>Missing.<span class="me1">MICOUNT</span> &gt; <span class="nu0">0</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; OR Unused.<span class="me1">UnusedCount</span> &gt; <span class="nu0">0</span><span class="br0">&#41;</span><br />
<span class="kw1">ORDER</span> <span class="kw1">BY</span> UnusedPct <span class="kw1">DESC</span>&nbsp;</p>
<p><span class="kw1">SET</span> <span class="kw1">TRANSACTION</span> <span class="kw1">ISOLATION</span> <span class="kw1">LEVEL</span> <span class="kw1">READ</span> COMMITTED</div>
]]></content:encoded>
			<wfw:commentRss>http://www.barkingaardvark.com/sqlblog/?feed=rss2&amp;p=102</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>NOLOCK</title>
		<link>http://www.barkingaardvark.com/sqlblog/?p=50</link>
		<comments>http://www.barkingaardvark.com/sqlblog/?p=50#comments</comments>
		<pubDate>Wed, 22 Dec 2010 15:34:12 +0000</pubDate>
		<dc:creator>dean</dc:creator>
				<category><![CDATA[ANSI Standard]]></category>
		<category><![CDATA[T-SQL]]></category>

		<guid isPermaLink="false">http://www.barkingaardvark.com/sqlblog/?p=50</guid>
		<description><![CDATA[If you&#8217;ve been around SQL Server and SQL Server development for a while, you will undoubtedly have encountered the NOLOCK hint.
SQL Server allows you to modify how the optimizer processes a query in a wide variety of ways. One of the things you can change is locking behavior &#8211; you can modify behavior in an [...]]]></description>
			<content:encoded><![CDATA[<p>If you&#8217;ve been around SQL Server and SQL Server development for a while, you will undoubtedly have encountered the NOLOCK hint.</p>
<p>SQL Server allows you to modify how the optimizer processes a query in a wide variety of ways. One of the things you can change is locking behavior &#8211; you can modify behavior in an individual query by applying a lock hint, like this:<br />
<code><br />
SELECT *<br />
FROM dbo.Table WITH (NOLOCK)<br />
</code><br />
This is extremely useful in some cases, but as with all things, it is open to misuse, and the NOLOCK hint is widely, widely misused.</p>
<p>Some background is in order. In an RDBMS, we have to balance all things (hence the title of this blog), and one of the things we have to balance is the line between consistency and concurrency. We could queue all database requests, forcing them to proceed one after another, which would mean that our database would never wind up in an inconsistent state. But this would cause insurmountable concurrency issues, reducing our multi-thousand dollar database server to a single-user machine. At the other end of the scale, we could allow anybody to do anything at any time. This would obviously increase concurrency to its greatest possible value, but would result in chaos. You wouldn&#8217;t have a database: you&#8217;d have a mess.</p>
<p>To address this, the ANSI committee came up with 4 isolation levels. These are, in increasing order of consistency: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, and SERIALIZABLE. These are implemented by use of locks. A process updating a row locks it while the update occurs, a process inserting new data inserts it with locks on it and only releases those locks once the entire insert is complete, etc. Otherwise, you never know what you&#8217;re getting &#8211; the new data or the old data, just some of the data or all of it.</p>
<p>But locks introduce another issue, which is known as blocking. A process that wants to read all last nights orders must wait until the process updating those orders has finished doing so. The reading process is said to be blocking on the updating one, the updater is blocking the reader.</p>
<p>This sounds bad. But blocking is a normal and expected part of any relational database that sees any degree of multiuser activity. As long as blocks are of short duration, the database will handle them transparently, and nobody will be any the wiser. </p>
<p>The problem in SQL Server 2000 (and in DB2) is that databases that are abused and that must serve both long-running read operations in addition to everything an OLTP system has to do (insert, updated, delete) will start to suffer extensive blocking. A long read will block updates and deletes, and depending on other circumstances, can block inserts as well. Updates block readers, which block other updates, inserts, deletes&#8230; this is all part of how a proper relational database is designed. </p>
<p>Locks and blocks prevent inconsistency, but most procedural programmers don&#8217;t understand database consistency anyway, and so to them a lock is a block is bad. Anything that gets around that is, to those lacking understanding, a good thing.</p>
<p>In both SQL Server and DB2 (and some other products with which I have not worked), a common problem is readers blocking writers. When you have a database that has been poorly designed used for both OLTP and reporting, you tend to get heavy reporting queries holding up OLTP activity: the INSERT, UPDATE, DELETE statements sit and wait until the long-running read finishes with the data. The uninformed programmer, seeing the number of processes blocked, hunts on the web and finds the magical NOLOCK query hint.</p>
<p>What NOLOCK does is move a particular query to the READ UNCOMMITTED isolation level. The query does not set any shared locks<sup>1</sup>, and it doesn&#8217;t honour any exclusive locks held by other processes. On a system suffering from poor design and resulting long-running reporting queries holding up normal business activity, this seems like a godsend: just throw NOLOCK on all queries, and the blocking problem magically goes away.</p>
<p>Or does it? What NOLOCK does is move the problem, from somewhere obvious to somewhere obscure. It doesn&#8217;t make much difference to data modification processes &#8211; those operations have their isolation levels escalated automatically anyway, because UPDATEs that don&#8217;t honour locks that other UPDATEs set makes the whole idea of concurrency a complete mockery. Where it makes a difference is in the read operation: because the read doesn&#8217;t honour exclusive locks, it will read another transaction&#8217;s uncommitted data. </p>
<p>That should scare you. </p>
<p>It will read data that could be rolled back. Allowing such reads violates both the C and the I of ACID (Atomic, Consistent, Isolated, Durable) &#8211; data is not consistent during such a read, and an uncommitted transaction is not isolated.</p>
<p>I have encountered a number of problems with NOLOCK (and with its DB2 counterpart, WITH UR) over the years. In SQL Server, it can result in an existing row either being missed or (more commonly) read twice if there is a data movement routine going on &#8211; say a page split, or a DBCC REINDEX &#8211; this will often manifest as a primary key violation in a routine that, when you look at it, couldn&#8217;t possibly be inserting values that already exist. </p>
<p>But if you don&#8217;t get immediate, obvious, in-your-face errors like primary key violations, the data rot that NOLOCK can induce is subtle and nearly impossible to track down.  Say you have a summary routine that runs with NOLOCK. Say it reads a row of financial data that is later rolled back and adds the value there to the summary for the day, or hour, or whatever. That aggregate value is now incorrect, and anything derived from it is incorrect. And even a guy like me, who has a lot of experience and skill tracking down things like this, I will never be able to prove that this was caused by reading uncommitted data. The best I will be able to do is to say, well, on this day the summary was out. </p>
<p>NOLOCK is dangerous. The only people who should use it are people who understand the implications, and it should almost never be used in any sort of saved routine (a stored procedure, for example). </p>
<hr />
<ol>
<li>This is generally true, but not strictly true: any read, for example, running at READ UNCOMMITTED will still set a schema stability lock &#8211; you won&#8217;t be able to ALTER the table while the read operation is running.</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.barkingaardvark.com/sqlblog/?feed=rss2&amp;p=50</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Note To Self</title>
		<link>http://www.barkingaardvark.com/sqlblog/?p=80</link>
		<comments>http://www.barkingaardvark.com/sqlblog/?p=80#comments</comments>
		<pubDate>Thu, 09 Dec 2010 16:54:59 +0000</pubDate>
		<dc:creator>dean</dc:creator>
				<category><![CDATA[IT Follies]]></category>

		<guid isPermaLink="false">http://www.barkingaardvark.com/sqlblog/?p=80</guid>
		<description><![CDATA[If anyone asks why you insist on using surrogate keys, point them to this.
]]></description>
			<content:encoded><![CDATA[<p>If anyone asks why you insist on using surrogate keys, <a href="http://jira.atlassian.com/browse/JRA-2703">point them to this</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.barkingaardvark.com/sqlblog/?feed=rss2&amp;p=80</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>I Wonder</title>
		<link>http://www.barkingaardvark.com/sqlblog/?p=77</link>
		<comments>http://www.barkingaardvark.com/sqlblog/?p=77#comments</comments>
		<pubDate>Thu, 09 Dec 2010 14:37:28 +0000</pubDate>
		<dc:creator>dean</dc:creator>
				<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://www.barkingaardvark.com/sqlblog/?p=77</guid>
		<description><![CDATA[I wonder if it is possible to solve Einstein&#8217;s Riddle using SQL.
The riddle is attributed to either Albert Einstein or Lewis Carroll, although there is no evidence that either of them had anything to do with the riddle, and it has various forms although the basic riddle remains the same.
The Riddle
In a town, there are [...]]]></description>
			<content:encoded><![CDATA[<p>I wonder if it is possible to solve <a href="http://ticsblog.com/2010/12/07/solving-einsteins-riddle-using-nondeterministic-computing/">Einstein&#8217;s Riddle</a> using SQL.</p>
<p><a href="http://en.wikipedia.org/wiki/Einstein's_riddle">The riddle is attributed</a> to either Albert Einstein or Lewis Carroll, although there is no evidence that either of them had anything to do with the riddle, and it has various forms although the basic riddle remains the same.</p>
<p><strong>The Riddle</strong></p>
<p>In a town, there are five houses, each painted with a different color.<br />
In every house leaves a person of different nationality.<br />
Each homeowner drink a different beverage, smokes a different brand of cigar, and owns a different type of pet.</p>
<p><strong>The Question</strong></p>
<p>Who owns the fishes?</p>
<p><strong>Hints</strong></p>
<p>The Brit lives in a red house.<br />
The Swede keeps dogs as pets.<br />
The Dane drinks tea.<br />
The Green house is next to, and on the left of the White house.<br />
The owner of the Green house drinks coffee.<br />
The person who smokes Pall Mall rears birds.<br />
The owner of the Yellow house smokes Dunhill.<br />
The man living in the center house drinks milk.<br />
The Norwegian lives in the first house.<br />
The man who smokes Blends lives next to the one who keeps cats.<br />
The man who keeps horses lives next to the man who smokes Dunhill.<br />
The man who smokes Blue Master drinks beer.<br />
The German smokes Prince.<br />
The Norwegian lives next to the blue house.<br />
The man who smokes Blends has a neighbor who drinks water.</p>
<p>This is a set-relational problem, and it might be possible to solve it with SQL. Challenging, for sure, but solvable. Most programmatic solutions seem to use recursion or long iterative statements to solve this, and I think it can probably be done in a single SQL statement.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.barkingaardvark.com/sqlblog/?feed=rss2&amp;p=77</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Great Advice</title>
		<link>http://www.barkingaardvark.com/sqlblog/?p=75</link>
		<comments>http://www.barkingaardvark.com/sqlblog/?p=75#comments</comments>
		<pubDate>Tue, 07 Dec 2010 15:02:44 +0000</pubDate>
		<dc:creator>dean</dc:creator>
				<category><![CDATA[Administration]]></category>

		<guid isPermaLink="false">http://www.barkingaardvark.com/sqlblog/?p=75</guid>
		<description><![CDATA[Paul S. Randal knows what he is talking about. And he has written a list of the top 10 things that DBAs should do.
There is sound advice there.
]]></description>
			<content:encoded><![CDATA[<p>Paul S. Randal knows what he is talking about. And he has written a <a href="http://redmondmag.com/Articles/2010/12/01/Top-10-Secrets-of-a-SQL-Server-Expert.aspx?Page=1">list of the top 10 things</a> that DBAs should do.</p>
<p>There is sound advice there.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.barkingaardvark.com/sqlblog/?feed=rss2&amp;p=75</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>1 Billion</title>
		<link>http://www.barkingaardvark.com/sqlblog/?p=73</link>
		<comments>http://www.barkingaardvark.com/sqlblog/?p=73#comments</comments>
		<pubDate>Fri, 13 Aug 2010 15:52:48 +0000</pubDate>
		<dc:creator>dean</dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[T-SQL]]></category>

		<guid isPermaLink="false">http://www.barkingaardvark.com/sqlblog/?p=73</guid>
		<description><![CDATA[My boss was stomping around agitated because The Business has some heavy data requests. He had written something some time ago that got them something close to what HE thought THEY thought they needed. 
I knew that this wasn&#8217;t what they really needed, but let him go ahead and send a sample of the data [...]]]></description>
			<content:encoded><![CDATA[<p>My boss was stomping around agitated because The Business has some heavy data requests. He had written something some time ago that got them something close to what HE thought THEY thought they needed. </p>
<p>I knew that this wasn&#8217;t what they really needed, but let him go ahead and send a sample of the data off anyway. Most times when people senior to you have their minds made up, there&#8217;s no point in arguing.</p>
<p>&#8220;It&#8217;s pretty fast,&#8221; he said, &#8220;if you need the code. It takes about an hour per partition.&#8221;</p>
<p>On our system, some of the really big tables are pseudo-partitions &#8211; separate tables with identical structures and similar names. Orders1, Orders2, Orders3, etc. He meant it took about an hour to process each one of these tables.</p>
<p>What I really needed was a subset of that data. About 12%, as it turns out. So I politely acknowledged his offer of Free Code! and wrote something that processed the first 16 partitions. It completed in about 2 hours 20 minutes.</p>
<p>As a measure of how much data this is, this is the rowcount of the largest table after the first 16 partitions were done. Remember, this rowcount is about 12% of the data.</p>
<p>&#8212;&#8212;&#8212;&#8211;<br />
109734329</p>
<p>I have another set of partitions to process today, which will a bit more than double this total.</p>
<p>Yes, if take the time to do the math you will see that the base data is more than 1  billion rows.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.barkingaardvark.com/sqlblog/?feed=rss2&amp;p=73</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Procedure Query Plans in Cache</title>
		<link>http://www.barkingaardvark.com/sqlblog/?p=69</link>
		<comments>http://www.barkingaardvark.com/sqlblog/?p=69#comments</comments>
		<pubDate>Tue, 29 Jun 2010 15:51:27 +0000</pubDate>
		<dc:creator>dean</dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[T-SQL]]></category>

		<guid isPermaLink="false">http://www.barkingaardvark.com/sqlblog/?p=69</guid>
		<description><![CDATA[If you remember, SQL Server takes your stored procedure and rewrites it, then prepares a plan. This process is called &#8216;compilation&#8217;. If it can, it then executes the prepared plan rather than interpreting the query anew each time.
These plans are stored in an area called the &#8216;procedure cache&#8217;. Plans are retained in the cache based [...]]]></description>
			<content:encoded><![CDATA[<p>If you remember, SQL Server takes your stored procedure and rewrites it, then prepares a plan. This process is called &#8216;compilation&#8217;. If it can, it then executes the prepared plan rather than interpreting the query anew each time.</p>
<p>These plans are stored in an area called the &#8216;procedure cache&#8217;. Plans are retained in the cache based on a number of factors such as amount of server memory, plan cost, and current memory pressure. The engine will get rid of infrequently used plans before frequently used ones, and cheap plans before expensive ones.</p>
<p>You can, and often will, get multiple plans for some procedures in cache. Predicting how many plans there will be is not an exact science, but in general the more complex the arguments to your procedure, the more plans will be in cache, because the engine will create a different plan if you&#8217;re looking for all orders from Chicago than if you&#8217;re looking for all orders with total > $1000.</p>
<p>Here is a query that will let you see all plans in cache for a given object (a stored procedure is an object in SQL Server), as well as some useful and interesting information about those plans.</p>
<p>Apologies for the style &#8211; the mix of camelcase and under_score names is due to my schizophrenia on this point. I used to be exclusively one, but this place uses the other, and when I write queries that use DMVs I get all split-personality like.</p>
<p>Usecounts = number of times the plan has been used.<br />
Plan_comp_time = the time that this plan was compiled.<br />
Last_execution_time = the time that this plan was last executed.<br />
HoursOld = length of time, in hours, that this plan has been in cache.<br />
HoursUntouched = length of time, in hours, since this plan was last executed.<br />
Execution_count = number of times this plan has been executed.<br />
AvgReads = average number of pages read during an execution.<br />
Max_logical_reads = the highest number of pages read during an execution.<br />
Avg_time = average time, in milliseconds, that this plan took to complete.<br />
Total_elapsed_time = total time &#8211; an indicator of total cost, since this is the sum of all times for this plan.<br />
last_elapsed_time = the time for the last execution.<br />
min_elapsed_time = the shortest time for an execution<br />
max_elapsed_time = the longest time for an execution</p>
<div class="dean_ch" style="white-space: wrap;">
<span class="kw1">SELECT</span> &nbsp;<span class="kw1">CONVERT</span><span class="br0">&#40;</span><span class="kw1">VARCHAR</span><span class="br0">&#40;</span><span class="nu0">36</span><span class="br0">&#41;</span>,o.<span class="me1">name</span><span class="br0">&#41;</span> <span class="kw1">AS</span> <span class="kw1">ROUTINE</span>, <br />
&nbsp; &nbsp; &nbsp; &nbsp; cp.<span class="me1">usecounts</span>, <br />
&nbsp; &nbsp; &nbsp; &nbsp; qs.<span class="me1">creation_time</span> <span class="kw1">AS</span> plan_comp_time,<br />
&nbsp; &nbsp; &nbsp; &nbsp; qs.<span class="me1">last_execution_time</span>,<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">CONVERT</span><span class="br0">&#40;</span><span class="kw1">VARCHAR</span><span class="br0">&#40;</span><span class="nu0">5</span><span class="br0">&#41;</span>, <span class="kw2">DATEDIFF</span><span class="br0">&#40;</span>hh, qs.<span class="me1">creation_time</span>, <span class="kw2">GETDATE</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="kw1">AS</span> HoursOld,<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span class="kw1">CONVERT</span><span class="br0">&#40;</span><span class="kw1">VARCHAR</span><span class="br0">&#40;</span><span class="nu0">5</span><span class="br0">&#41;</span>, <span class="kw2">DATEDIFF</span><span class="br0">&#40;</span>hh, last_execution_time, <span class="kw2">GETDATE</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="kw1">AS</span> HoursUntouched,<br />
&nbsp; &nbsp; &nbsp; &nbsp; qs.<span class="me1">execution_count</span>,<br />
&nbsp; &nbsp; &nbsp; &nbsp; qs.<span class="me1">total_logical_reads</span>/qs.<span class="me1">execution_count</span> <span class="kw1">AS</span> AvgReads,<br />
&nbsp; &nbsp; &nbsp; &nbsp; qs.<span class="me1">max_logical_reads</span>,<br />
&nbsp; &nbsp; &nbsp; &nbsp; qs.<span class="me1">total_elapsed_time</span>/qs.<span class="me1">execution_count</span> <span class="kw1">AS</span> AvgTime,<br />
&nbsp; &nbsp; &nbsp; &nbsp; qs.<span class="me1">total_elapsed_time</span>,<br />
&nbsp; &nbsp; &nbsp; &nbsp; qs.<span class="me1">last_elapsed_time</span>,<br />
&nbsp; &nbsp; &nbsp; &nbsp; qs.<span class="me1">min_elapsed_time</span>,<br />
&nbsp; &nbsp; &nbsp; &nbsp; qs.<span class="me1">max_elapsed_time</span><br />
<span class="kw1">FROM</span>&nbsp; &nbsp; sys.<span class="me1">dm_exec_cached_plans</span>&nbsp; &nbsp; cp<br />
JOIN&nbsp; &nbsp; sys.<span class="me1">dm_exec_query_stats</span>&nbsp;qs<br />
<span class="kw1">ON</span>&nbsp; &nbsp; &nbsp; cp.<span class="me1">plan_handle</span> = qs.<span class="me1">plan_handle</span><br />
CROSS APPLY sys.<span class="me1">dm_exec_sql_text</span><span class="br0">&#40;</span>cp.<span class="me1">plan_handle</span><span class="br0">&#41;</span> st<br />
JOIN&nbsp; &nbsp; sys.<span class="me1">objects</span> o<br />
<span class="kw1">ON</span>&nbsp; &nbsp; &nbsp; o.<span class="kw2">OBJECT_ID</span> = st.<span class="me1">objectid</span><br />
<span class="kw1">WHERE</span>&nbsp; &nbsp;o.<span class="me1">name</span> LIKE <span class="st0">&#8216;%SomeProcedureName%&#8217;</span><br />
<span class="co1">&#8211; ORDER BY hoursuntouched DESC</span><br />
&nbsp;</div>
]]></content:encoded>
			<wfw:commentRss>http://www.barkingaardvark.com/sqlblog/?feed=rss2&amp;p=69</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Dean Yells At People</title>
		<link>http://www.barkingaardvark.com/sqlblog/?p=67</link>
		<comments>http://www.barkingaardvark.com/sqlblog/?p=67#comments</comments>
		<pubDate>Thu, 15 Apr 2010 17:50:40 +0000</pubDate>
		<dc:creator>dean</dc:creator>
				<category><![CDATA[IT Follies]]></category>

		<guid isPermaLink="false">http://www.barkingaardvark.com/sqlblog/?p=67</guid>
		<description><![CDATA[We had a mini-change go in. It was small, a change to an interface to a third party application. I had an email in hand detailing my part in it, which was to produce a list of old users to new users.
The external application had been notified, and email had gone out to the users. [...]]]></description>
			<content:encoded><![CDATA[<p>We had a mini-change go in. It was small, a change to an interface to a third party application. I had an email in hand detailing my part in it, which was to produce a list of old users to new users.</p>
<p>The external application had been notified, and email had gone out to the users. We would go down at 8:00 AM, and be back up in 20 minutes.</p>
<p>Note that this is a change to an interface to an external application that processes actual money for people.</p>
<p>6:45 &#8211; I review the project plan, retrieve the query I will use, check the query plan, make sure all is good. I am ready to go.<br />
7:56 &#8211; the project manager makes his appearance and boots his laptop.<br />
7:59 &#8211; the web dev manager appears.<br />
8:01 &#8211; project manager calls out &#8216;are you guys ready to rock?&#8217;<br />
8:04 &#8211; the web dev manager informs us that we are waiting for another developer.<br />
8:05 &#8211; after some discussion in which someone says that there is some other database code to go in, I blow my stack, because as usual nobody has said anything to me or any other member of the database team. I hate surprises.<br />
8:07 &#8211; PM phones the other developer, who apparently we need. He is on the train, and will be about 20 minutes.<br />
8:10 &#8211; I calm down a bit, and search the code. I find some changes I made about 3 weeks ago (apparently I am supposed to keep all of these things in my head) and cue them up in my text editor, presuming that these are the necessary changes.<br />
8:28 &#8211; I am asked to run the mapping query I had originally been asked for. I run it and supply the results to those who need them.<br />
8:35 &#8211; the developer we are waiting for shows up and confirms that yes, we are putting in the code changes I located.<br />
8:36 &#8211; I apply the code changes to the production database.<br />
8:37 &#8211; I go back to other work, half expecting issues to surface at some point.<br />
8:41 &#8211; the interface is back up.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.barkingaardvark.com/sqlblog/?feed=rss2&amp;p=67</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

