<?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/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>SQL Fascination &#187; Internals</title>
	<atom:link href="http://sqlfascination.com/tag/internals/feed/" rel="self" type="application/rss+xml" />
	<link>http://sqlfascination.com</link>
	<description>Weirdness and oddities within SQL</description>
	<lastBuildDate>Mon, 06 Feb 2012 16:13:56 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='sqlfascination.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://s2.wp.com/i/buttonw-com.png</url>
		<title>SQL Fascination &#187; Internals</title>
		<link>http://sqlfascination.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://sqlfascination.com/osd.xml" title="SQL Fascination" />
	<atom:link rel='hub' href='http://sqlfascination.com/?pushpress=hub'/>
		<item>
		<title>Which User Made That Change?</title>
		<link>http://sqlfascination.com/2011/01/08/which-user-made-that-change/</link>
		<comments>http://sqlfascination.com/2011/01/08/which-user-made-that-change/#comments</comments>
		<pubDate>Sat, 08 Jan 2011 15:28:02 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2005]]></category>
		<category><![CDATA[SQL Server 2008]]></category>
		<category><![CDATA[SQL Server Denali]]></category>
		<category><![CDATA[Transaction Log]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=576</guid>
		<description><![CDATA[If you have spent any time tinkering about in the transaction log, you will of already come across a bit of a problem when trying to decide what was done and by whom &#8211; the &#8216;what part&#8217; I have decoded in a few posts, but the &#8216;whom&#8217; part is a lot harder. As far as [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=576&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>If you have spent any time tinkering about in the transaction log, you will of already come across a bit of a problem when trying to decide what was done and by whom &#8211; the &#8216;what part&#8217; I have decoded in a few posts, but the &#8216;whom&#8217; part is a lot harder. As far as I can tell the log only contains the SPID of the user who opened the transaction, and does not give us any indication as to who that user really was.</p>
<p>From an actual investigative perspective this is a bit of a painful exercise, I can see a row was deleted but to find out who / what did that actual deletion I would have to start examining either the SQL Server logs or the Windows Server Logs. The default behaviour of SQL Server security though is to only log failed login attempts so the successful ones will not show up by default &#8211; to get those appearing you need to change your SQL Server security settings. You can access these logs from the SQL management studio using either the xp_readerrorlogs or sp_readerrorlogs procedures although the nature of the log and textural values make it difficult to then combine in a set based manner - I can humanly read the values but machine reading them for any purpose is a bit of a pain &#8211; there is also the issue that those logs will be cycled &#8211; and the old logs could well be completely offline.</p>
<p>So I would prefer an easier solution, keeping a record of the logins within the database regardless of the SQL Server security settings, and being in a form that allows me to use a bit more of a set based solution against it. To start with, we will need a table to store the information available to us during the logon process:</p>
<pre><span style="color:#0000ff;">create table master</span><span style="color:#339966;">.dbo.spidArchive </span>(
 <span style="color:#339966;">LoginTime  </span><span style="color:#0000ff;">datetime2</span>(7)
 ,<span style="color:#339966;">SPID   </span><span style="color:#0000ff;">integer</span>
 ,<span style="color:#339966;">ServerName  </span><span style="color:#0000ff;">nvarchar</span>(100)
 ,<span style="color:#339966;">LoginName  </span><span style="color:#0000ff;">nvarchar</span>(100)
 ,<span style="color:#339966;">LoginType  </span><span style="color:#0000ff;">nvarchar</span>(100)
 ,<span style="color:#339966;">LoginSID  </span><span style="color:#0000ff;">nvarchar</span>(100)
 ,<span style="color:#339966;">ClientHost </span><span style="color:#0000ff;">nvarchar</span>(100)
 ,<span style="color:#339966;">IsPooled  </span><span style="color:#0000ff;">tinyint</span>
)</pre>
<p>The spidArchive table here is created in the master database so that it can cover the connections for any of the databases. You can see we have access to a lot of useful information, not just who executed the command, but from which machine they logged in from. The next step is to get SQL Server to add a row to the table every time a login occurs &#8211; from SQL Server 2005 onwards we have had access to DDL triggers as well as DML triggers and have the ability to intercept a number of non-DML events.</p>
<pre><span style="color:#0000ff;">create trigger </span><span style="color:#339966;">spidLogin</span> <span style="color:#0000ff;">on </span>all <span style="color:#0000ff;">server</span>
<span style="color:#0000ff;">after </span><span style="color:#339966;">logon</span>
<span style="color:#0000ff;">as</span>
 <span style="color:#0000ff;">declare </span><span style="color:#339966;">@eventdata </span><span style="color:#0000ff;">xml</span>;
 <span style="color:#0000ff;">set </span><span style="color:#339966;">@eventdata </span>= <span style="color:#ff00ff;">EVENTDATA</span>();

 <span style="color:#0000ff;">INSERT INTO master</span>.<span style="color:#339966;">dbo.spidArchive</span>
 (
  <span style="color:#339966;">LoginTime</span>
  ,<span style="color:#339966;">SPID   </span>
  ,<span style="color:#339966;">ServerName  </span>
  ,<span style="color:#339966;">LoginName  </span>
  ,<span style="color:#339966;">LoginType  </span>
  ,<span style="color:#339966;">LoginSID  </span>
  ,<span style="color:#339966;">ClientHost </span>
  ,<span style="color:#339966;">IsPooled</span>
 )
 <span style="color:#0000ff;">VALUES </span>
 (
  <span style="color:#33cccc;">@eventdata.value</span>(<span style="color:#ff0000;">'(/EVENT_INSTANCE/PostTime)[1]'</span>,<span style="color:#ff0000;">'datetime2(7)'</span>)
  ,<span style="color:#33cccc;">@eventdata.value</span>(<span style="color:#ff0000;">'(/EVENT_INSTANCE/SPID)[1]'</span>,<span style="color:#ff0000;">'nvarchar(100)'</span>)
  ,<span style="color:#33cccc;">@eventdata.value</span>(<span style="color:#ff0000;">'(/EVENT_INSTANCE/ServerName)[1]'</span>,<span style="color:#ff0000;">'nvarchar(100)'</span>)
  ,<span style="color:#33cccc;">@eventdata.value</span>(<span style="color:#ff0000;">'(/EVENT_INSTANCE/LoginName)[1]'</span>,<span style="color:#ff0000;">'nvarchar(100)'</span>)
  ,<span style="color:#33cccc;">@eventdata.value</span>(<span style="color:#ff0000;">'(/EVENT_INSTANCE/LoginType)[1]'</span>,<span style="color:#ff0000;">'nvarchar(100)'</span>)
  ,<span style="color:#33cccc;">@eventdata.value</span>(<span style="color:#ff0000;">'(/EVENT_INSTANCE/SID)[1]'</span>,<span style="color:#ff0000;">'nvarchar(100)'</span>)
  ,<span style="color:#33cccc;">@eventdata.value</span>(<span style="color:#ff0000;">'(/EVENT_INSTANCE/ClientHost)[1]'</span>,<span style="color:#ff0000;">'nvarchar(100)'</span>)
  ,<span style="color:#33cccc;">@eventdata.value</span>(<span style="color:#ff0000;">'(/EVENT_INSTANCE/IsPooled)[1]'</span>,<span style="color:#ff0000;">'tinyint'</span>)
 )</pre>
<p>During the login process, the EventData() function returns a fixed format XML fragment from which we can extract the values we seek and simply insert into our spidArchive table. Now we have a log being taken of all connections being established to the server, we can start using this to translate from a SPID to a user, even when the user is no longer connected &#8211; as long as we know the SPID and the time, we just need to look for the closest entry in the past for that SPID, and that will indicate which user was currently logged on at the time. This function should go in the master database again.</p>
<pre><span style="color:#0000ff;">CREATE FUNCTION</span> <span style="color:#339966;">dbo</span>.<span style="color:#339966;">ConvertSpidToName</span>(<span style="color:#339966;">@SPID</span> <span style="color:#0000ff;">integer</span>,<span style="color:#339966;"> @Date </span><span style="color:#0000ff;">datetime2</span>(7)) <span style="color:#0000ff;">RETURNS nvarchar</span>(100) <span style="color:#0000ff;">AS</span>
<span style="color:#0000ff;">BEGIN</span>
 <span style="color:#0000ff;">DECLARE </span><span style="color:#339966;">@name </span><span style="color:#0000ff;">nvarchar</span>(100)
 <span style="color:#0000ff;">SELECT TOP</span>(1)<span style="color:#339966;"> @name</span> = <span style="color:#339966;">LoginName</span>
 <span style="color:#0000ff;">FROM </span>master.<span style="color:#339966;">dbo</span>.<span style="color:#339966;">spidArchive</span>
 <span style="color:#0000ff;">WHERE </span><span style="color:#339966;">SPID </span>= <span style="color:#339966;">@SPID </span>AND <span style="color:#339966;">LoginTime </span>&lt;= <span style="color:#339966;">@Date</span>
 <span style="color:#0000ff;">ORDER BY</span> <span style="color:#339966;">LoginTime </span><span style="color:#0000ff;">DESC</span>;
 <span style="color:#0000ff;">RETURN </span>@name;
<span style="color:#0000ff;">END</span></pre>
<p>This function just performs the logic stated above  and converts the SPID and DateTime into the login name for the user. Once this infrastructure is in place we can now directly use that in a call to ::fn_dblog(null,null) to translate the SPID column</p>
<pre><span style="color:#0000ff;">select master</span>.<span style="color:#339966;">dbo</span>.<span style="color:#339966;">ConvertSpidToName</span>(<span style="color:#ff00ff;">log</span>.<span style="color:#339966;">SPID</span>, <span style="color:#ff00ff;">log</span>.[<span style="color:#339966;">Begin Time</span>]) as <span style="color:#339966;">UserName</span>, <span style="color:#ff00ff;">log</span>.* <span style="color:#0000ff;">from </span>::<span style="color:#008000;">fn_dblog</span>(null,null) <span style="color:#ff00ff;">log</span></pre>
<p>What you will notice is that for the majority of log lines, there is no user name displayed &#8211; this is because the SPID is only recorded against the LOP_BEGIN_XACT entry, the beginning of the transaction. This doesn&#8217;t really present a problem, from previous experiments we know all the entries for an individual transaction are given a unique Tansaction ID which we can use to group them together. It becomes pretty trivial to join back to the log, and connect any transaction entries to the LOP_BEGIN_XACT record and produce the name on every row possible.</p>
<pre><span style="color:#0000ff;">select master</span>.<span style="color:#339966;">dbo</span>.<span style="color:#339966;">ConvertSpidToName</span>(<span style="color:#339966;">log2</span>.<span style="color:#339966;">SPID</span>, <span style="color:#339966;">log2</span>.[<span style="color:#339966;">Begin Time</span>]) as <span style="color:#339966;">UserName</span>, <span style="color:#ff00ff;">log</span>.*
<span style="color:#0000ff;">from </span>::<span style="color:#008000;">fn_dblog</span>(null,null) <span style="color:#ff00ff;">log</span>
left join ::<span style="color:#008000;">fn_dblog</span>(null,null) <span style="color:#339966;">log2 </span>on log.[<span style="color:#339966;">Transaction ID</span>] = log2.[<span style="color:#339966;">Transaction ID</span>] and <span style="color:#339966;">log2</span>.<span style="color:#339966;">Operation </span>= <span style="color:#ff0000;">'LOP_BEGIN_XACT'</span></pre>
<p>So overall it is not too hard to get the log entries attributed to the accounts that generated them.</p>
<p>A couple of final notes / caveats:</p>
<ul>
<li>If your application is using a trusted sub-system approach this of course will not work as a technique, since all the users will be logged into the application through an internal mechanism (such as a users table) and then the application service connects using it&#8217;s own credentials &#8211; always a good thing since then the user&#8217;s have no direct access to the database. In that kind of situation this is of no value, every connection will be shown up as the same user/ source.</li>
<li>Within my code I chose to use datetime2(7), to be as accurate as possible on the connections and timings, you could drop to just datetime for SQL Server 2005 but with only 1/300ths of a second accuracy there is a chance on a very busy server that you could see two entries for a single SPID at the same datetime &#8211; which would pose a bit of a problem.</li>
<li>The spidArchive table can not be allowed to grow unconstrained &#8211; I have not included anything here for clearing down the table, but it is not difficult to conceive of it being archived off, or cleaned up weekly via a SQL Agent job.</li>
</ul>
<div id="_mcePaste" class="mcePaste" style="position:absolute;width:1px;height:1px;overflow:hidden;top:0;left:-10000px;">﻿</div>
<br />Filed under: <a href='http://sqlfascination.com/category/sql-server/'>SQL Server</a> Tagged: <a href='http://sqlfascination.com/tag/internals/'>Internals</a>, <a href='http://sqlfascination.com/tag/sql-server-2005/'>SQL Server 2005</a>, <a href='http://sqlfascination.com/tag/sql-server-2008/'>SQL Server 2008</a>, <a href='http://sqlfascination.com/tag/sql-server-denali/'>SQL Server Denali</a>, <a href='http://sqlfascination.com/tag/transaction-log/'>Transaction Log</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/576/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/576/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/576/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/576/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/576/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/576/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/576/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/576/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/576/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/576/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/576/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/576/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/576/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/576/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=576&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2011/01/08/which-user-made-that-change/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>SQL Internals Viewer</title>
		<link>http://sqlfascination.com/2010/11/27/sql-internals-viewer/</link>
		<comments>http://sqlfascination.com/2010/11/27/sql-internals-viewer/#comments</comments>
		<pubDate>Sat, 27 Nov 2010 13:31:24 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2005]]></category>
		<category><![CDATA[SQL Server 2008]]></category>
		<category><![CDATA[SQL Training]]></category>
		<category><![CDATA[Transaction Log]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=554</guid>
		<description><![CDATA[As Captain Oates once said, &#8216;I am just going outside and may be some time&#8217; &#8211; feels like quite a while since I had to time to see down and write something. I had a bit of time to take a look at the SQL Internals Viewer (http://internalsviewer.codeplex.com/) , it has been out for some [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=554&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>As Captain Oates once said, &#8216;I am just going outside and may be some time&#8217; &#8211; feels like quite a while since I had to time to see down and write something.</p>
<p>I had a bit of time to take a look at the SQL Internals Viewer (<a href="http://internalsviewer.codeplex.com/">http://internalsviewer.codeplex.com/</a>) , it has been out for some time but I had never downloaded it to play around to see how useful it is in terms of a way of learning more about the internals.</p>
<p>The Page Viewer is excellent, the breakdown of a page into the component parts for a row and the display of the page data is a superb aid to anyone wanting to understand how the data is stored on a page. Whilst you can use DBCC PAGE to get at all this information, presenting it in such a readable form will satisfy most people easily.</p>
<p><img class="aligncenter size-full wp-image-555" title="pageviewer" src="http://andrewhogg.files.wordpress.com/2010/11/pageviewer.jpg?w=600&#038;h=438" alt="" width="600" height="438" /></p>
<p>The page allocation map is a nice little addition, but really is just an extension of showing you what pages belong to which object etc.</p>
<p>The transaction log viewer though I was really looking forward to seeing, primarily to help me decode more transactions, but it has been a bit disappointing &#8211; the level of detail shown is very limited, and provides no real benefit over just looking at the log directly, or using the last transaction log trick I have previously posted.</p>
<p><img class="aligncenter size-full wp-image-556" title="Transaction Log Viewer" src="http://andrewhogg.files.wordpress.com/2010/11/translog.jpg?w=600&#038;h=205" alt="" width="600" height="205" /></p>
<p>As you can see from the screenshot, the level of details is pretty light for a simple transaction, no actual breakdown of the log record itself is provided, which is a shame &#8211; whilst it does given you some basic information and will help some people, I think if you are at the stage where you are taking an interest in the transaction log, you are already beyond this point.</p>
<p>So as an educational / learning aid, it is pretty good on the page internals side &#8211; and anyone wanting an easier way to visualize that for learning it is still worth grabbing. I would love to see more on the Log side &#8211; but at present the project appears to be in hibernation, with no changes in some considerable time, so I suspect we will not see any enhancements now.</p>
<br />Filed under: <a href='http://sqlfascination.com/category/sql-server/'>SQL Server</a> Tagged: <a href='http://sqlfascination.com/tag/internals/'>Internals</a>, <a href='http://sqlfascination.com/tag/sql-server-2005/'>SQL Server 2005</a>, <a href='http://sqlfascination.com/tag/sql-server-2008/'>SQL Server 2008</a>, <a href='http://sqlfascination.com/tag/sql-training/'>SQL Training</a>, <a href='http://sqlfascination.com/tag/transaction-log/'>Transaction Log</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/554/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/554/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/554/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/554/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/554/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/554/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/554/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/554/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/554/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/554/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/554/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/554/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/554/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/554/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=554&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2010/11/27/sql-internals-viewer/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>

		<media:content url="http://andrewhogg.files.wordpress.com/2010/11/pageviewer.jpg" medium="image">
			<media:title type="html">pageviewer</media:title>
		</media:content>

		<media:content url="http://andrewhogg.files.wordpress.com/2010/11/translog.jpg" medium="image">
			<media:title type="html">Transaction Log Viewer</media:title>
		</media:content>
	</item>
		<item>
		<title>Blank Transactions</title>
		<link>http://sqlfascination.com/2010/09/05/blank-transactions/</link>
		<comments>http://sqlfascination.com/2010/09/05/blank-transactions/#comments</comments>
		<pubDate>Sun, 05 Sep 2010 22:08:02 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2005]]></category>
		<category><![CDATA[SQL Server 2008]]></category>
		<category><![CDATA[Transaction Log]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=535</guid>
		<description><![CDATA[Busy time with a new addition to the household &#8211; sleep is clearly an optional parameter these days, but on to one of those oddities you might see in the transaction log. On many occasions you will see transactions in the log that have no operations, the individual transaction entry just has a LOP_BEGIN_XACT following [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=535&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Busy time with a new addition to the household &#8211; sleep is clearly an optional parameter these days, but on to one of those oddities you might see in the transaction log. On many occasions you will see transactions in the log that have no operations, the individual transaction entry just has a LOP_BEGIN_XACT following be a LOP_COMMIT_XACT, with no operations being recorded.</p>
<p>So what causes these?<br />
The immediate thought is a straight:</p>
<pre><span style="color:#0000ff;">Begin Transaction
</span><span style="color:#0000ff;">Commit Transaction</span></pre>
<p>If you try that and inspect the log, you will notice that it has not added in this mysterious, zero-operation transaction. So that is not the cause.</p>
<p>How about a rolled back transaction? well you should already know that this would not be the answer since the log reserves space ahead during a transaction to record undo operations, due to a rollback. To show that in more detail, given the following simple snippet of SQL:</p>
<pre><span style="color:#0000ff;">begin transaction</span>
<span style="color:#0000ff;">insert into</span> test (name) <span style="color:#0000ff;">values</span> (<span style="color:#ff0000;">'a'</span>)
<span style="color:#0000ff;">rollback transaction</span></pre>
<p>The log then shows the transaction beginning, performing the insert, and then rolling it back by deleting the record and recording it as an aborted transaction.</p>
<pre>LOP_ABORT_XACT
LOP_DELETE_ROWS
LOP_INSERT_ROWS
LOP_BEGIN_XACT</pre>
<p>So neither of the first obvious choices are the cause, the reason seems a bit bizarre, but centres around whether any change was attempted but ignored due to being unecessary. Setting up a test table and inserting that single row into it with a value of &#8216;a&#8217;, run the following statement:</p>
<pre><span style="color:#0000ff;">begin transaction</span>
<span style="color:#0000ff;">update</span> test <span style="color:#0000ff;">set</span> name = <span style="color:#ff0000;">'a'</span>
<span style="color:#0000ff;">rollback transaction</span></pre>
<p>Now when you inspect the log, there is a blank transaction, it recorded the start and end of the transaction, but no operations are shown. The same is true if the transaction is rolled back.</p>
<p>If the code is altered slightly to deliberately mean that no modification would occur though, the same does not hold true:</p>
<pre><span style="color:#0000ff;">begin transaction</span>
<span style="color:#0000ff;">update</span> test <span style="color:#0000ff;">set</span> name = <span style="color:#ff0000;">'a'</span> <span style="color:#0000ff;">where </span>1 = 0
<span style="color:#0000ff;">commit transaction</span></pre>
<p>Clearly the code is designed to make no modifications, so it is not surprising that no entry occurs in the transaction log, to make the test a bit fairer, let&#8217;s design the code in a way that it might make a modification, but it doesn&#8217;t.</p>
<pre><span style="color:#0000ff;">begin transaction</span>
<span style="color:#0000ff;">update</span> test <span style="color:#0000ff;">set</span> name = <span style="color:#ff0000;">'a'</span> <span style="color:#0000ff;">where</span> name = <span style="color:#ff0000;">'b'</span>
<span style="color:#0000ff;">commit transaction</span></pre>
<p>Still no entry in the transaction log.</p>
<p>So the distinction in the logging of these zero-op transactions is whether or not there was matching data to be altered, if a record was found but that the alteration was unnecessary we get a zero-op transaction appear in the log. It does make you wonder, why?</p>
<p>It also means that from an auditing perspective, the attempt to modify the data was not logged, not because it was unsuccessful, but because it was being altered to the values it already had.</p>
<br />Filed under: <a href='http://sqlfascination.com/category/sql-server/'>SQL Server</a> Tagged: <a href='http://sqlfascination.com/tag/internals/'>Internals</a>, <a href='http://sqlfascination.com/tag/sql-server-2005/'>SQL Server 2005</a>, <a href='http://sqlfascination.com/tag/sql-server-2008/'>SQL Server 2008</a>, <a href='http://sqlfascination.com/tag/transaction-log/'>Transaction Log</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/535/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/535/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/535/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=535&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2010/09/05/blank-transactions/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>Subtle change to ::fn_dblog in SQL 2008</title>
		<link>http://sqlfascination.com/2010/07/15/subtle-change-to-fn_dblog-in-sql-2008/</link>
		<comments>http://sqlfascination.com/2010/07/15/subtle-change-to-fn_dblog-in-sql-2008/#comments</comments>
		<pubDate>Thu, 15 Jul 2010 21:13:51 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2008]]></category>
		<category><![CDATA[Transaction Log]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=527</guid>
		<description><![CDATA[Bizarrely given that transaction log decoding is not really viable as a technique beyond interest, it&#8217;s quite surprising how many people visit just to understand or learn how to do it. Either a lot of data is being lost and no backup&#8217;s exist &#8211; or it fascinates a large number of people. In either instance, [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=527&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Bizarrely given that transaction log decoding is not really viable as a technique beyond interest, it&#8217;s quite surprising how many people visit just to understand or learn how to do it. Either a lot of data is being lost and no backup&#8217;s exist &#8211; or it fascinates a large number of people.</p>
<p>In either instance, there are sometimes subtle changes can pass you by, and the use of old habits can make life a bit harder. One such recent change that I missed was an alteration to the ::fn_dblog function. I really should have spent some time investigating the new fields that had been added &#8211; but with documentation being limited to say the least, it has not been the top priority and my habit is to fire up SQL 2005 when checking something in the transaction log &#8211; so I had not noticed them.</p>
<p>One of the changes is the creation of a link between the contents of sys.dm_tran_current_transaction and the transaction log itself. The contents of the view have not altered, but one of the new fields within the output from ::fn_fblog is a new field called Xact_ID &#8211; this field contains the same value as the Transaction_ID that is output from the DMV.</p>
<p>Now that is significantly convenient for when you&#8217;re poking around in logs trying to understand cause and effect .</p>
<p>A couple of simple stored procedures later to assist in saving and returning that filtered information as follows:</p>
<pre><span style="color:#0000ff;">CREATE PROCEDURE</span> sp_store_transaction
&lt;<span style="color:#0000ff;">AS</span>
<span style="color:#0000ff;">BEGIN</span>
<span style="color:#0000ff;">  DECLAR</span>E @dm_transaction_id bigint
<span style="color:#0000ff;">  SELECT </span>@dm_transaction_id = transaction_id <span style="color:#0000ff;">FROM </span><span style="color:#008000;">sys.dm_tran_current_transaction</span>
<span style="color:#0000ff;">  IF </span>OBJECT_ID (<span style="color:#ff0000;">N'tempdb.dbo.##db_last_transaction'</span>) IS NOT NULL
<span style="color:#0000ff;">    DROP TABLE</span> ##db_last_transaction
<span style="color:#0000ff;">  SELECT </span>[transaction id] INTO ##db_last_transaction
<span style="color:#0000ff;">  FROM </span>::<span style="color:#008000;">fn_dblog</span>(null,null)
<span style="color:#0000ff;">  WHERE </span>[Xact ID] = @dm_transaction_id
&lt;<span style="color:#0000ff;">END</span>
<span style="color:#0000ff;">GO</span></pre>
<pre><span style="color:#0000ff;">CREATE </span><span style="color:#0000ff;">PROCEDURE </span>sp_get_last_transaction
<span style="color:#0000ff;">AS</span>
<span style="color:#0000ff;">BEGIN</span>
<span style="color:#0000ff;">  SELECT</span> *
<span style="color:#0000ff;">  FROM </span>::<span style="color:#008000;">fn_dblog</span>(null,null)
<span style="color:#0000ff;">  WHERE</span> [Transaction ID] = (SELECT [transaction id] FROM ##db_last_transaction)
<span style="color:#0000ff;">END</span>
<span style="color:#0000ff;">GO</span></pre>
<p>And that is going to make looking at cause an effect far easier &#8211; first procedure you call anywhere within your transaction to store the current transaction_id within the log, and the second to retrieve the values for that stored transaction id. Something like:</p>
<pre><span style="color:#0000ff;">BEGIN TRAN</span>
<span style="color:#0000ff;">UPDATE </span>tbltest <span style="color:#0000ff;">SET </span>firstname = <span style="color:#ff0000;">'ab'</span>

<span style="color:#0000ff;">EXEC </span>sp_store_transaction
<span style="color:#0000ff;">COMMIT TRAN</span>

sp_get_last_transaction</pre>
<p>If you are wondering why I did not dump the log records out and only the transaction id that ties them together &#8211; if you dumped the records before the transaction committed or rolled back, you would not see the effect of that action &#8211; you need to retrieve all the log rows associated to that transaction, after it has finished.</p>
<br />Filed under: <a href='http://sqlfascination.com/category/sql-server/'>SQL Server</a> Tagged: <a href='http://sqlfascination.com/tag/internals/'>Internals</a>, <a href='http://sqlfascination.com/tag/sql-server-2008/'>SQL Server 2008</a>, <a href='http://sqlfascination.com/tag/transaction-log/'>Transaction Log</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/527/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/527/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/527/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=527&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2010/07/15/subtle-change-to-fn_dblog-in-sql-2008/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>A Strange Case of a Very Large LOP_FORMAT_PAGE</title>
		<link>http://sqlfascination.com/2010/02/28/a-strange-case-of-a-very-large-lop_format_page/</link>
		<comments>http://sqlfascination.com/2010/02/28/a-strange-case-of-a-very-large-lop_format_page/#comments</comments>
		<pubDate>Sun, 28 Feb 2010 15:49:35 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2005]]></category>
		<category><![CDATA[Transaction Log]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=482</guid>
		<description><![CDATA[I have been involved in a long running case with a client, trying to establish why an ETL process of a couple of gig of data, would result in over 5 gig of transaction log space being used. The database was set in bulk mode, and confirmed as being correctly in bulk mode with appropriate [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=482&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I have been involved in a long running case with a client, trying to establish why an ETL process of a couple of gig of data, would result in over 5 gig of transaction log space being used.</p>
<p>The database was set in bulk mode, and confirmed as being correctly in bulk mode with appropriate full backups and transaction log backups occurring &#8211; so how could the log be expanding so rapidly for such a simple import. One clue was that this was only occurring on a single system, and that no other system had ever witnessed this behaviour, so it had to be something environmental.</p>
<p>The output from the transaction log captured during a load window was sent to me and some quick aggregation of the rows show that there was close to 280k LOP_FORMAT_PAGE entries, but instead of the normal log entry size of 84 bytes, they were coming in at 8276 bytes. This was due to the log record overhead but basically meant that for each page being allocated by the inload, an entry was added larger than the page just added.</p>
<p>I contacted Paul Randal to ask why the log entry would get allocated this large and he identified it as not being in bulk logged mode &#8211; which made sense given the log expansion but there was little proof. Examining an individual LOP_FORMAT_PAGE entry I could see a clear repeating pattern within the hex data &#8211; which would be consistent to a page with rows within it.</p>
<p>One field of what was being imported was a known value for every row (the data related to a specific week number), and translating that value into hex, it was visible multiple times. Using the breakdown of a row and that this value was field number 3 the row could be decoded and actual values of a row imported reconstructed.</p>
<p>Using these values the main database was queried and that row did indeed exist &#8211; the data has 27 decimal values within it also, so the chances of a random match are incredibly small. This proved that the log was recording every single row, even though it was in bulk logged mode.</p>
<p>There are a number of things that can prevent bulk logged mode from operating correctly, and on examining the list the problem became clear to the client&#8217;s DBA; the main database backup was still running at the same time as the data inload was being scheduled. The bulk log operations have to be fully logged during the backup to ensure that the database can be returned to a consistent state once it has been restored.</p>
<p>It is the only time I have seen the LOP_FORMAT_PAGE taking up this much space in a transaction log, and if anyone else gets stuck on a similar problem, google should at least find something this time.</p>
<br />Filed under: <a href='http://sqlfascination.com/category/sql-server/'>SQL Server</a> Tagged: <a href='http://sqlfascination.com/tag/internals/'>Internals</a>, <a href='http://sqlfascination.com/tag/sql-server-2005/'>SQL Server 2005</a>, <a href='http://sqlfascination.com/tag/transaction-log/'>Transaction Log</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/482/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/482/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/482/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/482/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/482/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/482/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/482/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/482/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/482/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/482/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/482/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/482/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/482/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/482/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=482&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2010/02/28/a-strange-case-of-a-very-large-lop_format_page/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>Decoding a Simple Update Statement Within the Transaction Log</title>
		<link>http://sqlfascination.com/2010/02/21/decoding-a-simple-update-statement-within-the-transaction-log/</link>
		<comments>http://sqlfascination.com/2010/02/21/decoding-a-simple-update-statement-within-the-transaction-log/#comments</comments>
		<pubDate>Sun, 21 Feb 2010 14:05:01 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2005]]></category>
		<category><![CDATA[SQL Server 2008]]></category>
		<category><![CDATA[Transaction Log]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=473</guid>
		<description><![CDATA[After decoding the delete statement within the transaction log, I thought I would tackle another statement – an update statement, although I am attempting the simple version of the update which is only to update a single value within the fixed width fields section. Again to assist in decoding the log entry, I have used [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=473&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>After <a href="http://sqlfascination.com/2010/02/03/how-do-you-decode-a-simple-entry-in-the-transaction-log-part-1/" target="_blank">decoding the delete </a>statement within the transaction log, I thought I would tackle another statement – an update statement, although I am attempting the simple version of the update which is only to update a single value within the fixed width fields section. Again to assist in decoding the log entry, I have used the AdventureWorks database and know the values of the data before and after the modification, so it is easier to decode how the log entry is constructed.</p>
<p>As before the database has been backed up whilst in fully logged mode and the transaction log backed up to make it as small as possible. Once the database was ready I issued a simple statement against the database</p>
<pre><span style="color:#0000ff;">update </span>HumanResources.Employee
<span style="color:#0000ff;">set</span> MaritalStatus = 'M' <span style="color:#0000ff;">where</span> employeeid = 6</pre>
<p>The employee with the ID  of 6 has changed marital status, congratulations employee id 6.</p>
<p>The first confusion is that two LOP_MODIFY_ROW entries show up against the LCX_Clustered context, for the HumanResources.Employee.PK_Employee_EmployeeID allocation unit / name, but only 1 modification has been made.</p>
<p>What is the second modification?</p>
<p>Against each LOP_MODIFY_ROW there is an [Offset in Row] column, which indicates where within the row the modification has been made. The first modification is recorded at byte 24, the second is at byte 57. Instead of providing a field number / name, the alteration is being recorded at a byte level, so to figure out which columns have changed, we must map out the structure in terms of byte positions for the row.</p>
<table border="1" cellspacing="0" cellpadding="0" width="598">
<tbody>
<tr>
<td width="58" valign="top">Offset</td>
<td width="160" valign="top">Name</td>
<td width="95" valign="top">Size in Bytes</td>
<td width="284" valign="top">Value</td>
</tr>
<tr>
<td width="58" valign="top">0</td>
<td width="160" valign="top">Status Bits</td>
<td width="95" valign="top">2</td>
<td width="284" valign="top"> </td>
</tr>
<tr>
<td width="58" valign="top">2</td>
<td width="160" valign="top">Column Number Offset</td>
<td width="95" valign="top">2</td>
<td width="284" valign="top"> </td>
</tr>
<tr>
<td width="58" valign="top">4</td>
<td width="160" valign="top">EmployeeID</td>
<td width="95" valign="top">4</td>
<td width="284" valign="top">6</td>
</tr>
<tr>
<td width="58" valign="top">8</td>
<td width="160" valign="top">ContactID</td>
<td width="95" valign="top">4</td>
<td width="284" valign="top">1028</td>
</tr>
<tr>
<td width="58" valign="top">12</td>
<td width="160" valign="top">ManagerID</td>
<td width="95" valign="top">4</td>
<td width="284" valign="top">109</td>
</tr>
<tr>
<td width="58" valign="top">16</td>
<td width="160" valign="top">BirthDate</td>
<td width="95" valign="top">8</td>
<td width="284" valign="top">1965-04-19 00:00:00.000</td>
</tr>
<tr>
<td width="58" valign="top">24</td>
<td width="160" valign="top">MaritalStatus</td>
<td width="95" valign="top">2</td>
<td width="284" valign="top">S</td>
</tr>
<tr>
<td width="58" valign="top">26</td>
<td width="160" valign="top">Gender</td>
<td width="95" valign="top">2</td>
<td width="284" valign="top">M</td>
</tr>
<tr>
<td width="58" valign="top">28</td>
<td width="160" valign="top">HireDate</td>
<td width="95" valign="top">8</td>
<td width="284" valign="top">1998-01-20 00:00:00.000</td>
</tr>
<tr>
<td width="58" valign="top">36</td>
<td width="160" valign="top">SalariedFlag</td>
<td width="95" valign="top">1</td>
<td width="284" valign="top">1</td>
</tr>
<tr>
<td width="58" valign="top">37</td>
<td width="160" valign="top">VacationHours</td>
<td width="95" valign="top">2</td>
<td width="284" valign="top">40</td>
</tr>
<tr>
<td width="58" valign="top">39</td>
<td width="160" valign="top">SickLeaveHours</td>
<td width="95" valign="top">2</td>
<td width="284" valign="top">40</td>
</tr>
<tr>
<td width="58" valign="top">41</td>
<td width="160" valign="top">CurrentFlag</td>
<td width="95" valign="top">1</td>
<td width="284" valign="top">1</td>
</tr>
<tr>
<td width="58" valign="top">41</td>
<td width="160" valign="top">rowguid</td>
<td width="95" valign="top">16</td>
<td width="284" valign="top">E87029AA-2CBA-4C03-B948-D83AF0313E28</td>
</tr>
<tr>
<td width="58" valign="top">57</td>
<td width="160" valign="top">ModifiedDate</td>
<td width="95" valign="top">8</td>
<td width="284" valign="top">2010-02-20 18:13:03.710</td>
</tr>
<tr>
<td width="58" valign="top">65</td>
<td width="160" valign="top">Column Count</td>
<td width="95" valign="top">2</td>
<td width="284" valign="top"> </td>
</tr>
<tr>
<td width="58" valign="top">67</td>
<td width="160" valign="top">Variable Column Count</td>
<td width="95" valign="top">2</td>
<td width="284" valign="top">3</td>
</tr>
<tr>
<td width="58" valign="top">69</td>
<td width="160" valign="top">Variable Offets</td>
<td width="95" valign="top">6</td>
<td width="284" valign="top"> </td>
</tr>
<tr>
<td width="58" valign="top">75</td>
<td width="160" valign="top">NationalIDNumber</td>
<td width="95" valign="top">16</td>
<td width="284" valign="top">24756624</td>
</tr>
<tr>
<td width="58" valign="top">91</td>
<td width="160" valign="top">LoginID</td>
<td width="95" valign="top">44</td>
<td width="284" valign="top">adventure-works\david0</td>
</tr>
<tr>
<td width="58" valign="top">135</td>
<td width="160" valign="top">Title</td>
<td width="95" valign="top">100</td>
<td width="284" valign="top">Marketing Manager</td>
</tr>
</tbody>
</table>
<p>Again, because there are no nullable columns at all, there is no nullability bitmap within the structure. This mapping is specific to this row, but the offsets for the fixed portion of the row would be valid for all the records, it is only the variable width columns that would change per data row.</p>
<p>The two modifications are at byte 24 and 57, which is clearly the Marital Status and the Modified Date. </p>
<p>Why is the modified date being changed? The adventure works employee table has an update trigger on it, which alters the modified data column to be the current date when any change is made to the row.</p>
<p>For the Marital status transaction log entry, there are two values we need to logically know – what was it before the change, and what is the value after the change.</p>
<p>The Row Log Contents 0 and Row Log Contents 1 fields clearly provide that information</p>
<pre>Row Log Contents 0 : 0x53
Row Log Contents 1 : 0x4D
<span style="color:#0000ff;">select</span> <span style="color:#ff00ff;">convert</span>(nchar,0x53) = S
<span style="color:#0000ff;">select</span> <span style="color:#ff00ff;">convert</span>(nchar,0x4D) = M</pre>
<p>So they are clearly the before and after results.</p>
<p>Checking the transaction log entry against the trigger, the log row contents are dates within hex format, which are not too difficult to deal with</p>
<p>In Hex, and Row Log Contents  0 contains the previous modified date, although the SQL Management Studio results show the Hex has been shortened somewhat from the normal  8 bytes for a date. Padding the extra zeros in gives us:</p>
<pre>0x 00 00 00 00 35 95 00 00 = 0x 00 00 95 35 00 00 00 00  = 2004-07-31 00:00:00.000</pre>
<p>The same rules apply to the  RowLog Contents 1 which has also been cut short.</p>
<pre>0x A5 F1 D6 00 24 9D 00 00 =  00 00 9D 24 00 D6 F1 A5 =  2010-02-21 13:02:35.217</pre>
<p>Which is unsurprisingly today’s date and when I made the modification.</p>
<p>The immediate question is did the log contain two entries because there were two different causes of the change, or does it record a transaction log row per value modified. Since the Offset In Row value is within the transaction log row, you could guess at 1 entry per change.</p>
<p>To check, I restored the database to the starting position and issued a statement with two modifications:</p>
<pre><span style="color:#0000ff;">update </span>HumanResources.Employee
<span style="color:#0000ff;">set</span> MaritalStatus = <span style="color:#ff0000;">'M'</span>,
VacationHours = 60
<span style="color:#0000ff;">where</span> employeeid = 6</pre>
<p>The transaction log alters signficantly, the modified date LOP_MODIFY_ROW entry with an offset of 57 still gets added, but the alteration of the two values does not produce 2 x LOP_MODIFY_ROW but a single LOP_MODIFY_COLUMNS – this appears to have a more complex format that is going to be tougher to crack, so that will have to wait until another day.</p>
<br />Filed under: <a href='http://sqlfascination.com/category/sql-server/'>SQL Server</a> Tagged: <a href='http://sqlfascination.com/tag/internals/'>Internals</a>, <a href='http://sqlfascination.com/tag/sql-server-2005/'>SQL Server 2005</a>, <a href='http://sqlfascination.com/tag/sql-server-2008/'>SQL Server 2008</a>, <a href='http://sqlfascination.com/tag/transaction-log/'>Transaction Log</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/473/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/473/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/473/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/473/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/473/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/473/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/473/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/473/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/473/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/473/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/473/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/473/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/473/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/473/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=473&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2010/02/21/decoding-a-simple-update-statement-within-the-transaction-log/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>How Do You Decode A Simple Entry in the Transaction Log? (Part 2)</title>
		<link>http://sqlfascination.com/2010/02/05/how-do-you-decode-a-simple-entry-in-the-transaction-log-part-2/</link>
		<comments>http://sqlfascination.com/2010/02/05/how-do-you-decode-a-simple-entry-in-the-transaction-log-part-2/#comments</comments>
		<pubDate>Fri, 05 Feb 2010 08:56:44 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2005]]></category>
		<category><![CDATA[Transaction Log]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=452</guid>
		<description><![CDATA[In the previous post we pulled out the raw information relating to a transaction log entry for a deletion of a single row.  In this post we will decode the binary section of the log entry for the deletion performed. A reminder of the Row Log Contents for the Clustered Index log entry that we need to [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=452&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>In the <a href="http://sqlfascination.com/2010/02/03/how-do-you-decode-a-simple-entry-in-the-transaction-log-part-1/" target="_blank">previous post</a> we pulled out the raw information relating to a transaction log entry for a deletion of a single row.  In this post we will decode the binary section of the log entry for the deletion performed.</p>
<p>A reminder of the Row Log Contents for the Clustered Index log entry that we need to decode.</p>
<pre>0x3000410001000000B90400001000000000000000406700004D004D0000000000CB8900004A15001E004AD0E1AA37C27449B4D593524773771800000000359500001000000003005D008500BD003100340034003100370038003000370061006400760065006E0074007500720065002D0077006F0072006B0073005C006700750079003100500072006F00640075006300740069006F006E00200054006500630068006E0069006300690061006E0020002D0020005700430036003000</pre>
<p>So we have 180 bytes of information relating to the deletion, the question is &#8211; what is it? The most obvious answer has to be the record that has been deleted &#8211; lots of fixed log information has already been exposed as named fields, this generic variable length value must be the row that we deleted.</p>
<p>How to decode it though? applying the structure of a row is a good place,  the format mentioned before is detailed very nicely detailed in Kalen Delaney&#8217;s SQL Internal&#8217;s book so let&#8217;s use that as a guide.</p>
<ul>
<li>2 Byte : Status Bits</li>
<li>2 Bytes: Offset to find number of columns</li>
<li>X Bytes:Fixed Length Columns</li>
<li>2 Bytes: Total Number of Columns in the data row</li>
<li>1 Bit per column, Rounded up: Nullability Bitmap</li>
<li>2 Bytes:Number of Variable Length Columns within the data row</li>
<li>2 Bytes per variable length column : Row Offset marking the end of each variable length column</li>
<li>X Bytes:Variable Length Columns</li>
</ul>
<p>Ok, let&#8217;s start assigning the values from the log hex dump into the fields.</p>
<p>Status Bits (2 Bytes) : 3000<br />
Offset to Find Number of Columns (2 Bytes) : 4100<br />
Because of the endian ordering in effect, the offset should be read as 0041 &#8211; 65 in Decimal.  So there must be 61 Bytes of Fixed width data, since we have already used 4 bytes.</p>
<p>Let&#8217;s remind ourselves of the table structure:</p>
<pre>CREATE TABLE [HumanResources].[Employee](
 [EmployeeID] [int] IDENTITY(1,1) NOT NULL,
 [NationalIDNumber] [nvarchar](15) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [ContactID] [int] NOT NULL,
 [LoginID] [nvarchar](256) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [ManagerID] [int] NULL,
 [Title] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [BirthDate] [datetime] NOT NULL,
 [MaritalStatus] [nchar](1) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [Gender] [nchar](1) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [HireDate] [datetime] NOT NULL,
 [SalariedFlag] [dbo].[Flag] NOT NULL CONSTRAINT [DF_Employee_SalariedFlag]  DEFAULT ((1)),
 [VacationHours] [smallint] NOT NULL CONSTRAINT [DF_Employee_VacationHours]  DEFAULT ((0)),
 [SickLeaveHours] [smallint] NOT NULL CONSTRAINT [DF_Employee_SickLeaveHours]  DEFAULT ((0)),
 [CurrentFlag] [dbo].[Flag] NOT NULL CONSTRAINT [DF_Employee_CurrentFlag]  DEFAULT ((1)),
 [rowguid] [uniqueidentifier] ROWGUIDCOL  NOT NULL CONSTRAINT [DF_Employee_rowguid]  DEFAULT (newid()),
 [ModifiedDate] [datetime] NOT NULL CONSTRAINT [DF_Employee_ModifiedDate]  DEFAULT (getdate()),
 CONSTRAINT [PK_Employee_EmployeeID] PRIMARY KEY CLUSTERED</pre>
<p>The fixed columns are as follows:</p>
<pre>[EmployeeID] [int]  = 4
[ContactID] [int] = 4
[ManagerID] [int] = 4
[BirthDate] [datetime] = 8
[MaritalStatus] [nchar](1) = 2
[Gender] [nchar](1) = 2
[HireDate] [datetime] = 8
[SalariedFlag] [dbo].[Flag] = 1
[VacationHours] [smallint] = 2
[SickLeaveHours] [smallint] = 2
[CurrentFlag] [dbo].[Flag] = 0 - second bit.
[rowguid] [uniqueidentifier] = 16
[ModifiedDate] [datetime] = 8</pre>
<p>Adventure Works uses a user defined type of Flag, which is just declared as a bit. So thats 61 bytes of fixed width storage &#8211; which is the value we expected.</p>
<p>We need to know the column order, we could assume it is the same as the table declaration, but we should double check the actual colum ordering to be certain.</p>
<pre><span style="color:#0000ff;">select</span> colorder, syscolumns.name
<span style="color:#0000ff;">from</span> syscolumns
inner join systypes <span style="color:#0000ff;">on</span> syscolumns.xusertype = systypes.xusertype
<span style="color:#0000ff;">where </span>id =<span style="color:#ff00ff;">object_id</span>(<span style="color:#ff0000;">'HumanResources.Employee'</span>) and variable = 0
<span style="color:#0000ff;">order</span> <span style="color:#0000ff;">by</span> colorder

colorder name
-------- -------------
1        EmployeeID
3        ContactID
5        ManagerID
7        BirthDate
8        MaritalStatus
9        Gender
10       HireDate
11       SalariedFlag
12       VacationHours
13       SickLeaveHours
14       CurrentFlag
15       rowguid
16       ModifiedDate</pre>
<p>So the order looks good, let&#8217;s start assigning values:</p>
<pre>[EmployeeID] [int]  = 01000000 = 00000001 = 1
[ContactID] [int] = B9040000 = 000004B9 = 1209
[ManagerID] [int] = 10000000 = 00000001 = 1
[BirthDate] [datetime] = 0000000040670000 = 0000674000000000 = '1972-05-15 00:00:00.000'  - (Use Select convert(datetime, 0x0000674000000000) to check)
[MaritalStatus] [nchar](1) = 004D = 77 = UniCode for M (Use Select nchar(77) to check)
[Gender] [nchar](1) = 4D00 = 004D = 77 = M
[HireDate] [datetime] = 00000000CB890000 = 000089CB00000000 = '1996-07-31 00:00:00.000'
[SalariedFlag] [dbo].[Flag] = 4A = 01001010 in Binary, which will need further work.
[VacationHours] [smallint] = 1500 = 0015 = 21
[SickLeaveHours] [smallint] = 1E00 = 001E = 30
[CurrentFlag] [dbo].[Flag] = Contained within the First Flag Byte
[rowguid] [uniqueidentifier] = 4AD0E1AA37C27449B4D5935247737718
[ModifiedDate] [datetime] = 0000000035950000 = 0000953500000000 = '2004-07-31 00:00:00.000'
Number of Columns : 1000 = 0010 - = 16 Columns - which is correct.
Nullability Bitmap : 1 bit per Column, so 16/8 = 2 Bytes : 0000
Number of Variable Rows : 0300 = 0003 = 3 Variable Length Columns
Var Column End 1 : 5d00 = 005D = 93
Var Column End 2 : 8500 = 0085 = 133
Var Column End 3 : BD00 = 00BD = 189</pre>
<p>The number of columns was at the offset of 65, that used 2 bytes, the nullability was 2, the variable number of rows was 2 bytes and each variable row pointer used a further 2 each, so the data starts at byte after e.g. 65 + 12 = 77. First variable width data ends at 93, so the first variable width column is using 16 bytes of space.</p>
<p>I should mention why the nullability bitmap was 2, all the columns are declared as not null, so the bitmap did not increase in size, if there was a single nullable column, then the bitmap would have a bit per column in the table  &#8211; not a bit per nullable column, it is an all or nothing approach.</p>
<pre>Raw Values : 3100 3400 3400 3100 3700 3800 3000 3700

Reverse Bytes : 0031 0034 0034 0031 0037 0038 0030 0037
Result : 14417807  (You can use Select NChar(0x0031) etc to check each value.)</pre>
<p>Next up is 93 to 133, a gap of 40 bytes.</p>
<pre>Raw Values    : 6100 6400 7600 6500 6E00 7400 7500 7200 6500
2D00 7700 6F00 7200 6B00 7300 5C00 6700 7500 7900 3100
Reverse Bytes : 0061 0064 0076 0065 006E 0074 0075 0072 0065
002D 0077 006F 0072 006B 0073 005C 0067 0075 0079 0031
Output : adventure-works\guy1</pre>
<p>Final column, should be 189 &#8211; 133 = 56 bytes long, which it is, and decoding it again in the same way gives the following.</p>
<pre>Raw Values    : 5000 7200 6F00 6400 7500 6300 7400 6900 6F00 6E00 2000 5400 6500 6300
6800 6E00 6900 6300 6900 6100 6E00 2000 2D00 2000 5700 4300 3600 3000
Reverse Bytes : 0050 0072 006F 0064 0075 0063 0074 0069 006F 006E 0020 0054 0065 0063
 0068 006E 0069 0063 0069 0061 006E 0020 002D 0020 0057 0043 0036 0030
Result : Production Technician - WC60</pre>
<p>That is the row contents decoded, and the deleted row&#8217;s original values are now exposed.</p>
<p>As you can see from the complexity of decoding one part of a trivial delete operation within the log, this is not something that you would wish to rely on / use as an audit trail etc. But hopefully it has provided some insight and added to the minimal documentation on how to approach the decoding process. I will try to decode more of the operations as, insert and update shouldn&#8217;t be too hard to manage, but sucess is not guarenteed.</p>
<br />Filed under: <a href='http://sqlfascination.com/category/sql-server/'>SQL Server</a> Tagged: <a href='http://sqlfascination.com/tag/internals/'>Internals</a>, <a href='http://sqlfascination.com/tag/sql-server-2005/'>SQL Server 2005</a>, <a href='http://sqlfascination.com/tag/transaction-log/'>Transaction Log</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/452/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/452/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/452/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/452/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/452/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/452/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/452/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/452/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/452/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/452/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/452/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/452/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/452/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/452/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=452&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2010/02/05/how-do-you-decode-a-simple-entry-in-the-transaction-log-part-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>How Do You Decode A Simple Entry in the Transaction Log? (Part 1)</title>
		<link>http://sqlfascination.com/2010/02/03/how-do-you-decode-a-simple-entry-in-the-transaction-log-part-1/</link>
		<comments>http://sqlfascination.com/2010/02/03/how-do-you-decode-a-simple-entry-in-the-transaction-log-part-1/#comments</comments>
		<pubDate>Wed, 03 Feb 2010 22:35:31 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2005]]></category>
		<category><![CDATA[Transaction Log]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=438</guid>
		<description><![CDATA[There is always a lot of interest in reading log records within SQL, although when you start getting into it you soon realise that it is an undocumented quagmire and quickly decide that there are better things you can do with your time. It has taken a non-trivial amount of time to decode and has created a [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=438&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>There is always a lot of interest in reading log records within SQL, although when you start getting into it you soon realise that it is an undocumented quagmire and quickly decide that there are better things you can do with your time.</p>
<p>It has taken a non-trivial amount of time to decode and has created a long documented process, so it will be in two parts, so I should caveat this undertaking with two clear statements first:</p>
<ul>
<li>There is no real reason you want to go poking around in the log except for &#8216;interest value&#8217; &#8211; if you need to actually properly read log files then as you are about to see, using commercial applications will look very cheap in comparison to the time you would spend trying to do this. Even better &#8211; make sure you have proper backup&#8217;s so that you never need to try retrieve rows from the file!</li>
<li>There is basically very little external documentation to guide you in reading an entry, I am learning it by doing it, and using what little documentation there is, along with trying some logical deductions and good understanding of internals from training &#8211; it is designed to act as an example of figuring the log entry out, but also demonstrating that this is not trivial and not something you will want to do.</li>
</ul>
<p>That said, let&#8217;s start by trying to work out a single log record.</p>
<p>Using the adventure works example database, I have set it into fully logged mode and taken a backup to ensure that the transaction log is being used correctly. I&#8217;ve removed the foreign key constraints and triggers on the HumanResources.EmployeeTable, since I want to be able to delete a row and not trip over the example FK&#8217;s and trigger that prevents deletion.</p>
<p>To get to the log file values out, I am using the following snippet of SQL:</p>
<pre><span style="color:#0000ff;">Select </span>* <span style="color:#0000ff;">from</span> ::fn_dblog(null,null)</pre>
<p>On initial inspection the log has only a not too many rows, altering the Diff Map, File Header etc. So I issue the command to delete a single row from the table.</p>
<pre><span style="color:#0000ff;">Delete</span> <span style="color:#0000ff;">from </span>HumanResources.Employee <span style="color:#0000ff;">where</span> EmployeeId = 1</pre>
<p>And then select the contents of the log, and I have copied some of the fields from the output:</p>
<table>
<tbody>
<tr>
<th>Current LSN</th>
<th>Operation</th>
<th>Context</th>
<th>Transaction ID</th>
<th>Log Record Length</th>
<th>AllocUnitName</th>
</tr>
<tr>
<td>000001bd:000001d8:0002</td>
<td>LOP_BEGIN_XACT</td>
<td>LCX_NULL</td>
<td>0000:0000da58</td>
<td>96</td>
<td>NULL</td>
</tr>
<tr>
<td>000001bd:000001d8:0003</td>
<td>LOP_DELETE_ROWS</td>
<td>LCX_MARK_AS_GHOST</td>
<td>0000:0000da58</td>
<td>148</td>
<td>HumanResources.Employee.AK_Employee_LoginID</td>
</tr>
<tr>
<td>000001bd:000001d8:0004</td>
<td>LOP_SET_BITS</td>
<td>LCX_DIFF_MAP</td>
<td>0000:00000000</td>
<td>56</td>
<td>Unknown Alloc Unit</td>
</tr>
<tr>
<td>000001bd:000001d8:0005</td>
<td>LOP_DELETE_ROWS</td>
<td>LCX_MARK_AS_GHOST</td>
<td>0000:0000da58</td>
<td>124</td>
<td>HumanResources.Employee.AK_Employee_NationalIDNumber</td>
</tr>
<tr>
<td>000001bd:000001d8:0006</td>
<td>LOP_SET_BITS</td>
<td>LCX_DIFF_MAP</td>
<td>0000:00000000</td>
<td>56</td>
<td>Unknown Alloc Unit</td>
</tr>
<tr>
<td>000001bd:000001d8:0007</td>
<td>LOP_DELETE_ROWS</td>
<td>LCX_MARK_AS_GHOST</td>
<td>0000:0000da58</td>
<td>120</td>
<td>HumanResources.Employee.AK_Employee_rowguid</td>
</tr>
<tr>
<td>000001bd:000001d8:0008</td>
<td>LOP_DELETE_ROWS</td>
<td>LCX_MARK_AS_GHOST</td>
<td>0000:0000da58</td>
<td>108</td>
<td>HumanResources.Employee.IX_Employee_ManagerID</td>
</tr>
<tr>
<td>000001bd:000001d8:0009</td>
<td>LOP_SET_BITS</td>
<td>LCX_DIFF_MAP</td>
<td>0000:00000000</td>
<td>56</td>
<td>Unknown Alloc Unit</td>
</tr>
<tr>
<td>000001bd:000001d8:000a</td>
<td>LOP_DELETE_ROWS</td>
<td>LCX_MARK_AS_GHOST</td>
<td>0000:0000da58</td>
<td>288</td>
<td>HumanResources.Employee.PK_Employee_EmployeeID</td>
</tr>
<tr>
<td>000001bd:000001d8:000b</td>
<td>LOP_SET_BITS</td>
<td>LCX_PFS</td>
<td>0000:00000000</td>
<td>56</td>
<td>HumanResources.Employee.PK_Employee_EmployeeID</td>
</tr>
<tr>
<td>000001bd:000001d8:000c</td>
<td>LOP_COMMIT_XACT</td>
<td>LCX_NULL</td>
<td>0000:0000da58</td>
<td>52</td>
<td>NULL</td>
</tr>
</tbody>
</table>
<p>That&#8217;s a lot of entries for a single delete &#8211; which is explained when you check the AllocUnitName, the delete has to also delete the entries within the indexes and the Adventure works table I am working against does indeed have 5 indexes, 4 Non-clustered and 1 Clustered. So that is making a lot of sense, and the whole operation is surrounded by a LOP_BEGIN_XACT and LOP_COMMIT_XACT, and we know from normal SQL terminology with SET XACT ABORT ON / OFF that it is about transactional scope and whether a whole transaction rolls back if a single item within the statement.</p>
<p>Let&#8217;s concentrate on the record deletion for the clustered index, with the larger log length of 288. That is a long record for what is in theory marking the row as a ghost, which suggests the row is within the log record. This is also backed up by the differing lengths for the other ghost record marks, which differ in size, just as the index row size does.</p>
<p>What are the interesting fields available to us from the Log record that we can pick up on straight away:</p>
<ul>
<li>Transaction ID : All the deletions belong to the same transaction ID, so we know they are related / part of the same transactional operation.</li>
<li>Previous LSN : Each subsequent deletion in the list, shows the previous one as the previous LSN, so we can see the chain of log records.</li>
<li>Page ID : We can tell which page was altered.</li>
<li>Slot ID : Which slot within the page was altered.</li>
<li>SPID : The LOP_BEGIN_XACT row shows the SPID that issued the command</li>
<li>BeginTime : The LOP_BEGIN_XACT row shows the DateTime when the command was issued.</li>
<li>Transaction Name : The LOP_BEGIN_XACT row shows the type of transaction, for this one shows DELETE.</li>
<li>EndTime : The LOP_COMMIT_XACT row shows the end time of the transaction.</li>
</ul>
<p>Interestingly, there is no indication as to who issued the command, there is a TransactionSID &#8211; which might turn out to be the account SID, but that will be a tangental investigation.<br />
The final column to have a look at is RowLogContents, for the clustered index row deletion, rowlog contents 0 has the larger set of data to work on.</p>
<pre>0x3000410001000000B90400001000000000000000406700004D004D0000000000CB8900004A15001E004AD0E1AA37C27449B4D593524773771800000000359500001000000003005D008500BD003100340034003100370038003000370061006400760065006E0074007500720065002D0077006F0072006B0073005C006700750079003100500072006F00640075006300740069006F006E00200054006500630068006E0069006300690061006E0020002D0020005700430036003000</pre>
<p>If that does make you go running for the hills, then you have a chance of decoding it.</p>
<p>That&#8217;s only 180 Bytes, but this is the log row contents, not the actual log entry itself, so the fixed log fields are not within it, let&#8217;s work on it. The structure of a row is a good place, since we know expect the structure of the row to be present within the log. The format is very nicely detailed in Kalen Delaney&#8217;s SQL Internal&#8217;s book so let&#8217;s use that.</p>
<ul>
<li>2 Byte : Status Bits</li>
<li>2 Bytes: Offset to find number of columns</li>
<li>X Bytes:Fixed Length Columns</li>
<li>2 Bytes: Total Number of Columns in the data row</li>
<li>1 Bit per column, Rounded up: Nullability Bitmap</li>
<li>2 Bytes:Number of Variable Length Columns within the data row</li>
<li>2 Bytes per variable length column : Row Offset marking the end of each variable length column</li>
<li>X Bytes:Variable Length Columns</li>
</ul>
<p>We can use this in <a href="http://sqlfascination.com/2010/02/05/how-do-you-decode-a-simple-entry-in-the-transaction-log-part-2/" target="_blank">Part 2</a> to decode the binary contents of the log record.</p>
<br />Filed under: <a href='http://sqlfascination.com/category/sql-server/'>SQL Server</a> Tagged: <a href='http://sqlfascination.com/tag/internals/'>Internals</a>, <a href='http://sqlfascination.com/tag/sql-server-2005/'>SQL Server 2005</a>, <a href='http://sqlfascination.com/tag/transaction-log/'>Transaction Log</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/438/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/438/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/438/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/438/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/438/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/438/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/438/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/438/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/438/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/438/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/438/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/438/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/438/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/438/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=438&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2010/02/03/how-do-you-decode-a-simple-entry-in-the-transaction-log-part-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>How Can You Tell if a Database is in Pseudo Full/Bulk Logged Mode?</title>
		<link>http://sqlfascination.com/2009/12/13/how-can-you-tell-if-a-database-is-in-pseudo-fullbulk-logged-mode/</link>
		<comments>http://sqlfascination.com/2009/12/13/how-can-you-tell-if-a-database-is-in-pseudo-fullbulk-logged-mode/#comments</comments>
		<pubDate>Sun, 13 Dec 2009 17:39:21 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Backups]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2005]]></category>
		<category><![CDATA[SQL Server 2008]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=352</guid>
		<description><![CDATA[I was asked on Friday, &#8220;how do you tell if a database logging mode is reporting bulk or full, but it is still in simple?&#8221; &#8211; as mentioned before, a database is not in really full / bulk logged unless a full backup has been taken. Until that time the database is still running in [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=352&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I was asked on Friday, &#8220;how do you tell if a database logging mode is reporting bulk or full, but it is still in simple?&#8221; &#8211; as <a href="http://sqlfascination.com/2009/11/06/when-is-bulk-logged-mode-not-what-it-says/" target="_blank">mentioned before</a>, a database is not in really full / bulk logged unless a full backup has been taken. Until that time the database is still running in a simple mode, sometimes referred to as pseudo-simple. It is not easy to spot, because the properties of the database will report full / bulk as appropriate and give no indication that it is not actually logging in the way it says.</p>
<p>The existence of a backup of the database is not a reliable enough mechanism for this, since the database can be backed up and then moved out of full / bulk logged mode into simple and back again. This breaks the backup and transaction log chain, but the database is still reporting full &#8211; to make it worse there is a backup record showing on the history, giving it an air of legitimacy.</p>
<p>The backup records can be accessed from the sys.sysdatabases and msdb.dbo.backupset, <a href="http://code.msdn.microsoft.com/SQLExamples/Wiki/View.aspx?title=LastBackUpDate" target="_blank">MSDN</a> even has an example script showing how to see when a database was last backed up and by whom.</p>
<pre><span style="color:#0000ff;">SELECT </span>
T1.Name <span style="color:#0000ff;">as</span> DatabaseName, <span style="color:#ff00ff;">COALESCE</span>(<span style="color:#ff00ff;">Convert</span>(<span style="color:#0000ff;">varchar</span>(12), <span style="color:#ff00ff;">MAX</span>(T2.backup_finish_date), 101),<span style="color:#ff0000;">'Not Yet Taken'</span>) <span style="color:#0000ff;">as</span> LastBackUpTaken, <span style="color:#ff00ff;">COALESCE</span>(<span style="color:#ff00ff;">Convert</span>(<span style="color:#0000ff;">varchar</span>(12), <span style="color:#ff00ff;">MAX</span>(T2.user_name), 101),'NA') as UserName
<span style="color:#0000ff;">FROM </span><span style="color:#008000;">sys.sysdatabases </span>T1 LEFT OUTER JOIN msdb.dbo.backupset T2 <span style="color:#0000ff;">ON</span> T2.database_name = T1.name
<span style="color:#0000ff;">GROUP BY</span> T1.Name
<span style="color:#0000ff;">ORDER BY</span> T1.Name</pre>
<p>To play around with the scripts you probably want a test database:</p>
<pre><span style="color:#0000ff;">CREATE DATABASE </span>[LogModeTest]<span style="color:#0000ff;"> ON  PRIMARY</span>
( <span style="color:#0000ff;">NAME</span> = N<span style="color:#ff0000;">'LogModeTest'</span>, <span style="color:#0000ff;">FILENAME</span> = N<span style="color:#ff0000;">'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\DATA\LogModeTest.mdf' </span>, <span style="color:#0000ff;">SIZE</span> = 3072KB , <span style="color:#0000ff;">MAXSIZE</span> = UNLIMITED, <span style="color:#0000ff;">FILEGROWTH</span> = 1024KB )
 LOG ON
( <span style="color:#0000ff;">NAME</span> = N<span style="color:#ff0000;">'LogModeTest_log'</span>, <span style="color:#0000ff;">FILENAME</span> = N<span style="color:#ff0000;">'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\DATA\LogModeTest_log.ldf'</span> , <span style="color:#0000ff;">SIZE</span> = 1024KB , <span style="color:#0000ff;">MAXSIZE</span> = 2048GB , <span style="color:#0000ff;">FILEGROWTH</span> = 10%)
 <span style="color:#0000ff;">COLLATE</span> Latin1_General_CI_AI</pre>
<p>With a minor alteration to the MSDN script you can get the backup history for this database:</p>
<pre><span style="color:#0000ff;">SELECT</span>
T1.Name <span style="color:#0000ff;">as </span>DatabaseName, <span style="color:#ff00ff;">COALESCE</span>(<span style="color:#ff00ff;">Convert</span>(<span style="color:#0000ff;">varchar</span>(12), <span style="color:#ff00ff;">MAX</span>(T2.backup_finish_date), 101),<span style="color:#ff0000;">'Not Yet Taken'</span>) <span style="color:#0000ff;">as</span> LastBackUpTaken <span style="color:#0000ff;">FROM </span><span style="color:#008000;">sys.sysdatabases </span>T1
LEFT OUTER JOIN msdb.dbo.backupset T2 ON T2.database_name = T1.name
<span style="color:#0000ff;">WHERE</span> T1.Name = 'LogModeTest'
<span style="color:#0000ff;">GROUP BY</span> T1.Name</pre>
<p>The results show the database is not yet backed up:</p>
<pre>DatabaseName                  LastBackUpTaken 
----------------------------- ---------------
LogModeTest                   Not Yet Taken</pre>
<p>That is easy to fix, so let&#8217;s take a backup of the database and recheck the last backup value.</p>
<pre><span style="color:#0000ff;">BACKUP DATABASE</span> [LogModeTest]<span style="color:#0000ff;"> TO  DISK</span> = N<span style="color:#ff0000;">'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Backup\LogModeTest.bak'</span> <span style="color:#0000ff;">WITH</span> NOFORMAT, NOINIT,  <span style="color:#0000ff;">NAME</span> = N<span style="color:#ff0000;">'LogModeTest-Full Database Backup'</span>, SKIP, NOREWIND, NOUNLOAD,  STATS = 10

DatabaseName                   LastBackUpTaken 
------------------------------ ---------------
LogModeTest                    12/13/2009</pre>
<p>As expected the date of the backup is now set. If we alter the logging mode of the database to simple we will break the transaction log chain. To demonstrate the backup information being an unreliable source, let&#8217;s change to simple, create a table and then return to the fully logged mode.</p>
<pre><span style="color:#0000ff;">ALTER DATABASE</span> [LogModeTest] <span style="color:#0000ff;">SET </span>RECOVERY SIMPLE <span style="color:#0000ff;">WITH</span> NO_WAIT
<span style="color:#0000ff;">CREATE TABLE</span> foo(id <span style="color:#0000ff;">int</span> identity)
<span style="color:#0000ff;">ALTER DATABASE</span> [LogModeTest] <span style="color:#0000ff;">SET</span> RECOVERY FULL <span style="color:#0000ff;">WITH</span> NO_WAIT</pre>
<p>If we now attempt to backup the transaction log, SQL is going to throw an error.</p>
<pre><span style="color:#0000ff;">BACKUP LOG </span>[LogModeTest] <span style="color:#0000ff;">TO  DISK</span> = N<span style="color:#ff0000;">'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Backup\LogModeTest.bak'</span> <span style="color:#0000ff;">WITH</span> NOFORMAT, NOINIT,  <span style="color:#0000ff;">NAME</span> = N<span style="color:#ff0000;">'LogModeTest-Transaction Log  Backup'</span>, SKIP, NOREWIND, NOUNLOAD,  STATS = 10 

<span style="color:#ff0000;">Msg 4214, Level 16, State 1, Line 1
BACKUP LOG cannot be performed because there is no current database backup.
Msg 3013, Level 16, State 1, Line 1
BACKUP LOG is terminating abnormally.</span></pre>
<p>And if we check the database backup history using the MSDN script:</p>
<pre>DatabaseName                   LastBackUpTaken
------------------------------ ---------------
LogModeTest                    12/13/2009</pre>
<p>So the backup history continues to show a date of the last full backup even though the transaction log chain is now broken. SQL certainly knows the database has not had a full backup since swapping into fully logged mode, so any transaction log backup is invalid, thus the error.</p>
<p>There is an easier way to find out that you are in pseudo-simple mode, without trying to perform a transaction log backup:</p>
<pre><span style="color:#0000ff;">SELECT </span>name, <span style="color:#ff00ff;">COALESCE</span>(<span style="color:#ff00ff;">Convert</span>(<span style="color:#0000ff;">varchar</span>(30),last_log_backup_lsn), 'No Full Backup Taken') as BackupLSN
<span style="color:#0000ff;">FROM</span> <span style="color:#008000;">sys.databases</span>
<span style="color:#0000ff;">INNER JOIN</span><span style="color:#008000;"> sys.database_recovery_status</span> on <span style="color:#008000;">sys.databases</span>.database_id = <span style="color:#008000;">sys.database_recovery_status</span>.database_id</pre>
<p>Run this against your server and it lists the databases that have had a backup taken (by the existence of a backup LSN) and which have not had a full backup that could be used in recovery. If we then backup the database and recheck the values, the test database now records an LSN, showing it is out of psuedo-simple and into the full / bulk logged modes.</p>
<p>So that indicates whether we are in pseudo simple or not, but does not link back to the properties of the database to check what is the actual database logging mode &#8211; you are primarily only interested in databases that are not in simple mode in the first place, but are running in psuedo-simple due to the lack of a relevant full database backup. We can alter the query to handle this specific situation and the result is:</p>
<pre><span style="color:#0000ff;">SELECT</span> name, recovery_model_desc, <span style="color:#ff00ff;">COALESCE</span>(<span style="color:#ff00ff;">Convert</span>(<span style="color:#0000ff;">varchar</span>(30),last_log_backup_lsn), 'No Full Backup Taken') <span style="color:#0000ff;">as</span> BackupLSN
<span style="color:#0000ff;">FROM</span> <span style="color:#008000;">sys.databases</span>
<span style="color:#0000ff;">INNER JOIN</span> <span style="color:#008000;">sys.database_recovery_status</span> on<span style="color:#008000;"> sys.databases</span>.database_id = <span style="color:#008000;">sys.database_recovery_status</span>.database_id
<span style="color:#0000ff;">WHERE</span> <span style="color:#008000;">sys.databases</span>.recovery_model &lt;&gt; 3 <span style="color:#0000ff;">AND</span> last_log_backup_lsn is null</pre>
<p>If you run that query against your database server and get any results then you have databases that are not running the recovery mode they are indicating / you that you think they are &#8211; which would generally not be a good thing.</p>
<br />Posted in SQL Server Tagged: Backups, Internals, SQL Server 2005, SQL Server 2008 <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/352/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/352/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/352/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/352/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/352/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/352/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/352/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/352/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/352/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/352/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/352/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/352/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/352/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/352/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=352&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2009/12/13/how-can-you-tell-if-a-database-is-in-pseudo-fullbulk-logged-mode/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>Why is SQL Azure and Index Fragmentation a Bad Combination?</title>
		<link>http://sqlfascination.com/2009/11/25/why-is-sql-azure-and-index-fragmentation-a-bad-combination/</link>
		<comments>http://sqlfascination.com/2009/11/25/why-is-sql-azure-and-index-fragmentation-a-bad-combination/#comments</comments>
		<pubDate>Wed, 25 Nov 2009 22:20:10 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Indexes]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Azure]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=261</guid>
		<description><![CDATA[I&#8217;ve been thinking through and experimenting a bit more with some of the concepts in SQL Azure &#8211; specifically I was considering the impact of fragmentation on both the storage (in terms of the storage limit) as well as the maintenance. This is not a new issue, DBA&#8217;s face fragmentation regularly and can deal with it [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=261&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been thinking through and experimenting a bit more with some of the concepts in SQL Azure &#8211; specifically I was considering the impact of fragmentation on both the storage (in terms of the storage limit) as well as the maintenance. This is not a new issue, DBA&#8217;s face fragmentation regularly and can deal with it in a variety of ways, but with SQL Azure the problem looks magnified by a lack of tools and working space. Whilst looking into this, I then realised that there is an unfortunate consequence of not knowing how much data space your index is actually using.</p>
<p>Each table in SQL Azure has to have a clustered index if data is going to be inserted into it and clustered indexes can suffer from fragmentation if chosen poorly. The combination of SQL Azure and the time-honoured fragmentation provides three consequences about it, fragmentation:</p>
<ul>
<li>will occur and you have no way in which to measure it due to the lack of DMV support.</li>
<li>will create wasted space within your space allocation limit.</li>
<li>will reduce your performance.</li>
</ul>
<p>You could work it out if you knew how much space you had actually used vs. what the size of the data held is, but we are unable to measure either of those values. If you have chosen the data compression option on the index then even those values would not give you a fragmentation ratio.</p>
<p>This leaves us with a situation in which you can not know how much you are fragmented, meaning:</p>
<ul>
<li>You schedule a regular index rebuild.</li>
<li>Hope SQL Azure performs index rebuilds for you.</li>
</ul>
<p>I&#8217;m not aware of SQL Azure doing this for you &#8211; and you do not have SQL Agent facilities either.</p>
<p>So this seems very wrong, the concept of SQL Azure is to take away a lot of the implementation details and hassle from the subscriber &#8211; DR and failover is handled etc. But there looks to be as gap in which certain items such as fragmentation is falling &#8211; I have not seen any documentation saying SQL Azure handles it (but there could be some hidden somewhere and I hope there is!) and neither are you given the right tools in which to program and handle it yourself.</p>
<p>What happens when you hit that size limit?</p>
<pre><span style="color:#ff0000;">Msg 40544, Level 20, State 5, Line 1 The database has reached its size quota. Partition or delete data, drop indexes, or consult the documentation for possible resolutions. Code: 524289 </span></pre>
<p>That took a lot of time to get to, (SQL Azure is not fast), but was generated using a simple example that would also demonstrate fragmentation.</p>
<pre><span style="color:#0000ff;">Create Table</span> fragtest ( id <span style="color:#0000ff;">uniqueidentifier primary key clustered</span>,
padding <span style="color:#0000ff;">char</span>(3000)
) </pre>
<p>Very simple stuff, deliberately using a clustered key on a GUID to cause a decent level of fragmentation, and also using the padding fixed with character field to ensure 2 rows per page only, maximising the page splits.</p>
<pre><span style="color:#0000ff;">insert into</span> fragtest <span style="color:#0000ff;">values</span> (<span style="color:#ff00ff;">newid</span>(), <span style="color:#ff00ff;">replicate</span>(<span style="color:#ff0000;">'a'</span>,1000))
go 200000</pre>
<p>Because of the randomness of the newid() function, the level of fragmentation is not predictable but will certainly occur &#8211; in my test I hit the wall on 196,403 records and failed with an out of space message.</p>
<p>Given the 2 rows per page and the number of rows, with ~0% fragmentation the data should be able ~767Mb &#8211; that is considerably short of 1 Gb &#8211; so there is a significant level of fragmentation in there wasting space, about 23% of it. If you include the 2k per page being wasted by the awkward row size then the actual raw data stored is roughly ~60% of the overall size allowing for row overheads etc.</p>
<p>So there are two important points from this contrived example:</p>
<ul>
<li>You can lose significant space from bad design.</li>
<li>Doing this backs you into a corner that you will not be able to get out of &#8211; this is the worst part.</li>
</ul>
<p>How are you cornered? well, try work out how to get out of the situation and defrag the clustered index / free up the space, you could:</p>
<ul>
<li>Attempt an index rebuild.</li>
<li>Try to rebuild it with SORT_IN_TEMP.</li>
<li>Drop the index.</li>
<li>Delete data.</li>
</ul>
<p>The first three fail, the SORT_IN_TEMP is not supported and would not of rescued the situation either since you have no working space in which to write the newly sorted rows prior to removing the old ones.  So do you really want to delete data? I don&#8217;t think we can consider that an option for now.</p>
<p>This all seems like a &#8216;rock&#8217; and a &#8216;hard place&#8217;; whilst SQL Azure can support these data quantities,  it seems prudent that you never consider actually going close to them at all &#8211; and that you equally are going to find it difficult to understand if you are close to them, since there is no way of measuring the fragmentation. The alternative is that you manually rebuild indexes on a regular basis to control fragmentation, but then enough free space is going to have to be left to allow you to rebuild your largest index without running out of space &#8211; reducing your data capacity significantly.</p>
<p>The corner is not entirely closed off, the way out of the corner would be to create another SQL Azure database within my account and select the data from database1.fragtest to database2.fragtest and then drop the original table and transfer it back &#8211; not ideal but it would work in an emergency.</p>
<p>I think the key is to design to make sure you do not have to face this issue; keep your data quantities very much under the SQL Azure size limits, and watch for the potential of tables being larger than the remaining space and preventing an re-indexing from occurring.</p>
<p>Interested to know your thoughts on this one, and what other consequences of being close to the limit will come out.</p>
<br />Posted in SQL Server Tagged: Indexes, Internals, SQL Azure <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/261/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/261/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/261/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/261/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/261/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/261/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/261/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/261/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/261/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/261/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/261/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/261/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/261/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/261/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=261&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2009/11/25/why-is-sql-azure-and-index-fragmentation-a-bad-combination/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>The Sequence of an Index Uniquifier</title>
		<link>http://sqlfascination.com/2009/10/20/the-sequence-of-an-index-uniquifier/</link>
		<comments>http://sqlfascination.com/2009/10/20/the-sequence-of-an-index-uniquifier/#comments</comments>
		<pubDate>Tue, 20 Oct 2009 20:46:29 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2005]]></category>
		<category><![CDATA[Uniquifier]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=174</guid>
		<description><![CDATA[During a training session today, I was asked about the structure of the Uniquifier, and whether it was a straight identity column. Off the top of my head I couldn&#8217;t remember the exact structure, I considered it a 4 byte int, but was not sure whether it acted as a pure identity value or acted in a [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=174&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>During a training session today, I was asked about the structure of the Uniquifier, and whether it was a straight identity column. Off the top of my head I couldn&#8217;t remember the exact structure, I considered it a 4 byte int, but was not sure whether it acted as a pure identity value or acted in a more complex manner when incrementing, so decided to investigate it tonight.</p>
<p>To start from the beginning, an index uniquifier is the term given to the field that is automatically generated by SQL Server when you create a clustered index, but the index key is not specified as unique. Since each record in the table has to be uniquely identifiable, SQL will automatically assigned a 4 byte field to the row to make it unique, commonly called the &#8216;Uniquifier&#8217;. At this point I am sure English scholars will be frowning, pondering on the nature of the word and whether it qualifies as English; however that the term used so we will run with it.</p>
<p>It is actually quite easy to see this field in action, let&#8217;s create a simple table:</p>
<pre><span style="color:#0000ff;">CREATE TABLE</span> dbo.unique_test  (  
firstname char(20) NOT NULL,  
surname char(20) NOT NULL  
)  <span style="color:#0000ff;">ON</span> [PRIMARY] GO
<span style="color:#0000ff;">CREATE CLUSTERED INDEX</span> [ix_test] <span style="color:#0000ff;">ON</span> [dbo].[unique_test]
 (
[firstname] ASC
)
<span style="color:#0000ff;">ON</span> [PRIMARY]</pre>
<p>The clustered index is not unique, by design, so let&#8217;s start adding duplicate rows to see the effect:</p>
<pre><span style="color:#0000ff;">insert into</span> unique_test <span style="color:#0000ff;">values</span> ('John', 'Smith')
go 10</pre>
<p>The table now contains 10 rows, each with the same details. This does not cause any undue concern, because each row is actually still unique &#8211; the way to show this is using the DBCC INC and DBCC Page commands, I&#8217;ve cut the output down since it is so wide.</p>
<pre>dbcc ind ('testdb','unique_test',1)
PageFID PagePID     IAMFID IAMPID      PageType
------- ----------- ------ ----------- --------
1       41          NULL   NULL        10      
1       174         1      41          1       </pre>
<p>The output shows a data page numbered 174 for my example and the IAM page with an ID of 41. We can crack open the page and view the contents very easily using DBCC Page.</p>
<pre>dbcc dbcc traceon(3604)
dbcc page (idtest,1,174,3)</pre>
<p>The output is quite large, but in essence, the first record is stored with the following details: </p>
<pre>UNIQUIFIER = [NULL]                 
Slot 0 Column 1 Offset 0x4 Length 20
firstname = John                    
Slot 0 Column 2 Offset 0x18 Length 20</pre>
<p> The second record:</p>
<pre>Slot 1 Column 0 Offset 0x33 Length 4
UNIQUIFIER = 1                      
Slot 1 Column 1 Offset 0x4 Length 20
firstname = John                    
Slot 1 Column 2 Offset 0x18 Length 20
surname = Smith  </pre>
<p> The third record:</p>
<pre>Slot 2 Column 0 Offset 0x33 Length 4
UNIQUIFIER = 2                      
Slot 2 Column 1 Offset 0x4 Length 20
firstname = John                    
Slot 2 Column 2 Offset 0x18 Length 20
surname = Smith                     </pre>
<p>And so forth. The first record&#8217;s uniquifier is visible and clearly named within the data page, but set to null. The second copy of the same value receives the uniquifier of one, the third copy receives a 2 etc.  This count is maintained separately for each duplication, so the insert of a new name multiple times will also receive its own counter, beginning at null and working upwards, 1,2,3 etc. So just because the uniquifier is 4 bytes, this does not limit the total number of rows in the table to ~2.1 billion, but does logically limit the total number of duplicates to 2.1 billion. I must confess to not having tested that limit, generating 2.1 billion rows of duplicate data is not trivial and a scrapbook calculation predicts 435 hours of processing on a virtual pc. I suspect the error message it raises when it hits the limit would be interesting.</p>
<p>If we remove all the rows from the table and then add 10 more does the uniquifier reset? Easy to test but the short answer was no, the uniquifier continued to rise, 10 thru 19.</p>
<p>I was a bit suspicious of this since any requirement for the uniquifier to rise / remember what existed before requires it to be stored somewhere &#8211; it has to survive a crash after all, but there is no apparent place the current count is stored. If there was, you wouldn&#8217;t be storing just 1 value, you would be forced to store a value for each record key that had duplicates. This could run into thousands of separate counters being maintained per clustered key so it just doesn&#8217;t make sense that it is stored, it would be a very noticable overhead.</p>
<p>When checking the DBCC Ind for the empty table it insisted it still had a data page, but the only contents of the data page was a single ghost record &#8211; a row that has been marked as deleted. The ghost record was the for the &#8216;John Smith&#8217; with the highest uniquifier before, was this coincidence? The other ghost records had not hung around, so why did this one persist?</p>
<p>I dropped and recreated the table again, inserted 10 rows and then deleted them. Checking DBCC Ind the table still showed a HoBT IAM allocation page for the table and a data page, the data page contained a single ghost record, the one with a Uniquifier of 9 &#8211; the highest given out when 10 duplicates were added. Even waiting some considerable time the ghost record was not cleaned up, so it appears that it will not delete it.</p>
<p>If I added another duplicate row, it picked up the next number in the sequence (10) and shortly after the ghost record was removed from the page. Very convenient and not a coincidence at all &#8211; the memory of the last uniquifier given out  persists as a ghost record, even if all the duplicates for the table have been removed.  What seems strange is this ghost record hanging about, persisting an entire record, to keep the duplicate count for that key, when no instances of it remain on the table.</p>
<p>It can not possibly do this for every key since the space overhead would become very noticable again, so how does it choose what to persist, the last entry? unfortunately it doesn&#8217;t appear that simple at all, after a number of tests it appeared to only be interested in keeping the ghost entry for the record that had the highest key value, so alphabetically, the one closed to &#8216;Z&#8217; for my example.</p>
<p>Conclusion? On the evidence, whilst the other ghost records still persist for a short time, even deleting and then adding more duplicates can see the number continue from where it left of, but given a short time for the ghost records to be removed the uniquifier will restart the sequence back at Null,1,2 etc. Except in the case of the highest entry from the index perspective, that ghost record decides to stick around until there is another entry using the same key, continuing the sequence, at which point the ghost record then finally disappears.</p>
<p>I can not think of any sensible reason why it would do this, can you?</p>
<p>Overall, the uniquifier is a cost overhead of not having a unique index, and at a cost of 4 bytes, an int identity column makes a lot of sense - for all purposes it acts the same and serves the same purpose but in a far more visible manner &#8211; so it really does not make much sense to rely on the uniquifier provided for you, take control and create your own.</p>
<br />Posted in SQL Server Tagged: Internals, SQL Server 2005, Uniquifier <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/174/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/174/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/174/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/174/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/174/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/174/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/174/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/174/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/174/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/174/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/174/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/174/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/174/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/174/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=174&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2009/10/20/the-sequence-of-an-index-uniquifier/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>What is the SQL Server 2008 DateTimeOffset Internal Structure?</title>
		<link>http://sqlfascination.com/2009/10/13/what-is-the-sql-server-2008-datetimeoffset-internal-structure/</link>
		<comments>http://sqlfascination.com/2009/10/13/what-is-the-sql-server-2008-datetimeoffset-internal-structure/#comments</comments>
		<pubDate>Tue, 13 Oct 2009 19:50:12 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[DateTimeOffset]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2008]]></category>

		<guid isPermaLink="false">http://sqlfascination.com/?p=137</guid>
		<description><![CDATA[After decoding the DateTime2 internal structure I thought I would take a quick look at the DateTimeOffset structure, since it should not present too many difficulties and post it up quickly, but was surprised at the initial result of a test. It follows the same basic premise that the time portion is followed by the date portion [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=137&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>After decoding the <a title="DateTime2 Internal Structure" href="http://sqlfascination.com/2009/10/11/what-is-the-sql-server-2008-datetime2-internal-structure/">DateTime2 internal structure</a> I thought I would take a quick look at the DateTimeOffset structure, since it should not present too many difficulties and post it up quickly, but was surprised at the initial result of a test. It follows the same basic premise that the time portion is followed by the date portion and the time / date is based on a day count from the epoch time of 0001/01/01 and the time the number of intervals since midnight, where the interval is defined by the accuracy.</p>
<p>I was expecting the datetime offset value itself to occupy the additional 2 bytes quoted, and not affect the other values. Bad assumption, as soon as I cracked open a few examples I could immediately see that setting the offset also alters the underlying time / date component values as well.</p>
<p>Using the same comparison methods as before the underlying time value is clearly being adjusted:</p>
<pre>'0001/01/01 00:00:00 -0:00' =&gt; 0x0700000000000000000000
'0001/01/01 00:00:00 -12:00' =&gt; 0x0700E034956400000030FD</pre>
<p>So, even though an offset is being stored, the underlying time is also being altered to match, and the internal storage is using UTC as the reference point. This makes sense as the most valid reference that you could use.</p>
<pre>'0001-01-01 12:00:00.0000000 +00:00' =&gt; 0x0700E03495640000000000
'0001-01-01 00:00:00.0000000 -12:00' =&gt; 0x0700E034956400000030FD</pre>
<p>The same time / date is generated for the two values, but the last two bytes hold the offset and have stored the offset used when the date was initially stored. The underlying storage of the time though is clearly identical in both,  so UTC is the common ground they each get stored again.</p>
<p>The final 2 bytes for the offset are pretty easy to decode, since the pattern is the same as before with a slight twist. The offset time records the number of minutes for the offset in hex, with the first byte of the two being the least significant as before with the time, so you end up reading the two bytes left-to-right and then decode that byte right-to-left.</p>
<p>The twist is that for positive offsets, the value increments 1,2,3, in hex as appropriate, but for negative values, it starts decrementing by considering -1 equal to &#8216;FFFF&#8217;, I&#8217;ve split the hex output into the individual components by adding some spaces to make it easier to read. (Accuracy Code, Time Value, Date Value, Offset Used)</p>
<pre>'2001-01-01 12:00:00.0000000 +00:01' =&gt; 0x07   009A717164   75250B  0100
'2001-01-01 12:00:00.0000000 +00:00' =&gt; 0x07   00E0349564   75250B  0000
'2001-01-01 12:00:00.0000000 -00:01' =&gt; 0x07   0026F8B864   75250B  FFFF</pre>
<p>Since the offsets supported at only +14 hours to -14 hours, there is no risk of the two ranges overlapping. When I think about this a bit more, it is acting as a signed number, -1 being 11111111111 etc. So the 2 bytes at the end is a signed int of the number of minutes offset.</p>
<p>There are a number of time zones in the world that do not occur at exact hourly intervals from UTC, some are on the half hour mark such as Caracas (-4:30) or Delhi (+5:30) to name a few, whilst Kathmandu (+5:45) is even more specific.  In theory the format allows offsets specified to even greater levels of distinction, although I am not sure as to why you would wish to use it. Do you really want an offset of +3:17 minutes? That is potentially scary to consider that as a valid input to the value.</p>
<p>That has made me wonder as to why the accuracy was set so high, when in reality 15 minute intervals would of been sufficient, with a -14 to +14 range, that is 113 different values inclusively, which could be accomodated within a single byte.</p>
<p>So why spend 2 bytes on the format, when 1 was enough? Was it to just be compatible to the ISO format in some way that required it? Not sure.</p>
<br />Posted in SQL Server Tagged: DateTimeOffset, Internals, SQL Server 2008 <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/137/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/137/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/137/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/137/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/137/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/137/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/137/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/137/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/137/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/137/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/137/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/137/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/137/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/137/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=137&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2009/10/13/what-is-the-sql-server-2008-datetimeoffset-internal-structure/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
		<item>
		<title>What is the SQL Server 2008 DateTime2 Internal Structure?</title>
		<link>http://sqlfascination.com/2009/10/11/what-is-the-sql-server-2008-datetime2-internal-structure/</link>
		<comments>http://sqlfascination.com/2009/10/11/what-is-the-sql-server-2008-datetime2-internal-structure/#comments</comments>
		<pubDate>Sun, 11 Oct 2009 22:15:47 +0000</pubDate>
		<dc:creator>Andrew Hogg</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[DateTime2]]></category>
		<category><![CDATA[Internals]]></category>
		<category><![CDATA[SQL Server 2008]]></category>

		<guid isPermaLink="false">http://andrewhogg.wordpress.com/?p=121</guid>
		<description><![CDATA[SQL Server has a number of new date time formats, but the one I am most interested in is DateTime2. The internal format of the SQL DateTime is commonly mistaken as 2&#215;4 byte integers, with the latter integer being milliseconds since midnight. It is in fact the number of 1/300ths of a second since midnight which is [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=121&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>SQL Server has a number of new date time formats, but the one I am most interested in is DateTime2. The internal format of the SQL DateTime is commonly mistaken as 2&#215;4 byte integers, with the latter integer being milliseconds since midnight. It is in fact the number of 1/300ths of a second since midnight which is why the accuracy of the DateTime within SQL Server has historically been 3.33ms. (If you really want to see it, crack it open by converting it to a binary, adding 1 and re-converting, you add 3.33ms, not 1 ms.)</p>
<p>So DateTime2 must use a different format, and as a weekend exercise that had no purpose than understanding the internals I thought I&#8217;d take a look. I have not seen the information in the BoL or posted as yet, so might be of use.  I am starting with the DateTime2(7) and looking at the maximum accuracy structure. The code used to crack it open each time is basically as follows:</p>
<pre><span style="color:#0000ff;">declare</span> @dt <span style="color:#0000ff;">datetime2</span>(7)
<span style="color:#0000ff;">set</span> @dt = <span style="color:#ff0000;">'2000/01/01 00:00:00'
</span><span style="color:#0000ff;">declare</span> @bin <span style="color:#0000ff;">varbinary</span>(<span style="color:#ff00ff;">max</span>)
<span style="color:#0000ff;">set</span> @bin = <span style="color:#ff00ff;">CONVERT</span>(<span style="color:#0000ff;">varbinary</span>(<span style="color:#ff00ff;">max</span>), @dt)</pre>
<p>To make my life easier, SQL conveniently outputs all the values as hexi-decimal numbers. The results are not what you would expect.</p>
<pre>0x07000000000007240B</pre>
<p>The date which traditionally occupied the first 4 bytes, clearly is occupying the last few bytes. So the format is not going to be obvious or simple. Interestingly the returned result is 9 bytes, but the length is quoted as 8. It is returning 8 when checked using the length, that first byte is somewhat odd to make an appearance.  It&#8217;s also suspiciously the accuracy value, and with a few tests using a change of accuracy, it show that value changes. So the first pseudo-byte is the accuracy indicator.</p>
<p>To start figuring out some more, let&#8217;s take the time back to the beginning point, which in this case is not 1900/01/01 but 0001/01/01 which when converted gives us:</p>
<pre>'0001/01/01 00:00:00' =&gt; 0x070000000000000000</pre>
<p>Start incrementing the day portion and there is an obvious pattern, the 6th byte changes.</p>
<pre>'0001/01/02 00:00:00' =&gt; 0x070000000000010000
'0001/01/03 00:00:00' =&gt; 0x070000000000020000
'0001/01/31 00:00:00' =&gt; 0x0700000000001E0000</pre>
<p>As you try the 2nd month, to check where the month is, the same byte alters, so it represents days, not specific date parts. Is it the number of days since the beginning of the year? No.</p>
<pre>'0001/02/01 00:00:00' =&gt; 0x0700000000001F0000</pre>
<p>If it was, there would be an issue since 1 byte does not represent enough values, as we can see, FF occurs on the 13th of September, and then it rolls over and puts a 1 in the 7th Byte position.</p>
<pre>'0001/09/13 00:00:00' =&gt; 0x070000000000FF0000
'0001/09/14 00:00:00' =&gt; 0x070000000000000100
'0001/09/15 00:00:00' =&gt; 0x070000000000010100</pre>
<p>It rolls over, then carries on as before. This immediately suggests the next test, to roll over the year, and the pattern continues.</p>
<pre>'0001/12/31 00:00:00' =&gt; 0x0700000000006C0100  
'0001/12/31 00:00:00' =&gt; 0x0700000000006D0100</pre>
<p>So the format is just counting, we see it in the example as hex, but it is a straight number count going on but the hex values are left-to-right. Only 2 bytes are used so far, which do not represent enough day combinations, add the third byte in by going past 180 years:</p>
<pre>'0180/06/06 00:00:00' =&gt; 0x070000000000FFFF00  
'0180/06/07 00:00:00' =&gt; 0x070000000000000001</pre>
<p>So the final byte is then increased, so the number of combinations becomes 16777215 &#8211; that seems a lot better and certainly going to cover the range required.</p>
<pre>'2001/01/01 00:00:00' =&gt; 0x07000000000075250B</pre>
<p>So that is the final 3 bytes decoded, a simple pattern - and provides the template of how the time is also stored.</p>
<pre>'0001/01/01 00:00:00.0000000' =&gt; 0x070000000000000000
'0001/01/01 00:00:00.0000001' =&gt; 0x070100000000000000
'0001/01/01 00:00:00.0000255' =&gt; 0x07FF00000000000000
'0001/01/01 00:00:00.0065535' =&gt; 0x07FFFF000000000000
'0001/01/01 00:00:00.0065536' =&gt; 0x070000010000000000</pre>
<p>So to check whether the format is the same,</p>
<pre>'0001/01/01 00:00:00.9999999' =&gt; 0x077F96980000000000</pre>
<p>Decode that again and it all matches:</p>
<pre><span style="color:#0000ff;">select </span>(152 * 256 * 256) + (150 * 256) + 127
-----------
9999999</pre>
<p>When we click over into 1 second exactly, we increment the first byte by 1, so the time portion is still represented in 100ns intervals, with the normal system of each byte counting up 1 every time the previous byte rolls over. As we get to the limit of the 3 bytes, it rolls into the 4th and then the 5th.</p>
<pre>'0001/01/01 00:00:01.0000000' =&gt; 0x078096980000000000</pre>
<p>So the internal format of the DateTime2(7) is decoded, not difficult but it is an interesting choice &#8211; it is now a straight binary number, with the Least Significant Byte being on the Left, the Most Significant being on the right (for each section.) Within the byte however, to convert it you must still read it right-to-left.</p>
<p>The first 5 bytes are recording how many time units intervals have passed since midnight, and the last 3 bytes recording how many days have passed since 0001/01/01.</p>
<p>The time unit intervals are dictated by the accuracy of the number, 100ns for DateTime2(7), and 1 Micro second intervals for a DateTime2(6) etc.  The way in which you interpret it does not change, but the units you are multiplying the time portion by, alters based on the accuracy.</p>
<p>You could construct 2 dates that are identical at a binary level, but due to the field meta-data on accuracy, they do not represent the same date time.</p>
<pre>declare @dt1 dt1 datetime2(6)
set @dt1 = '0001/01/01 00:00:00.000001'
declare @dt2 datetime2(7)
set @dt2 = '0001/01/01 00:00:00.0000001'

0x060100000000000000
0x070100000000000000 </pre>
<p> And that is perhaps why on output they automatically have prefixed the binary value with the datetime accuracy, so that they are not entirely identical? I&#8217;m not sure but would be interested to find out.</p>
<br />Posted in SQL Server Tagged: DateTime2, Internals, SQL Server 2008 <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andrewhogg.wordpress.com/121/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andrewhogg.wordpress.com/121/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andrewhogg.wordpress.com/121/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andrewhogg.wordpress.com/121/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/andrewhogg.wordpress.com/121/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/andrewhogg.wordpress.com/121/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/andrewhogg.wordpress.com/121/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/andrewhogg.wordpress.com/121/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andrewhogg.wordpress.com/121/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andrewhogg.wordpress.com/121/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andrewhogg.wordpress.com/121/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andrewhogg.wordpress.com/121/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andrewhogg.wordpress.com/121/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andrewhogg.wordpress.com/121/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=sqlfascination.com&amp;blog=9662534&amp;post=121&amp;subd=andrewhogg&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://sqlfascination.com/2009/10/11/what-is-the-sql-server-2008-datetime2-internal-structure/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/8215e290861f1c44a457d26c4f24af70?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">andrewhogg</media:title>
		</media:content>
	</item>
	</channel>
</rss>
