<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss 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:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>sqlity.net</title>
	
	<link>http://sqlity.net/en</link>
	<description>Quality for SQL</description>
	<lastBuildDate>Fri, 03 Feb 2012 14:38:44 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/sqlitynet" /><feedburner:info uri="sqlitynet" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>Selecting the entire Database as XML String – 2</title>
		<link>http://feedproxy.google.com/~r/sqlitynet/~3/mpDbqxzeaD4/</link>
		<comments>http://sqlity.net/en/626/selecting-the-entire-database-as-xml-string-2/#comments</comments>
		<pubDate>Wed, 01 Feb 2012 17:11:49 +0000</pubDate>
		<dc:creator>Sebastian</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[XML]]></category>

		<guid isPermaLink="false">http://sqlity.net/en/?p=626</guid>
		<description><![CDATA[About two weeks ago I wrote about how to get the content of the entire database into an XML document. Today Daniel commented, that he would like the XML to be in a compacter format. In this post I am going to explain how to get there. While WordPress&#8482; conveniently ate the example XML Daniel <a href="http://sqlity.net/en/626/selecting-the-entire-database-as-xml-string-2/#more-626'" class="more-link">more »</a>]]></description>
			<content:encoded><![CDATA[<div>
<p>
About two weeks ago I wrote about <a href="http://sqlity.net/en/577/selecting-the-entire-database-as-xml-string/">how to get the content of the entire database into an XML document</a>. Today <a href="http://sqlity.net/en/577/selecting-the-entire-database-as-xml-string/#comment-18" title="Daniel's comment">Daniel commented</a>, that he would like the XML to be in a compacter format. In this post I am going to explain how to get there.
</p>
<p>
While WordPress&trade; conveniently ate the example XML Daniel tried to post, I will assume for this article that he was going for this format:
</p>
<div>
<pre class="brush: xml; title: ; notranslate">
&lt;tables&gt;
  &lt;table name=&quot;[dbo].[T1366]&quot;&gt;
    &lt;row id=&quot;1&quot; c1=&quot;1366&quot; c2=&quot;2732&quot; c3=&quot;4098&quot; c4=&quot;5464&quot; c5=&quot;6830&quot; c6=&quot;8196&quot; /&gt;
    &lt;row id=&quot;2&quot; c1=&quot;1366&quot; c2=&quot;2732&quot; c3=&quot;4098&quot; c4=&quot;5464&quot; c5=&quot;6830&quot; c6=&quot;8196&quot; /&gt;
  &lt;/table&gt;
  &lt;table name=&quot;[dbo].[T127]&quot;&gt;
    &lt;row id=&quot;1&quot; c1=&quot;127&quot; c2=&quot;254&quot; c3=&quot;381&quot; c4=&quot;508&quot; c5=&quot;635&quot; c6=&quot;762&quot; c7=&quot;889&quot; /&gt;
    &lt;row id=&quot;2&quot; c1=&quot;127&quot; c2=&quot;254&quot; c3=&quot;381&quot; c4=&quot;508&quot; c5=&quot;635&quot; c6=&quot;762&quot; c7=&quot;889&quot; /&gt;
  &lt;/table&gt;
&lt;/tables&gt;
</pre>
</div>
<p>
Instead of all column values being sub-nodes of their &lt;row&gt; nodes they are now attributes of an empty &lt;row&gt; node. Also the &lt;data&gt; node between &lt;table&gt; and &lt;row&gt; is now missing, as it was kind of superfluous. And while I was changing the code anyway, I went ahead and added the previously missing root node as &lt;tables&gt;.
</p>
<p>
So how did I get there?
</p>
<p>
First let us look at the missing root node, which turns out to be the simplest of all the changes. When you select rows for a table specifying the FOR XML PATH('NodeName') directive, every row is going to be represented as a node with the passed in name. Those nodes are just hanging together without an enclosing root node. To make the output a valid XML document you can just specify the ROOT clause next to the PATH clause like this:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
  SELECT * 
    FROM dbo.Table
  FOR XML PATH('rowNodeName'),ROOT('rootNodeName'),TYPE
</pre>
</div>
<p>
The TYPE directive tells SQL Server to return the XML as a value with the XML type. If you leave it out the XML gets returned as a string, which can create problems if you want to continue to work with it in T-SQL.
</p>
<p>
The next step was, to get rid of the &lt;data&gt; node. When you specify an XML value in a select statement that is in turn using FOR XML PATH, that value is wrapped in a node with the value's column name. The generated SQL in my original solution looked like this:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
SELECT * FROM(
SELECT '[dbo].[t1]' [@name],(SELECT * FROM dbo.t1 FOR XML PATH('row'),TYPE) data
UNION ALL
SELECT '[dbo].[t2]' [@name],(SELECT * FROM dbo.t2 FOR XML PATH('row'),TYPE) data
)X FOR XML PATH('table'),TYPE;
</pre>
</div>
<p>
The data column in the UNION ALL query contains XML values, so the outer SELECT * ... FOR XML PATH('table') is going to wrap each one into a &lt;data&gt; node like this:
</p>
<div>
<pre class="brush: xml; title: ; notranslate">
&lt;table name=&quot;[dbo].[t1]&quot;&gt;
  &lt;data&gt;
    &lt;row&gt;
      &lt;id&gt;1&lt;/id&gt;
      &lt;c1&gt;1&lt;/c1&gt;
    &lt;/row&gt;
    &lt;row&gt;
      &lt;id&gt;2&lt;/id&gt;
      &lt;c1&gt;1&lt;/c1&gt;
    &lt;/row&gt;
  &lt;/data&gt;
&lt;/table&gt;
&lt;table name=&quot;[dbo].[t2]&quot;&gt;
  &lt;data&gt;
    &lt;row&gt;
      &lt;id&gt;1&lt;/id&gt;
      &lt;c1&gt;2&lt;/c1&gt;
      &lt;c2&gt;4&lt;/c2&gt;
    &lt;/row&gt;
    &lt;row&gt;
      &lt;id&gt;2&lt;/id&gt;
      &lt;c1&gt;2&lt;/c1&gt;
      &lt;c2&gt;4&lt;/c2&gt;
    &lt;/row&gt;
  &lt;/data&gt;
&lt;/table&gt;
</pre>
</div>
<p>
To remove the additional node we can use the fact that SQL Sever will inline the value of a column that does not have a name. So we need to select the data column without specifying its name. To do that we will use a trick:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
SELECT [@name],(SELECT data) FROM(
SELECT '[dbo].[t1]' [@name],(SELECT * FROM dbo.t1 FOR XML PATH('row'),TYPE) data
UNION ALL
SELECT '[dbo].[t2]' [@name],(SELECT * FROM dbo.t2 FOR XML PATH('row'),TYPE) data
)X FOR XML PATH('table'),TYPE;
</pre>
</div>
<p>
By wrapping the data column in its own SELECT it loses its name property for the outer SELECT statement. The output it generates now looks like this:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
&lt;table name=&quot;[dbo].[t1]&quot;&gt;
  &lt;row&gt;
    &lt;id&gt;1&lt;/id&gt;
    &lt;c1&gt;1&lt;/c1&gt;
  &lt;/row&gt;
  &lt;row&gt;
    &lt;id&gt;2&lt;/id&gt;
    &lt;c1&gt;1&lt;/c1&gt;
  &lt;/row&gt;
&lt;/table&gt;
&lt;table name=&quot;[dbo].[t2]&quot;&gt;
  &lt;row&gt;
    &lt;id&gt;1&lt;/id&gt;
    &lt;c1&gt;2&lt;/c1&gt;
    &lt;c2&gt;4&lt;/c2&gt;
  &lt;/row&gt;
  &lt;row&gt;
    &lt;id&gt;2&lt;/id&gt;
    &lt;c1&gt;2&lt;/c1&gt;
    &lt;c2&gt;4&lt;/c2&gt;
  &lt;/row&gt;
&lt;/table&gt;
</pre>
</div>
<p>
 The last thing we need to accomplish is to make the column values attributes of each &lt;row&gt; node instead of sub-nodes. If you specify a column name with a leading "@" sign, FOR XML PATH will make that value an attribute with that name (without the "@"):
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
SELECT id AS [@id] FROM dbo.t1 FOR XML PATH('row'),TYPE;
</pre>
<div>
<p>
The output of above statement looks like this:
<div>
<pre class="brush: xml; title: ; notranslate">
&lt;row id=&quot;1&quot; /&gt;
&lt;row id=&quot;2&quot; /&gt;
</pre>
</div>
<p>
So instead of a SELECT * FROM for each table, we need to list all columns in the format shown above. To get this specially formatted column list we are going to use <a href="http://sqlity.net/en/400/t-sql-tuesday-22-data-presentation/">XML concatenation</a> one more time:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
SELECT STUFF((SELECT ','+QUOTENAME(name)+' AS '+QUOTENAME('@'+name) 
                              FROM sys.columns c 
                             WHERE c.object_id = OBJECT_ID('dbo.t2')
                             ORDER BY column_id 
                            FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'');
</pre>
</div>
<p>
This produces the desired list:
<pre>[id] AS [@id],[c1] AS [@c1],[c2] AS [@c2]</pre>
</p>
<p>
With that we have all the parts collected to create the new "compact" solution. The last step is to put it all together:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
DECLARE @cmd NVARCHAR(MAX) ;
SET @cmd = 'SELECT [@name],(SELECT data) FROM('
    + STUFF(
(SELECT ' UNION ALL SELECT ''' + QUOTENAME(OBJECT_SCHEMA_NAME(object_id))
        + '.' + QUOTENAME(t.name) + ''' [@name],' + '(SELECT '+c.ColList+' FROM '
        + QUOTENAME(OBJECT_SCHEMA_NAME(object_id)) + '.' + QUOTENAME(t.name)
        + ' FOR XML PATH(''row''),TYPE) data'
 FROM   sys.tables t
 CROSS APPLY (SELECT STUFF((SELECT ','+QUOTENAME(name)+' AS '+QUOTENAME('@'+name) 
                              FROM sys.columns c 
                             WHERE c.object_id = t.object_id
                             ORDER BY column_id 
                            FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
             )c(ColList)
    FOR     XML PATH('') ,
                TYPE).value('.', 'NVARCHAR(MAX)'), 1, 11, '')
    + ')X FOR XML PATH(''table''),ROOT(''tables''),TYPE;' ;

EXEC(@cmd) ;
</pre>
</div>
<p>
This produces the desired output that was shown all the way at the beginning of this article. The size of the resulting XML document for my example database went from about 1.4 MB for the original solution to now about 0.5 MB &ndash; a significant improvement.
</p>
</div><img src="http://feeds.feedburner.com/~r/sqlitynet/~4/mpDbqxzeaD4" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://sqlity.net/en/626/selecting-the-entire-database-as-xml-string-2/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://sqlity.net/en/626/selecting-the-entire-database-as-xml-string-2/</feedburner:origLink></item>
		<item>
		<title>The Performance Impact of Forwarded Records on Table Scans</title>
		<link>http://feedproxy.google.com/~r/sqlitynet/~3/m7p0XluTc3M/</link>
		<comments>http://sqlity.net/en/609/the-performance-impact-of-forwarded-records-on-table-scans/#comments</comments>
		<pubDate>Tue, 31 Jan 2012 16:00:18 +0000</pubDate>
		<dc:creator>Sebastian</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[SQL Server Internals]]></category>

		<guid isPermaLink="false">http://sqlity.net/en/?p=609</guid>
		<description><![CDATA[Forwarded records are a mechanism that SQL Server employs to reduce the amount of maintenance work for indexes on a table without a clustered index. During an update of a record in a table that does not have a clustered index, an increase in size might prevent the record to fit on its current page. <a href="http://sqlity.net/en/609/the-performance-impact-of-forwarded-records-on-table-scans/#more-609'" class="more-link">more »</a>]]></description>
			<content:encoded><![CDATA[<div>
<p>
Forwarded records are a mechanism that SQL Server employs to reduce the amount of maintenance work for indexes on a table without a clustered index. During an update of a record in a table that does not have a clustered index, an increase in size might prevent the record to fit on its current page. If that happens, a forwarding pointer is created in the current page and the record is moved to a new location.
</p>
<p>
It is well known that such forwarded records will cause each RID-Lookup operation that encounters one to have to read a second page. If you are doing a filtered index scan followed by an RID-Lookup this can potentially double the amount of work necessary to retrieve all rows.
</p>
<p>
What is less well known is the fact, that such forwarded records can significantly impact the performance of a simple table scan as well. In this article I will show you why this happens.
</p>
<h3>Terminology</h3>
<p>
To explain the way SQL Server handles forwarded records during a table scan, I am first going to rehash some storage basics.
</p>
<p>
SQL Server knows two ways to organize the data in a table on disk. One is the Clustered Index and the other one the Heap. In a Clustered Index data is organized in a B+Tree structure allowing for fast access based on the Clustered Index Key. In a Heap on the other hand the data is not sorted or linked in any way. Any new row is inserted into the table in any free space big enough to hold the row.
</p>
<p>
In both cases the smallest unit of storage and access is a Page. Every piece of data SQL Server needs to store on disk is organized in blocks called pages. Each page is exactly 8192 Bytes in size. The only exception to this rule are the log files. Log data is stored in a structure called virtual log file and those are not part of today's discussion.
</p>
<p>
Indexes created on a Heap use a physical address to point from the index record to the underlying data record. This address consists of the file number, the number of the page that contains the record within that file and the slot number inside that page.
</p>
<h3>Forwarded Records</h3>
<p>
When a record with variable length data type columns is updated, its size might increase. If the page it is stored in is filled already, the updated record might need to be moved to a new page. If that happens, all pointers in any index on that table addressing the moved row now point to the wrong (old) place. There are two ways to handle this situation: Update all indexes with the new location or put a special record in the old place that "forwards" to the new location.
</p>
<p>
Going through all existing indexes can be extensively resource intensive. For that reason SQL Server is using the second option to put a forwarding record in the old place.
</p>
<p>
Such a forwarding record links to the new physical address of the actual data. The new record also contains a pointer back to the forwarding record. This back-pointer allows the forwarding record to get updated in case the data record has to move again or in case it gets deleted which causes the forwarding record to be deleted as well. Updating the forwarding records in cases of repeated data record moves prevents long forwarding chains. Instead there is always only at max on hop.
</p>
<p>
In the case of an RID-Lookup SQL Server has to follow those forwarding records to get to the actual data record. This causes an additional (logical) page read for each forwarding record encountered.
</p>
<p>
During a table scan on the other hand each page will be read anyway so SQL Server could just ignore all forwarding records. That however could cause reading inconsistencies. Rows that are updated after the scan started and before it finishes could be missed entirely by the scan, if the record gets moved to a page that was scanned already. For that reason SQL Server follows each forwarding record immediately. This not only causes an additional read right there, is also turns the sequential read of all pages in allocation order into a succession of random reads, hurting performance potentially even more.
</p>
<h3>Demonstration</h3>
<p>
To demonstrate this behavior let us set up two tables dbo.ForwardSmall and dbo.NoForwardSmall as well as a Procedure dbo.ChangeRow to help with the creation of forwarded records:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
IF OBJECT_ID('dbo.ForwardSmall') IS NOT NULL DROP TABLE dbo.ForwardSmall;
CREATE TABLE dbo.ForwardSmall(Id INT ,F VARCHAR(8000))
GO
IF OBJECT_ID('dbo.NoForwardSmall') IS NOT NULL DROP TABLE dbo.NoForwardSmall;
CREATE TABLE dbo.NoForwardSmall(Id INT ,F VARCHAR(8000))
GO

IF OBJECT_ID('dbo.ChangeRow') IS NOT NULL DROP PROCEDURE dbo.ChangeRow;
GO
CREATE PROCEDURE dbo.ChangeRow
@Id INT,
@Size INT
AS
BEGIN
  SET NOCOUNT ON;
  MERGE dbo.ForwardSmall fs
  USING (SELECT @Id,REPLICATE('X',@Size)) x(Id,F)
  ON fs.Id = x.Id
  WHEN MATCHED THEN
    UPDATE SET fs.F = x.F
  WHEN NOT MATCHED THEN
    INSERT (Id,F)VALUES(x.Id,x.F);
END
GO
</pre>
</div>
<p>
The procedure will take an Id parameter as well as a size parameter. It uses to merge command to either insert or update the record with the given Id value. The F column will be valued with a string of length @Size.
</p>
<p>
The following script will create 16 rows in the table dbo.ForwardSmall and then update those records to create forwarded records.
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
EXEC dbo.ChangeRow 11,900;
EXEC dbo.ChangeRow 12,900;
EXEC dbo.ChangeRow 13,900;
EXEC dbo.ChangeRow 14,900;
EXEC dbo.ChangeRow 15,900;
EXEC dbo.ChangeRow 16,900;
EXEC dbo.ChangeRow 17,900;
EXEC dbo.ChangeRow 18,900;

EXEC dbo.ChangeRow 21,900;
EXEC dbo.ChangeRow 22,900;
EXEC dbo.ChangeRow 23,900;
EXEC dbo.ChangeRow 24,900;
EXEC dbo.ChangeRow 25,900;
EXEC dbo.ChangeRow 26,900;
EXEC dbo.ChangeRow 27,900;
EXEC dbo.ChangeRow 28,900;
--------------------------
EXEC dbo.ChangeRow 27,0;
EXEC dbo.ChangeRow 28,0;
EXEC dbo.ChangeRow 18,1800;
EXEC dbo.ChangeRow 28,900;
EXEC dbo.ChangeRow 18,900;

EXEC dbo.ChangeRow 26,0;
EXEC dbo.ChangeRow 17,1800;
EXEC dbo.ChangeRow 27,900;
EXEC dbo.ChangeRow 17,900;

EXEC dbo.ChangeRow 25,0;
EXEC dbo.ChangeRow 16,1800;
EXEC dbo.ChangeRow 26,900;
EXEC dbo.ChangeRow 16,900;

EXEC dbo.ChangeRow 24,0;
EXEC dbo.ChangeRow 15,1800;
EXEC dbo.ChangeRow 25,900;
EXEC dbo.ChangeRow 15,900;


EXEC dbo.ChangeRow 23,0;
EXEC dbo.ChangeRow 14,1800;
EXEC dbo.ChangeRow 24,900;
EXEC dbo.ChangeRow 14,900;

EXEC dbo.ChangeRow 22,0;
EXEC dbo.ChangeRow 13,1800;
EXEC dbo.ChangeRow 23,900;
EXEC dbo.ChangeRow 13,900;

EXEC dbo.ChangeRow 21,0;
EXEC dbo.ChangeRow 12,1800;
EXEC dbo.ChangeRow 22,1499;
EXEC dbo.ChangeRow 12,780;

EXEC dbo.ChangeRow 11,910;

EXEC dbo.ChangeRow 22,900;
EXEC dbo.ChangeRow 21,910;
</pre>
</div>
<p>
Each record starts out with ~950 bytes, so the 16 records will fit into two pages. From there the records are updated in a way so that each of them ends up on the other page with a forwarding record in its old place.
</p>
<p>
Now lets put the exact same data into the dbo.NoForwardSmall table:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
INSERT INTO dbo.NoForwardSmall
SELECT * FROM dbo.ForwardSmall;
</pre>
</div>
<p>
This again requires two pages, but this time no forwarded records are created.
</p>
<p>
To confirm, we can use the following query using the sys.dm_db_index_physical_stats DMF:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
SELECT OBJECT_SCHEMA_NAME(object_id)+'.'+OBJECT_NAME(object_id) Tbl,index_type_desc,alloc_unit_type_desc,page_count,record_count,forwarded_record_count
FROM sys.dm_db_index_physical_stats(DB_ID(),NULL,NULL,NULL,'detailed') ps
WHERE object_id IN (OBJECT_ID('dbo.ForwardSmall'),OBJECT_ID('dbo.NoForwardSmall'));
</pre>
</div>
<p>
This query returns the number of pages, the total number of records and the number of forwarded records for each table:
</p>
<div>
<style type="text/css">
table.tableizer-table {border: 1px solid #CCC; font-family: Arial, Helvetica, sans-serif; font-size: 12px;} .tableizer-table td {padding: 4px; margin: 3px; border: 1px solid #ccc;}
.tableizer-table th {background-color: #104E8B; color: #FFF; font-weight: bold;}
</style>
<table class="tableizer-table">
<tr class="tableizer-firstrow"><th>Tbl</th><th>index_type_desc</th><th>alloc_unit_type_desc</th><th>page_count</th><th>record_count</th><th>forwarded_record_count</th></tr> <tr><td>dbo.ForwardSmall</td><td>HEAP</td><td>IN_ROW_DATA</td><td>2</td><td>32</td><td>16</td></tr> <tr><td>dbo.NoForwardSmall</td><td>HEAP</td><td>IN_ROW_DATA</td><td>2</td><td>16</td><td>0</td></tr></table>
</div>
<p>
Now let us look at what happens during a table scan:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
SET STATISTICS IO ON;
SELECT * FROM dbo.NoForwardSmall;
GO
SELECT * FROM dbo.ForwardSmall;
SET STATISTICS IO OFF;
</pre>
</div>
<p>
This produces the following output:
</p>
<div>
<pre>
(16 row(s) affected)
Table 'NoForwardSmall'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(16 row(s) affected)
Table 'ForwardSmall'. Scan count 1, logical reads 18, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
</pre>
</div>
<p>
Scanning the dbo.NoForwardSmall table caused 2 logical reads - one for each page of the table. Scanning the dbo.ForwardSmall table however caused 18 logical reads. That is almost ten times more. Each of the 16 records caused an additional page read.
</p>
<h3>Conclusion</h3>
<p>
This article showed that SQL Server follows each forwarding record it encounters right away. This not only happens in the case of an RID-Lookup but also in the case of a table scan. These additional reads can present a significant performance impact. If you have a decently sized table with variable length columns and with regular update activity, add this behavior to the list of reasons why the table really should have a clustered index. 
</p>
</div><img src="http://feeds.feedburner.com/~r/sqlitynet/~4/m7p0XluTc3M" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://sqlity.net/en/609/the-performance-impact-of-forwarded-records-on-table-scans/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://sqlity.net/en/609/the-performance-impact-of-forwarded-records-on-table-scans/</feedburner:origLink></item>
		<item>
		<title>2012-02-08: Index Internals – February 2012 Philadelphia SQL Server Users Group Meeting</title>
		<link>http://feedproxy.google.com/~r/sqlitynet/~3/pHUF7pP8ZEg/</link>
		<comments>http://sqlity.net/en/605/2012-02-08-index-internals-february-2012-philadelphia-sql-server-users-group-meeting/#comments</comments>
		<pubDate>Sun, 29 Jan 2012 01:37:19 +0000</pubDate>
		<dc:creator>Sebastian</dc:creator>
				<category><![CDATA[Events]]></category>

		<guid isPermaLink="false">http://sqlity.net/en/?p=605</guid>
		<description><![CDATA[On 2/8 I am going repeat my talk about Index Internals at the Philadelphia SQL Server Users Group Meeting. Signup here to attend: http://pssug20120208.eventbrite.com/ TitleIndex Internals - How SQL Server stores and accesses data When02/08/2012 at 17:30 Where Philadelphia SQL Server Users Group Meeting DeVry University Rm 121 1140 Virginia Dr Fort Washington, PA]]></description>
			<content:encoded><![CDATA[<p>
On 2/8 I am going repeat my talk about Index Internals at the <a href="http://www.pssug.org/">Philadelphia SQL Server Users Group</a> Meeting. <br />Signup here to attend: <a href="http://pssug20120208.eventbrite.com/">http://pssug20120208.eventbrite.com/</a>
</p>
<style type="text/css"><!-- .WhenAndWhere td {padding:5px; border:1px solid #000;border-radius:3px;} --></style>
<table  class="WhenAndWhere">
<tr>
<td>Title</td><td><a href="http://sqlity.net/en/592/presentation-materials-index-internals/">Index Internals - How SQL Server stores and accesses data</a></td>
</tr>
<tr>
<td>When</td><td>02/08/2012 at 17:30</td>
</tr><tr>
<td>Where</td><td>
<div>
<a href="http://pssug20120208.eventbrite.com/">Philadelphia SQL Server Users Group Meeting</a>
<br />DeVry University
<br />Rm 121
<br />1140 Virginia Dr
<br />Fort Washington, PA
</div>
</td>
</tr>
</table><img src="http://feeds.feedburner.com/~r/sqlitynet/~4/pHUF7pP8ZEg" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://sqlity.net/en/605/2012-02-08-index-internals-february-2012-philadelphia-sql-server-users-group-meeting/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://sqlity.net/en/605/2012-02-08-index-internals-february-2012-philadelphia-sql-server-users-group-meeting/</feedburner:origLink></item>
		<item>
		<title>How to rollback in procedures</title>
		<link>http://feedproxy.google.com/~r/sqlitynet/~3/b9olR8YHJBY/</link>
		<comments>http://sqlity.net/en/585/how-to-rollback-in-procedures/#comments</comments>
		<pubDate>Mon, 23 Jan 2012 15:00:39 +0000</pubDate>
		<dc:creator>Sebastian</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[Best Practice]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Transactions]]></category>

		<guid isPermaLink="false">http://sqlity.net/en/?p=585</guid>
		<description><![CDATA[Transaction Management The question where in the call stack a transaction should be managed, is similar to the one where errors should be handled. There are several strong reasons to push the transaction management as high up the stack as possible. The most obvious reason is the lack of support for nested transactions in SQL <a href="http://sqlity.net/en/585/how-to-rollback-in-procedures/#more-585'" class="more-link">more »</a>]]></description>
			<content:encoded><![CDATA[<div>
<h3>Transaction Management</h3>
<p>
The question where in the call stack a transaction should be managed, is similar to the one where errors should be handled. There are several strong reasons to push the transaction management as high up the stack as possible. The most obvious reason is the lack of support for nested transactions in SQL Server.
</p>
<p>
SQL Server does allow to nest BEGIN TRANSACTION ... COMMIT blocks within each other. However, only the outermost layer actually opens and closes a transaction. In the inner layers SQL Server just maintains a counter; only the outermost COMMIT has any effect on the data.
</p>
<p>
With that structure in place, SQL Server has no way to do a partial rollback, so if a rollback is requested, everything is rolled back to the beginning of the outer most transaction.
</p>
<p>
If you have code, that does some data changes followed by a procedure call followed by other data changes, all within a transaction, and the procedure decides to execute a rollback, the rest of the data changes after the procedure call will execute outside of any transaction. Among other catastrophies this can cause inconsistencies in your data and it will prevent another rollback later.
</p>
<p>
SQL Server realizes this to be a mayor issue. It raises an error anytime the transaction count before and after the execution of a procedure do not match. You can try this out with this code snippet:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
IF OBJECT_ID('dbo.DoRollback') IS NOT NULL DROP PROCEDURE dbo.DoRollback;
GO
CREATE PROCEDURE dbo.DoRollback
AS
BEGIN
  BEGIN TRAN
  ROLLBACK;
  RETURN 0;
END;
GO

RAISERROR('Calling dbo.DoRollback outside of transaction',0,1)WITH NOWAIT;
EXEC dbo.DoRollback; -- No Error

GO

RAISERROR('Calling dbo.DoRollback inside of transaction',0,1)WITH NOWAIT;
BEGIN TRAN;
EXEC dbo.DoRollback; -- Error
</pre>
</div>
<p>
Executing the procedure after starting a transaction causes an error like this:
</p>
<pre>Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.</pre>
<p>
That error however does not prevent execution of additional code. The following example demonstrates that by calling the above procedure within another procedure:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
IF OBJECT_ID('dbo.CallDoRollback') IS NOT NULL DROP PROCEDURE dbo.CallDoRollback;
GO
CREATE PROCEDURE dbo.CallDoRollback
AS
BEGIN
  BEGIN TRAN;
  EXEC dbo.DoRollback;
  RAISERROR('--Inside dbo.CallDoRollback after calling dbo.DoRollback',0,1)WITH NOWAIT;
  IF(@@TRANCOUNT&gt;0)ROLLBACK;
  RETURN 0;
END;
GO

RAISERROR('Calling dbo.CallDoRollback',0,1)WITH NOWAIT;
EXEC dbo.CallDoRollback;
</pre>
</div>
<p>
So the conclusion here is, that a stored procedure should never execute a rollback. Instead it should raise an error to signal to the outer layer that a situation occurred that requires some mediation like a rollback.
</p>
<h3>Partial Rollbacks</h3>
<p>
Every once in while you need to code a procedure that needs to undo its own actions independent of the circumstances it was called in. That can be achieved with a partial rollback.
</p>
<p>
While SQL Server does not support nested transactions, it allows you to set a save-point within an open transaction. After setting a safe-point you can then rollback all changes that occurred after it. The DoCorrectRollback procedure demonstrates how you can use that functionality in your procedures.
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
IF OBJECT_ID('dbo.DoCorrectRollback') IS NOT NULL DROP PROCEDURE dbo.DoCorrectRollback;
GO
CREATE PROCEDURE dbo.DoCorrectRollback
AS
BEGIN
  DECLARE @ErrorStatus INT; SET @ErrorStatus = 0;
  DECLARE @TranName VARCHAR(32);SET @TranName = REPLACE((CAST(NEWID() AS VARCHAR(36))),'-','');

  BEGIN TRAN;
  RAISERROR('Setting savepoint: %s',0,1,@TranName)WITH NOWAIT;
  SAVE TRAN @TranName;
 
  BEGIN TRY
    EXEC dbo.DoError;
  END TRY
  BEGIN CATCH
    DECLARE @msg NVARCHAR(MAX);SET @msg = ERROR_MESSAGE();
    RAISERROR('Caught this error: %s',0,1,@msg)WITH NOWAIT;
    SET @ErrorStatus = -1
  END CATCH;

  IF(@ErrorStatus != 0)
  BEGIN
    RAISERROR('Rolling back to savepoint: %s',0,1,@TranName)WITH NOWAIT;
    ROLLBACK TRANSACTION @TranName;
  END;
  
  COMMIT;
  
  RETURN 0;
END;
GO

RAISERROR('Calling dbo.DoCorrectRollback outside of transaction',0,1)WITH NOWAIT;
EXEC dbo.DoCorrectRollback;

GO

RAISERROR('Calling dbo.DoCorrectRollback inside of transaction',0,1)WITH NOWAIT;
BEGIN TRAN
  EXEC dbo.DoCorrectRollback;
COMMIT;
GO
</pre>
</div>
<p>
The procedure first executes a BEGIN TRANSACTION to make sure that there is an active transaction. (Remember, this does not have any effect inside a preexisting transaction.) Next, the procedure sets a save-point. A save-point requires a unique name that can be up to 32 characters long. The procedure uses the NEWID() function to get this name. NEWID() returns 36 characters of which 4 are a '-', so by removing them we end up with the required 32 characters. 
</p>
<p>
After this transaction setup section the actual code is executed inside a TRY...CATCH block. If an error is thrown, the error handling code causes a ROLLBACK to be executed. The ROLLBACK TRANSACTION statement takes the save-point name as parameter, which causes the rollback to undo only actions that happened after the save-point was set - the beginning of the procedure in our case.
</p>
<p>
This ROLLBACK to the previously set save-point does not change the open transaction counter at all, so before the procedure returns it needs to execute a commit to bring the transaction counter back to the state it was in at the beginning of the procedure. This step has to be taken independently of the ROLLBACK being executed or not. It should therefore be the last statement of the procedure.
</p>
<p>
This allows the procedure to be called inside and outside of a transaction without causing adverse effects by executing a rollback.
</p>
<h3>Limitations</h3>
<p>
SQL Server allows to set several save-points within one active transaction as long as they have unique names. If a name is reused the existing save-point is moved to the current position and no error is thrown, so be sure to use unique names as shown in the example.
</p>
<p>
Another limitation is, that save-points cannot be used in distributed transactions spanning more than one SQL Server instance. If you try to set a save-point in a distributed transaction an error is thrown.
</p>
<p>
Lastly, the example given does not notify the outer layers about the existence of a problem. This should certainly be part of the design of your procedures. There are several ways to achieve this, for example by raising an error after the rollback step that contains the reason for the rollback.
</p>
</div><img src="http://feeds.feedburner.com/~r/sqlitynet/~4/b9olR8YHJBY" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://sqlity.net/en/585/how-to-rollback-in-procedures/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://sqlity.net/en/585/how-to-rollback-in-procedures/</feedburner:origLink></item>
		<item>
		<title>2012-01-24: Index Internals – January 2012 SJ SQL Server Meeting</title>
		<link>http://feedproxy.google.com/~r/sqlitynet/~3/tqlsNpIlj50/</link>
		<comments>http://sqlity.net/en/594/2012-01-24-index-internals-january-2012-sj-sql-server-meeting/#comments</comments>
		<pubDate>Sun, 22 Jan 2012 21:01:59 +0000</pubDate>
		<dc:creator>Sebastian</dc:creator>
				<category><![CDATA[Events]]></category>

		<guid isPermaLink="false">http://sqlity.net/en/?p=594</guid>
		<description><![CDATA[On 1/24 I am going to talk about Index Internals at the SJ SQL Server Meeting. Signup here to attend: http://sjpssug20120124.eventbrite.com/ TitleIndex Internals - How SQL Server stores and accesses data When01/24/2012 at 17:30 Where SJ SQL Server Meeting Haddon Heights Public Library 608 Station Ave Haddon Heights, NJ 08035]]></description>
			<content:encoded><![CDATA[<p>
On 1/24 I am going to talk about Index Internals at the SJ SQL Server Meeting. <br />Signup here to attend: <a href="http://sjpssug20120124.eventbrite.com/">http://sjpssug20120124.eventbrite.com/</a>
</p>
<style type="text/css"><!-- .WhenAndWhere td {padding:5px; border:1px solid #000;border-radius:3px;} --></style>
<table  class="WhenAndWhere">
<tr>
<td>Title</td><td><a href="http://sqlity.net/en/592/presentation-materials-index-internals/">Index Internals - How SQL Server stores and accesses data</a></td>
</tr>
<tr>
<td>When</td><td>01/24/2012 at 17:30</td>
</tr><tr>
<td>Where</td><td>
<div>
<a href="http://sjpssug20120124.eventbrite.com/">SJ SQL Server Meeting</a>
<br />Haddon Heights Public Library
<br />608 Station Ave
<br />Haddon Heights, NJ 08035
</div>
</td>
</tr>
</table><img src="http://feeds.feedburner.com/~r/sqlitynet/~4/tqlsNpIlj50" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://sqlity.net/en/594/2012-01-24-index-internals-january-2012-sj-sql-server-meeting/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://sqlity.net/en/594/2012-01-24-index-internals-january-2012-sj-sql-server-meeting/</feedburner:origLink></item>
		<item>
		<title>Presentation Materials – Index Internals</title>
		<link>http://feedproxy.google.com/~r/sqlitynet/~3/VFAT6Xe7GFM/</link>
		<comments>http://sqlity.net/en/592/presentation-materials-index-internals/#comments</comments>
		<pubDate>Sun, 22 Jan 2012 20:46:16 +0000</pubDate>
		<dc:creator>Sebastian</dc:creator>
				<category><![CDATA[Presentations]]></category>

		<guid isPermaLink="false">http://sqlity.net/en/?p=592</guid>
		<description><![CDATA[Abstract In this session we will examine one of SQL Servers' most fundamental yet most important functions - how does SQL Server store data on disk and how does it find it again when executing a query. Using a combination of demos, lecture and discussions we will begin with the structure of the data on <a href="http://sqlity.net/en/592/presentation-materials-index-internals/#more-592'" class="more-link">more »</a>]]></description>
			<content:encoded><![CDATA[<h3>Abstract</h3>
<p>
In this session we will examine one of SQL Servers' most fundamental yet most important functions - how does SQL Server store data on disk and how does it find it again when executing a query. Using a combination of demos, lecture and discussions we will begin with the structure of the data on disk by delving into the world of HoBTs and Extends, RIDs and Keys and especially Pages. From there we will tackle topics concerning the retrieval of data such as Index levels, Index Scans and Seeks and Lookups.
</p>

<h3>Downloads</h3>
<ul>
 <li><a href="http://downloads.sqlity.net/IndexInternalsHandout.pdf">Slides</a></li>
 <li><a href="http://downloads.sqlity.net/TableInfo_IndexInfo.sql">TableInfo &amp; IndexInfo</a></li>
</ul>

<h3>Other Resources</h3>
<ul>
 <li> <a href="http://www.sqlsentry.com/plan-explorer/sql-server-query-view.asp" target="_blank">SQL Sentry Plan Explorer</a></li>
 <li><a target="_blank" href="http://www.amazon.com/gp/product/0735626243/ref=as_li_ss_tl?ie=UTF8&amp;tag=sqlitynet-20&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=0735626243" id="static_txt_preview">Microsoft® SQL Server® 2008 Internals (Pro - Developer)</a></li>
</ul><img src="http://feeds.feedburner.com/~r/sqlitynet/~4/VFAT6Xe7GFM" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://sqlity.net/en/592/presentation-materials-index-internals/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://sqlity.net/en/592/presentation-materials-index-internals/</feedburner:origLink></item>
		<item>
		<title>Selecting the entire Database as XML String</title>
		<link>http://feedproxy.google.com/~r/sqlitynet/~3/w5DKJ5BZg5Q/</link>
		<comments>http://sqlity.net/en/577/selecting-the-entire-database-as-xml-string/#comments</comments>
		<pubDate>Fri, 13 Jan 2012 17:09:15 +0000</pubDate>
		<dc:creator>Sebastian</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[XML]]></category>

		<guid isPermaLink="false">http://sqlity.net/en/?p=577</guid>
		<description><![CDATA[I just ran into this question on StackOverflow: Dump data into single XML file from MS SQL Server 2008 R2. In there Daniel is looking for a solution to export his database as an XML file. I thought that request was an excellent way to practice my XML skills so I spend a little time <a href="http://sqlity.net/en/577/selecting-the-entire-database-as-xml-string/#more-577'" class="more-link">more »</a>]]></description>
			<content:encoded><![CDATA[<p>
I just ran into this question on StackOverflow: <a href="http://stackoverflow.com/questions/8851638/dump-data-into-single-xml-file-from-ms-sql-server-2008-r2">Dump data into single XML file from MS SQL Server 2008 R2</a>.
In there Daniel is looking for a solution to export his database as an XML file.
</p>
<p>
I thought that request was an excellent way to practice my XML skills so I spend a little time to come up with a solution.
</p>
<p>
For testing purposes I first created a few tables in an empty database:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">

DECLARE @cmd NVARCHAR(MAX) ;
SET @cmd = ( SELECT 'CREATE TABLE T' + CAST(n AS NVARCHAR(MAX))
                    + '(id INT IDENTITY(1,1)'
                    + (SELECT   ',c' + CAST(c.n AS NVARCHAR(MAX))
                                + ' INT DEFAULT ' + CAST(c.n AS NVARCHAR(MAX))
                                + '*' + CAST(t.n AS NVARCHAR(MAX))
                       FROM     dbo.GetNums(( t.n - 1 ) % 10 + 1) c
             FOR    XML PATH('') ,
                        TYPE).value('.', 'NVARCHAR(MAX)') + ');INSERT INTO T'
                    + CAST(n AS NVARCHAR(MAX)) + ' DEFAULT VALUES;'
             FROM   dbo.GetNums (3000) t
FOR XML PATH('') ,
        TYPE).value('.', 'NVARCHAR(MAX)') ;

EXEC(@cmd) ;
</pre>
</div>
<p>
This snippet uses <a href="http://www.sqlmag.com/article/sql-server/virtual-auxiliary-table-of-numbers">Itzik's GetNums</a> function to generate 3000 CREATE TABLE and INSERT statements.
</p>
<p>
With the table in place I was able to come up with this solution to the original question:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
DECLARE @cmd NVARCHAR(MAX) ;
SET @cmd = 'SELECT * FROM('
    + STUFF(
(SELECT ' UNION ALL SELECT ''' + QUOTENAME(OBJECT_SCHEMA_NAME(object_id))
        + '.' + QUOTENAME(name) + ''' [@name],' + '(SELECT * FROM '
        + QUOTENAME(OBJECT_SCHEMA_NAME(object_id)) + '.' + QUOTENAME(name)
        + ' FOR XML PATH(''row''),TYPE) data'
 FROM   sys.tables
    FOR     XML PATH('') ,
                TYPE).value('.', 'NVARCHAR(MAX)'), 1, 11, '')
    + ')X FOR XML PATH(''table''),TYPE;' ;

EXEC(@cmd) ;
</pre>
</div>
<p>
The script builds a single statement. For each table it selects the table name in a column with the name [@name] and the content of that table as XML (using a sub-select) in a column with the name data. All these SELECTs are "UNION ALL"ed together and formated into the final XML document with an additional outer SELECT FOR XML.
</p>
<p>
If you are only after some of the tables, you can add a where clause in row 8 of the above snippet.
</p>
<p>
The resulting XML has this format:
</p>
<div>
<pre class="brush: xml; title: ; notranslate">
&lt;table name=&quot;[dbo].[T1628]&quot;&gt;
  &lt;data&gt;
    &lt;row&gt;
      &lt;id&gt;1&lt;/id&gt;
      &lt;c1&gt;1628&lt;/c1&gt;
      &lt;c2&gt;3256&lt;/c2&gt;
      &lt;c3&gt;4884&lt;/c3&gt;
      &lt;c4&gt;6512&lt;/c4&gt;
      &lt;c5&gt;8140&lt;/c5&gt;
      &lt;c6&gt;9768&lt;/c6&gt;
      &lt;c7&gt;11396&lt;/c7&gt;
      &lt;c8&gt;13024&lt;/c8&gt;
    &lt;/row&gt;
  &lt;/data&gt;
&lt;/table&gt;
&lt;table name=&quot;[dbo].[T2102]&quot;&gt;
  &lt;data&gt;
    &lt;row&gt;
      &lt;id&gt;1&lt;/id&gt;
      &lt;c1&gt;2102&lt;/c1&gt;
      &lt;c2&gt;4204&lt;/c2&gt;
    &lt;/row&gt;
  &lt;/data&gt;
&lt;/table&gt;
</pre>
</div>
<p>
For each table there is a &lt;table&gt; tag that has a name attribut with the table name. It contains a &lt;data&gt; tag that in turn contains one &lt;row&gt; tag for every row in the table.
</p>
<p>
The only thing missing to make this valid XML is a root tag, but that should be easy to add.<img src="http://feeds.feedburner.com/~r/sqlitynet/~4/w5DKJ5BZg5Q" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://sqlity.net/en/577/selecting-the-entire-database-as-xml-string/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		<feedburner:origLink>http://sqlity.net/en/577/selecting-the-entire-database-as-xml-string/</feedburner:origLink></item>
		<item>
		<title>Index Misconceptions [TSQL Tuesday #026 - Second Chances]</title>
		<link>http://feedproxy.google.com/~r/sqlitynet/~3/IvkWoyEJvto/</link>
		<comments>http://sqlity.net/en/563/index-misconceptions-tsql-tuesday-026-second-chances/#comments</comments>
		<pubDate>Tue, 10 Jan 2012 05:00:11 +0000</pubDate>
		<dc:creator>Sebastian</dc:creator>
				<category><![CDATA[T-SQL Tuesday]]></category>

		<guid isPermaLink="false">http://sqlity.net/en/?p=563</guid>
		<description><![CDATA[T-SQL Tuesday #26 is hosted by David Howard (blog&#124;twitter). This month’s topic is “Second Chances”. Index Misconceptions The topic this month is "Second Chances" which means that we can write about anything, we would have liked to write about before but didn't get to. Well, not really anything, but any of the 25 previos T-SQL <a href="http://sqlity.net/en/563/index-misconceptions-tsql-tuesday-026-second-chances/#more-563'" class="more-link">more »</a>]]></description>
			<content:encoded><![CDATA[<style type="text/css">
<!--
p {margin-top:8px;}
-->
</style>
<div>
<p><a href="http://davidbrycehoward.com/archive/2012/01/tsql-tuesday-026-second-chances/"><img height="132" border="0" hspace="9" width="131" alt="SqlTuesday Index Misconceptions [TSQL Tuesday #026   Second Chances]" src="http://images.sqlity.net/SqlTuesday.png" title="Index Misconceptions [TSQL Tuesday #026   Second Chances]" /></a></p>
<p>T-SQL Tuesday #26 is hosted by David Howard (<a href="http://davidbrycehoward.com/">blog</a>|<a href="https://twitter.com/#!/DaveH0ward">twitter</a>). This month’s topic is “<a href="http://davidbrycehoward.com/archive/2012/01/tsql-tuesday-026-second-chances/">Second Chances</a>”.</p>
<h2>Index Misconceptions</h2>
<p>
The topic this month is "Second Chances" which means that we can write about anything, we would have liked to write about before but didn't get to. Well, not really anything, but any of the 25 previos T-SQL Tuesday topics. I picked two topics: <a href="http://michaeljswart.com/2010/09/invitation-to-participate-in-t-sql-tuesday-10-indexes/">Indexes</a> by <a href="http://michaeljswart.com/">Michael Swart</a> and <a href="http://sankarreddy.com/2010/10/invitation-to-participate-in-t-sql-tuesday-11-misconceptions-in-sql-server/">Misconceptions in SQL Server</a> by <a href="http://sankarreddy.com/">Sankar Reddy</a>.
</p>
<h3>Misconception: SQL Server Indexes are binary trees</h3>
<p>I keep running into articles that claim that SQL Server internally uses a binary tree to build it's indexes. That however is incorrect and I would like to use this opportunity to clear things up a little.
</p>
<p>
SQL Server stores its tables (to be exact: table partitions) in a format that is called a HoBT. HoBT stands for "Heap or B-Tree". A Heap is used for tables without a clustered index. A B-Tree is used for all clustered and nonclustered indexes. If you lock up the word <a href="http://en.wikipedia.org/wiki/B-tree">B-Tree on Wikipedia</a>, the first sentence states: "Not to be confused with <a href="http://en.wikipedia.org/wiki/Binary_tree" title="Binary tree">Binary tree</a>."
</p>
<p>
The B in B-Tree is often said to stand fo "Balanced". However, when Rudolf Bayer and Ed McCreight at Boeing invented the B-Tree in 1971, they did not specify the meaning of the B at all.
Speculations include Balanced, Bushy, Bayer and Boeing (<a href="http://en.wikipedia.org/wiki/B-tree#Etymology_unknown">see again Wikipedia</a>).
</p>
<p>
SQL Server is not using a plain B-Tree but instead a variation called a <a href="http://en.wikipedia.org/wiki/B%2B_tree">B+Tree</a>. The main difference to a B-Tree is, that in a B+Tree the actual data is only stored in the leaf nodes with all other nodes containing only key values. In B-Trees the data is distributed over all levels.
</p>
<p>
So, let's take a look under the covers to see the structure for ourselves. First lets create a table to play with:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
CREATE TABLE dbo.IdxTst1
    (
      Id INT IDENTITY(1, 1) ,
      V1 CHAR(795) ,
      F1 CHAR(7254) ,
      CONSTRAINT PK_IdxTst1 PRIMARY KEY CLUSTERED( Id, V1 )
    ) ;
</pre>
</div>
<p> The table has a 4 byte (INT) identity column and two fixed length CHAR columns. The Clustered Index key contains the Id and the 795 byte, a total of 799 bytes. The full row contains an additional 7254 bytes for a total row data size of 8053 bytes. Each row when stored contains some meta information and for this table this additional information is 7 bytes per row, which brings us to a total of 8060 bytes for each row. This is the maximum number of bytes that one row is allowed to take (we are excluding LOB data from this exercise). This row size will make sure that there is always only one row per page. 
</p>
<p>
  To look at the structure of the table we are going to use the <a href="http://msdn.microsoft.com/en-us/library/ms188917.aspx">sys.dm_db_index_physical_stats</a> dmf:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
SELECT index_type_desc,Alloc_unit_type_desc,index_depth,index_level,page_count,record_count,avg_record_size_in_bytes 
  FROM sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('dbo.IdxTst1'),NULL,NULL,'DETAILED');
</pre>
</div>
<p>
This query returns one row for every level of every B+Tree for the dbo.IdxTst1 table. As the table is not partitioned and contains only one index (the clustered index), the query in our case returns only information about this one index.
</p>
<p>
After inserting one row into the table the query returns the following result set:
</p>
<table>
  <thead>
    <tr>
      <th class="index_type_desc-cell">index_type_desc</th>
      <th class="Alloc_unit_type_desc-cell">Alloc_unit_type_desc</th>
      <th class="index_depth-cell">index_depth</th>
      <th class="index_level-cell">index_level</th>
      <th class="page_count-cell">page_count</th>
      <th class="record_count-cell">record_count</th>
      <th class="avg_record_size_in_bytes-cell">avg_record_size_in_bytes</th>
    </tr>
  </thead>
  <tbody>
    <tr class="lastRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">1</td>
      <td class="index_level-cell">0</td>
      <td class="page_count-cell">1</td>
      <td class="record_count-cell">1</td>
      <td class="avg_record_size_in_bytes-cell">8060</td>
    </tr>
  </tbody>
</table>
<p>
The B+Tree has so far only one level (index_level = 0), contains 1 node (page_count = 1) and 1 record within that node. The record has the expected size of 8060 bytes.
</p>
<p>
To insert a second row SQL Server needs to create a new page (node), as each page can hold only one row. With now two data pages we also need a new root page that links to the data pages:
</p>
<table>
  <thead>
    <tr>
      <th class="index_type_desc-cell">index_type_desc</th>
      <th class="Alloc_unit_type_desc-cell">Alloc_unit_type_desc</th>
      <th class="index_depth-cell">index_depth</th>
      <th class="index_level-cell">index_level</th>
      <th class="page_count-cell">page_count</th>
      <th class="record_count-cell">record_count</th>
      <th class="avg_record_size_in_bytes-cell">avg_record_size_in_bytes</th>
    </tr>
  </thead>
  <tbody>
    <tr class="firstRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">2</td>
      <td class="index_level-cell">0</td>
      <td class="page_count-cell">2</td>
      <td class="record_count-cell">2</td>
      <td class="avg_record_size_in_bytes-cell">8060</td>
    </tr>
    <tr class="lastRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">2</td>
      <td class="index_level-cell">1</td>
      <td class="page_count-cell">1</td>
      <td class="record_count-cell">2</td>
      <td class="avg_record_size_in_bytes-cell">806</td>
    </tr>
  </tbody>
</table>
<p>
As expected, the B+Tree has now two levels: Index_level 0 with 2 pages holding one row of 8060 bytes each, and index_level 1 for the root page which holds two records of 806 bytes each &ndash; one for each level 0 page. The 806 bytes contain the 799 byte key value plus the pointer to the data page for this key value.
</p>
<p>
With a key storage size of 806 a page can hold 10 different values, so lets insert an other 8 rows to check:
</p>
<table>
  <thead>
    <tr>
      <th class="index_type_desc-cell">index_type_desc</th>
      <th class="Alloc_unit_type_desc-cell">Alloc_unit_type_desc</th>
      <th class="index_depth-cell">index_depth</th>
      <th class="index_level-cell">index_level</th>
      <th class="page_count-cell">page_count</th>
      <th class="record_count-cell">record_count</th>
      <th class="avg_record_size_in_bytes-cell">avg_record_size_in_bytes</th>
    </tr>
  </thead>
  <tbody>
    <tr class="firstRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">2</td>
      <td class="index_level-cell">0</td>
      <td class="page_count-cell">10</td>
      <td class="record_count-cell">10</td>
      <td class="avg_record_size_in_bytes-cell">8060</td>
    </tr>
    <tr class="lastRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">2</td>
      <td class="index_level-cell">1</td>
      <td class="page_count-cell">1</td>
      <td class="record_count-cell">10</td>
      <td class="avg_record_size_in_bytes-cell">806</td>
    </tr>
  </tbody>
</table>
<p>
And one more to force an additional page on index_level 1:
</p>
<table>
  <thead>
    <tr>
      <th class="index_type_desc-cell">index_type_desc</th>
      <th class="Alloc_unit_type_desc-cell">Alloc_unit_type_desc</th>
      <th class="index_depth-cell">index_depth</th>
      <th class="index_level-cell">index_level</th>
      <th class="page_count-cell">page_count</th>
      <th class="record_count-cell">record_count</th>
      <th class="avg_record_size_in_bytes-cell">avg_record_size_in_bytes</th>
    </tr>
  </thead>
  <tbody>
    <tr class="firstRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">3</td>
      <td class="index_level-cell">0</td>
      <td class="page_count-cell">11</td>
      <td class="record_count-cell">11</td>
      <td class="avg_record_size_in_bytes-cell">8060</td>
    </tr>
    <tr>
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">3</td>
      <td class="index_level-cell">1</td>
      <td class="page_count-cell">2</td>
      <td class="record_count-cell">11</td>
      <td class="avg_record_size_in_bytes-cell">806</td>
    </tr>
    <tr class="lastRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">3</td>
      <td class="index_level-cell">2</td>
      <td class="page_count-cell">1</td>
      <td class="record_count-cell">2</td>
      <td class="avg_record_size_in_bytes-cell">806</td>
    </tr>
  </tbody>
</table>
<p>
As predicted, we now have two pages on index_level 1 and a new root page on index_level 2. All the data (11 rows) is still stored in index_level 0 in 11 separate pages. All intermediate (and root) pages contain only key values (806 bytes).
</p>
<p>
The root page can hold 10 entries and each index_level 1 page can also hold 10 entries. That should allow us to insert an additional 89 rows into this table without requiring a fourth index level. So let's try it:
</p>
<table>
  <thead>
    <tr>
      <th class="index_type_desc-cell">index_type_desc</th>
      <th class="Alloc_unit_type_desc-cell">Alloc_unit_type_desc</th>
      <th class="index_depth-cell">index_depth</th>
      <th class="index_level-cell">index_level</th>
      <th class="page_count-cell">page_count</th>
      <th class="record_count-cell">record_count</th>
      <th class="avg_record_size_in_bytes-cell">avg_record_size_in_bytes</th>
    </tr>
  </thead>
  <tbody>
    <tr class="firstRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">4</td>
      <td class="index_level-cell">0</td>
      <td class="page_count-cell">100</td>
      <td class="record_count-cell">100</td>
      <td class="avg_record_size_in_bytes-cell">8060</td>
    </tr>
    <tr>
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">4</td>
      <td class="index_level-cell">1</td>
      <td class="page_count-cell">24</td>
      <td class="record_count-cell">100</td>
      <td class="avg_record_size_in_bytes-cell">806</td>
    </tr>
    <tr>
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">4</td>
      <td class="index_level-cell">2</td>
      <td class="page_count-cell">5</td>
      <td class="record_count-cell">24</td>
      <td class="avg_record_size_in_bytes-cell">806</td>
    </tr>
    <tr class="lastRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">4</td>
      <td class="index_level-cell">3</td>
      <td class="page_count-cell">1</td>
      <td class="record_count-cell">5</td>
      <td class="avg_record_size_in_bytes-cell">806</td>
    </tr>
  </tbody>
</table>
<p>
What happened here? We have the expected 100 data rows and the corresponding 100 index_level 0 pages but instead of only 10 index_level 1 and one root page we have now 30 non leaf level pages spread over three levels.
</p>
<p>
The reason for this is that SQL Server, every time it requires a new page to insert a row, takes the page that should have contained the new row and splits its contents in half, leaving one half in the old page and moving the other half into the new page. After that it inserts the new row into the new page. This algorithm is used for all index pages, no matter of their index_level and independent of the value of the new row. (For leaf level pages SQL Server does not split the existing page if the position of the new row is the end of the table. It instead just adds a new empty page to the table and inserts the row in there. The example in this article hides this behavior as there is always only one row per data page.)
That algorithm leaves most of the intermediate index pages with only 5 rows, so after only 55 rows a new index level is required.
</p>
<p>
To reclaim that space lost space we need to rebuild the index:
</p>
<div>
<pre class="brush: sql; title: ; notranslate">
ALTER INDEX PK_IdxTst1 ON dbo.IdxTst1 REBUILD;
</pre>
</div>
<p>
Now the table structure looks as expected:
</p>
<table>
  <thead>
    <tr>
      <th class="index_type_desc-cell">index_type_desc</th>
      <th class="Alloc_unit_type_desc-cell">Alloc_unit_type_desc</th>
      <th class="index_depth-cell">index_depth</th>
      <th class="index_level-cell">index_level</th>
      <th class="page_count-cell">page_count</th>
      <th class="record_count-cell">record_count</th>
      <th class="avg_record_size_in_bytes-cell">avg_record_size_in_bytes</th>
    </tr>
  </thead>
  <tbody>
    <tr class="firstRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">3</td>
      <td class="index_level-cell">0</td>
      <td class="page_count-cell">100</td>
      <td class="record_count-cell">100</td>
      <td class="avg_record_size_in_bytes-cell">8060</td>
    </tr>
    <tr>
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">3</td>
      <td class="index_level-cell">1</td>
      <td class="page_count-cell">10</td>
      <td class="record_count-cell">100</td>
      <td class="avg_record_size_in_bytes-cell">806</td>
    </tr>
    <tr class="lastRow">
      <td class="index_type_desc-cell">CLUSTERED INDEX</td>
      <td class="Alloc_unit_type_desc-cell">IN_ROW_DATA</td>
      <td class="index_depth-cell">3</td>
      <td class="index_level-cell">2</td>
      <td class="page_count-cell">1</td>
      <td class="record_count-cell">10</td>
      <td class="avg_record_size_in_bytes-cell">806</td>
    </tr>
  </tbody>
</table>
<p>
The FILLFACTOR of the index does not matter in this case as non data pages in an index are always completely filled during an index rebuild operation (unless PAD_INDEX = ON is also specified).
</p>
<h3>Conclusion</h3>
<p>
We did see that SQL Server uses a physical tree implementation that allows for more than two child nodes per tree node. We also confirmed that SQL Server stores only the key values in the non-leaf nodes. That are the two main characteristics of a B+Tree. It also clearly rules out the binary search tree format.
</p>
<p>
We also ran into a situation where most of the non-leaf levels of the index where only half filled. This is one of the disadvantages that come with the use of B+Trees. Usually this is however not a big issue, as the number of non leaf pages is usually small compared to the total number of pages. You might however want to keep an eye out for this behavior, especially if you ar dealing with overly wide keys, as we did in this example.
</p>
<p>
If you rather store you data as compact as possible you can do so by executing an index rebuild.
</p>
<h3>Additional Information</h3>
<p>
For further information about index internals with some nice graphics check out Michael Swart's <a href="http://michaeljswart.com/2010/09/guts-of-an-clustered-index/">Guts Of An Clustered Index</a>, his contribution to his own SQL Saturday #10.
</p>
</div><img src="http://feeds.feedburner.com/~r/sqlitynet/~4/IvkWoyEJvto" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://sqlity.net/en/563/index-misconceptions-tsql-tuesday-026-second-chances/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://sqlity.net/en/563/index-misconceptions-tsql-tuesday-026-second-chances/</feedburner:origLink></item>
		<item>
		<title>T-SQL Tuesday #25 – SQL Server Tips &amp; Tricks</title>
		<link>http://feedproxy.google.com/~r/sqlitynet/~3/GCyDdP44MC0/</link>
		<comments>http://sqlity.net/en/556/t-sql-tuesday-25-%e2%80%93-sql-server-tips-tricks/#comments</comments>
		<pubDate>Tue, 13 Dec 2011 16:39:22 +0000</pubDate>
		<dc:creator>Sebastian</dc:creator>
				<category><![CDATA[T-SQL Tuesday]]></category>

		<guid isPermaLink="false">http://sqlity.net/en/?p=556</guid>
		<description><![CDATA[T-SQL Tuesday #25 is hosted by Allen White (blog). This month’s topic is “SQL Server Tips and Tricks”. Transaction Log Reuse Wait One question that keeps coming up in forums, at user group meetings and on twitter is: Why is my transaction log growing out of bounds? To adhere to the ACID properties, SQL Server <a href="http://sqlity.net/en/556/t-sql-tuesday-25-%e2%80%93-sql-server-tips-tricks/#more-556'" class="more-link">more »</a>]]></description>
			<content:encoded><![CDATA[<style type="text/css">
<!--
p {margin-top:8px;}
-->
</style>
<br />
<p><a href="http://sqlblog.com/blogs/allen_white/archive/2011/12/05/t-sql-tuesday-025-invitation-to-share-your-tricks.aspx"><img height="132" border="0" hspace="9" width="131" alt="SqlTuesday T SQL Tuesday #25 – SQL Server Tips & Tricks" src="http://images.sqlity.net/SqlTuesday.png" title="T SQL Tuesday #25 – SQL Server Tips & Tricks" /></a></p>
<p>T-SQL Tuesday #25 is hosted by Allen White (<a href="http://sqlblog.com/blogs/allen_white/default.aspx">blog</a>). This month’s topic is “<a href="http://sqlblog.com/blogs/allen_white/archive/2011/12/05/t-sql-tuesday-025-invitation-to-share-your-tricks.aspx">SQL Server Tips and Tricks</a>”.</p>
<h2>Transaction Log Reuse Wait</h2>
<p>
One question that keeps coming up in forums, at user group meetings and on twitter is: Why is my transaction log growing out of bounds?
</p>
<p>
To adhere to the <a href="http://en.wikipedia.org/wiki/ACID">ACID properties</a>, SQL Server records all changes to the database first into the transaction log file. A transaction can only be committed after that write succeeds. The changes to the data itself actually get applied only to the data pages in the buffer pool. If and when they get written to the disk is influenced by several factors that are independent of the transaction itself. The records in the transaction log allow SQL Server to redo a change after a crash for example that prevented the data page changes to be written to disk.
</p>
<p>
That means in theory, that after the transaction is committed and after the data pages are preserved on disk, there is no need for SQL Server to hold on to the transaction log data anymore. That's why the transaction log usually does not grow unbounded, because SQL Server can reuse the parts of the file(s) that are not needed anymore.
</p>
<p>
In practice however, there can be several reasons, why such a reuse is not possible. The most common one is the requirement for log backups. If you have your database set to recovery mode FULL, SQL Server does not reuse any part of the log file until it is backed up with a <a href="http://sqlserverpedia.com/wiki/Types_of_Backups">transaction log backup</a>. There are several other reasons why SQL Server might have to wait before it can reuse the transaction log, ranging from long running open transactions to transactional replication.
</p>
<p>
If you are in a situation that requires you to find out why the transaction log file keeps growing and growing, this simple query can give you the answer:
</p>
<pre class="brush: sql; title: ; notranslate">
SELECT name,log_reuse_wait_desc FROM sys.databases;
</pre> 
<p>
The log_reuse_wait_desc column contains the reason why the SQL Server currently can't reuse the log file of that database. Explanations for each of the values you can find in BOL under <a href="http://msdn.microsoft.com/en-us/library/ms345414.aspx">Factors That Can Delay Log Truncation</a>.
</p><img src="http://feeds.feedburner.com/~r/sqlitynet/~4/GCyDdP44MC0" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://sqlity.net/en/556/t-sql-tuesday-25-%e2%80%93-sql-server-tips-tricks/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://sqlity.net/en/556/t-sql-tuesday-25-%e2%80%93-sql-server-tips-tricks/</feedburner:origLink></item>
		<item>
		<title>SQL Test available</title>
		<link>http://feedproxy.google.com/~r/sqlitynet/~3/Mr72YOo2hVc/</link>
		<comments>http://sqlity.net/en/524/sql-test-available/#comments</comments>
		<pubDate>Wed, 07 Dec 2011 19:59:37 +0000</pubDate>
		<dc:creator>Sebastian</dc:creator>
				<category><![CDATA[tSQLt]]></category>

		<guid isPermaLink="false">http://sqlity.net/en/?p=524</guid>
		<description><![CDATA[Exciting news! Red Gate is now shipping SQL Test, a graphical UI for tSQLt. tSQLt is a Database Unit Testing Framework for SQL Server that was developed by Dennis and me. SQL Test integrates with SSMS and allows you to create, edit and run your tSQLt tests right where you do all your T-SQL development <a href="http://sqlity.net/en/524/sql-test-available/#more-524'" class="more-link">more »</a>]]></description>
			<content:encoded><![CDATA[<div>
<p>
Exciting news!
</p>
<p>
Red Gate is now shipping SQL Test, a graphical UI for tSQLt. 
</p>
<p>
tSQLt is a <a href="http://tSQLt.org"> Database Unit Testing Framework for SQL Server</a> that was developed by <a href="http://www.testdrivendatabases.com/">Dennis</a> and me. 
<br />
SQL Test integrates with SSMS and allows you to create, edit and run your tSQLt tests right where you do all your T-SQL development work anyway. That makes the whole workflow of testing a lot simpler and easier to follow:
</p>
<div style="border:1px solid #DDD;padding:5px;width:300px;margin:10px;">
<a href="http://www.red-gate.com/products/sql-development/sql-test/"><img src="http://sqlity.net/en/wp-content/uploads/2011/12/SQL-Test_running-300x245.png" alt="SQL Test running 300x245 SQL Test available" title="SQL-Test running tests" width="300" height="245" class="alignnone size-medium wp-image-527" /></a>
<br />
Testcases executing in SQL Test
</div>
<p>
Also check out Grant Fritchey's new video here: <a target="youtube" href="http://youtu.be/1w9X-WzcFz0">http://youtu.be/1w9X-WzcFz0</a>
</p>
</div>
<div>
<p>
Give it a try now:
</p>
<a href="http://www.red-gate.com/products/sql-development/sql-test/" target="redgate">
<div style="border-radius:10px;border:1px solid #0077C5;background:#0077C5;margin:10px 0;padding:10px;text-align:center;color:#FFF;font-weight:bold;font-size:12px; width:200px;">
  Download SQL Test Now!
</div>
</a>
</div><img src="http://feeds.feedburner.com/~r/sqlitynet/~4/Mr72YOo2hVc" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://sqlity.net/en/524/sql-test-available/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://sqlity.net/en/524/sql-test-available/</feedburner:origLink></item>
	</channel>
</rss>

