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

<channel>
	<title>rusanu.com</title>
	<atom:link href="http://rusanu.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://rusanu.com</link>
	<description>RUSANU CONSULTING LLC</description>
	<lastBuildDate>Sun, 18 Mar 2012 00:29:42 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>1000 Consecutive days on StackOverflow</title>
		<link>http://rusanu.com/2012/03/17/1000-consecutive-days-on-stackoverflow/</link>
		<comments>http://rusanu.com/2012/03/17/1000-consecutive-days-on-stackoverflow/#comments</comments>
		<pubDate>Sun, 18 Mar 2012 00:29:42 +0000</pubDate>
		<dc:creator>remus</dc:creator>
				<category><![CDATA[Announcements]]></category>
		<category><![CDATA[comment]]></category>
		<category><![CDATA[stackoverflow]]></category>

		<guid isPermaLink="false">http://rusanu.com/?p=1450</guid>
		<description><![CDATA[Few days ago I noticed that my StackOverflow profile shows 995 consecutive visited days. So naturally I started thinking about what does it mean to come back every day for a thousand days in a row. Looking back at the post I wrote almost 3 years ago to the day: stackoverflow.com: how to execute well [...]]]></description>
			<content:encoded><![CDATA[<p>Few days ago I noticed that my StackOverflow profile shows <a href="http://stackoverflow.com/users/105929/remus-rusanu">995 consecutive visited days</a>. So naturally I started thinking about what does it mean to come back every day for a thousand days in a row. Looking back at the post I wrote almost 3 years ago to the day: <a href="http://rusanu.com/2009/05/18/stackoverflowcom-how-to-execute-well-on-a-good-idea/">stackoverflow.com: how to execute well on a good idea</a> I can say that not much has changed: StackOverflow (and now the entire StackExchange network) is first and foremost a great community. The technical execution and the social nurturing of the site makes for a low friction environment that invites and rewards contribution, and it keeps getting better and better.</p>
]]></content:encoded>
			<wfw:commentRss>http://rusanu.com/2012/03/17/1000-consecutive-days-on-stackoverflow/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Adding a nullable column can update the entire table</title>
		<link>http://rusanu.com/2012/02/16/adding-a-nullable-column-can-update-the-entire-table/</link>
		<comments>http://rusanu.com/2012/02/16/adding-a-nullable-column-can-update-the-entire-table/#comments</comments>
		<pubDate>Thu, 16 Feb 2012 23:31:25 +0000</pubDate>
		<dc:creator>remus</dc:creator>
				<category><![CDATA[Denali]]></category>
		<category><![CDATA[Samples]]></category>
		<category><![CDATA[Troubleshooting]]></category>

		<guid isPermaLink="false">http://rusanu.com/?p=1438</guid>
		<description><![CDATA[In a previous article Online non-NULL with values column add in SQL Server 2012 I talked about how adding a non-null column with default values is now an online operation in SQL Server 2012 and I mentioned how the situation when the newly added column may increase the rowsize can result in the operation being [...]]]></description>
			<content:encoded><![CDATA[<p>In a previous article <a href="http://rusanu.com/2011/07/13/online-non-null-with-values-column-add-in-sql-server-11/">Online non-NULL with values column add in SQL Server 2012</a> I talked about how adding a non-null column with default values is now an online operation in SQL Server 2012 and I mentioned how the situation when the newly added column may increase the rowsize can result in the operation being performed offline:</p>
<blockquote><p>In the case when the newly added column increases the maximum possible row size over the 8060 bytes limit the column cannot be added online.</p></blockquote>
<p>In this article I want to show you how such a situation can arise and how it impacts even the case that prior to SQL Server 2012 was always online, namely adding a nullable column. Lets consider the following example:</p>
<p><code>
<pre>
create table T (
	c char(1) null,
	filler char(7998) null,
	vc1 varchar(50) null,
	vc2 varchar(50) null);
go	

insert into  T (c, vc1, vc2) values
	('A', NULL, NULL),
	('B', replicate ('X', 50), NULL),
	('C', NULL, replicate('Y', 50)),
	('D', replicate ('X', 50), replicate('Y', 50));
go

alter table T add some_int int null;
go
</pre>
<p></code></p>
<p>If you run the snippet above in SQL Server 2005, SQL Server 2008 or SQL Server 2008 R2 it will succeed, and the column <tt>some_int</tt> will be added online, as a metadata only operation. However, the resulting table has some interesting properties. Lets try to update the newly added <tt>some_int</tt> column:</p>
<p><code>
<pre>
update T set some_int = null
	where c= 'A';
update T set some_int = null
	where c= 'B';
update T set some_int = null
	where c= 'C';
update T set some_int = null
	where c= 'D';
go

(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)
<span style="color:red">Msg 511, Level 16, State 1, Line 7
Cannot create a row of size 8064 which is greater than the allowable maximum row size of 8060.</span>
The statement has been terminated.
</pre>
<p></code></p>
<p>3 updates succeeded, the fourth failed. Well, I&#8217;ve been warned when I created the table, right? <b>Warning: The table &#8220;T&#8221; has been created, but its maximum row size exceeds the allowed maximum of 8060 bytes. INSERT or UPDATE to this table will fail if the resulting row exceeds the size limit.</b> But I&#8217;m updating a <b>fixed length</b> column and, furthermore, I&#8217;m updating it from NULL to NULL. Lets try something else:</p>
<p><code>
<pre>
select * into T2 from T;
go
<span style="color:red">Msg 511, Level 16, State 1, Line 1
Cannot create a row of size 8064 which is greater than the allowable maximum row size of 8060.</span>
The statement has been terminated.
</pre>
<p></code></p>
<p>OK, how about this:</p>
<p>alter table T rebuild;<br />
go<br />
<span style="color:red">Msg 511, Level 16, State 1, Line 1<br />
Cannot create a row of size 8064 which is greater than the allowable maximum row size of 8060.</span><br />
The statement has been terminated.
</pre>
<p></code></p>
<p>So my previous sequence of actions led to a table that I cannot rebuild! Clearly, not a very desirable state.</p>
<h2>SQL Server 2012 nullable ADD COLUMN</h2>
<p>In SQL Server 2012 the situation above cannot happen. The adding of the column would be blocked:
<p>
<code></pre>
<p>create table T (<br />
	c char(1) null,<br />
	filler char(7998) null,<br />
	vc1 varchar(50) null,<br />
	vc2 varchar(50) null);<br />
go	</p>
<p>insert into  T (c, vc1, vc2) values<br />
	('A', NULL, NULL),<br />
	('B', replicate ('X', 50), NULL),<br />
	('C', NULL, replicate('Y', 50)),<br />
	('D', replicate ('X', 50), replicate('Y', 50));<br />
go</p>
<p>alter table T add some_int int null;<br />
go</p>
<p><span style="color:red">Msg 511, Level 16, State 1, Line 1<br />
Cannot create a row of size 8064 which is greater than the allowable maximum row size of 8060.</span><br />
The statement has been terminated.
</pre>
<p></code></p>
<p>So SQL Server 2012 is able to detect the problem upfront and prevent the table from reaching the state in which it cannot be rebuilt. But detecting this situation is not a straightforward matter of table metadata and column size, the situation arises only if the <i>data</i> in the table has this problem. After all the following sequence succeeds:</p>
<p><code>
<pre>
create table T (
	c char(1) null,
	filler char(7998) null,
	vc1 varchar(50) null,
	vc2 varchar(50) null);
go	

insert into  T (c, vc1, vc2) values
	('A', NULL, NULL),
	('B', replicate ('X', 50), NULL),
	('C', NULL, replicate('Y', 50));
go

alter table T add some_int int null;
go
</pre>
<p></code></p>
<p>Indeed it is only the record 'D' that has a problem because both variable columns <tt>vc1</tt> and <tt>vc2</tt> have values in the row that are not NULL (and of a certain size). The ALTER TABLE ... ADD COLUMN in this situation <i>must</i> validate each row to ensure that it fits in the page, including the newly added column size. Therefore every row gets updated and the newly added column value of NULL is populated in the row image, thus enforcing that every row fits in the table. If any row grows over the maximum size due to the newly added column then the ALTER fails and all the rows updated thus far are rolled back.</p>
<blockquote><p><b>If adding a nullable column in SQL Server 2012 has the potential of increasing the row size over the 8060 size then the ALTER performs an offline size-of-data update to every row of the table to ensure it fits in the page. This behavior is new in SQL Server 2012.</b></p></blockquote>
<p>This situation can arise when adding a new non-sparse fixed length column or a variable non-nullable column with default value to a table that already has the potential of creating rows over the maximum size of 8060. This new behavior (update every row in order to validate they all fit after the column is added) does not occur when adding a nullable variable length column or a sparse fixed length column.</p>
]]></content:encoded>
			<wfw:commentRss>http://rusanu.com/2012/02/16/adding-a-nullable-column-can-update-the-entire-table/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Show all index and heap access operators in the plan cache</title>
		<link>http://rusanu.com/2012/01/27/show-all-index-and-heap-access-operators-in-the-plan-cache/</link>
		<comments>http://rusanu.com/2012/01/27/show-all-index-and-heap-access-operators-in-the-plan-cache/#comments</comments>
		<pubDate>Fri, 27 Jan 2012 23:42:44 +0000</pubDate>
		<dc:creator>remus</dc:creator>
				<category><![CDATA[Samples]]></category>
		<category><![CDATA[Troubleshooting]]></category>
		<category><![CDATA[query plan]]></category>
		<category><![CDATA[sql server]]></category>

		<guid isPermaLink="false">http://rusanu.com/?p=1383</guid>
		<description><![CDATA[with xmlnamespaces (default 'http://schemas.microsoft.com/sqlserver/2004/07/showplan') select x.value(N'@NodeId',N'int') as NodeId , x.value(N'@PhysicalOp', N'sysname') as PhysicalOp , x.value(N'@LogicalOp', N'sysname') as LogicalOp , ox.value(N'@Database',N'sysname') as [Database] , ox.value(N'@Schema',N'sysname') as [Schema] , ox.value(N'@Table',N'sysname') as [Table] , ox.value(N'@Index',N'sysname') as [Index] , ox.value(N'@IndexKind',N'sysname') as [IndexKind] , x.value(N'@EstimateRows', N'float') as EstimateRows , x.value(N'@EstimateIO', N'float') as EstimateIO , x.value(N'@EstimateCPU', N'float') as EstimateCPU , x.value(N'@AvgRowSize', [...]]]></description>
			<content:encoded><![CDATA[<p><code>
<pre>
with xmlnamespaces (default 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
select x.value(N'@NodeId',N'int') as NodeId
	, x.value(N'@PhysicalOp', N'sysname') as PhysicalOp
	, x.value(N'@LogicalOp', N'sysname') as LogicalOp
	, ox.value(N'@Database',N'sysname') as [Database]
	, ox.value(N'@Schema',N'sysname') as [Schema]
	, ox.value(N'@Table',N'sysname') as [Table]
	, ox.value(N'@Index',N'sysname') as [Index]
	, ox.value(N'@IndexKind',N'sysname') as [IndexKind]
	, x.value(N'@EstimateRows', N'float') as EstimateRows
	, x.value(N'@EstimateIO', N'float') as EstimateIO
	, x.value(N'@EstimateCPU', N'float') as EstimateCPU
	, x.value(N'@AvgRowSize', N'float') as AvgRowSize
	, x.value(N'@TableCardinality', N'float') as TableCardinality
	, x.value(N'@EstimatedTotalSubtreeCost', N'float') as EstimatedTotalSubtreeCost
	, x.value(N'@Parallel', N'tinyint') as DOP
	, x.value(N'@EstimateRebinds', N'float') as EstimateRebinds
	, x.value(N'@EstimateRewinds', N'float') as EstimateRewinds
	, st.*
	, pl.query_plan
from sys.dm_exec_query_stats as st
cross apply sys.dm_exec_query_plan (st.plan_handle) as pl
cross apply pl.query_plan.nodes('//RelOp[./*/Object/@Database]') as op(x)
cross apply op.x.nodes('./*/Object') as ob(ox)
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2012/01/query_plan_access_relop.png"><img src="http://rusanu.com/wp-content/uploads/2012/01/query_plan_access_relop.png" alt="" title="query_plan_access_relop" width="600" class="aligncenter size-full wp-image-1423" /></a></p>
<p>I recently needed a query to look into the current query plan cache and locate all actual data access operators (index scans, index seeks, table scans). This is the query I used, I decided to place it here if someone else find it useful and, more importantly, so that I can find it again when I needed it&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://rusanu.com/2012/01/27/show-all-index-and-heap-access-operators-in-the-plan-cache/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>What is an LSN: Log Sequence Number</title>
		<link>http://rusanu.com/2012/01/17/what-is-an-lsn-log-sequence-number/</link>
		<comments>http://rusanu.com/2012/01/17/what-is-an-lsn-log-sequence-number/#comments</comments>
		<pubDate>Wed, 18 Jan 2012 03:33:28 +0000</pubDate>
		<dc:creator>remus</dc:creator>
				<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[dbcc loginfo]]></category>
		<category><![CDATA[fn_dblog]]></category>
		<category><![CDATA[log]]></category>
		<category><![CDATA[log sequence number]]></category>
		<category><![CDATA[lsn]]></category>

		<guid isPermaLink="false">http://rusanu.com/?p=1381</guid>
		<description><![CDATA[LSNs, or Log Sequence Numbers, are explained on MSDN at Introduction to Log Sequence Numbers: Every record in the SQL Server transaction log is uniquely identified by a log sequence number (LSN). LSNs are ordered such that if LSN2 is greater than LSN1, the change described by the log record referred to by LSN2 occurred [...]]]></description>
			<content:encoded><![CDATA[<p>LSNs, or Log Sequence Numbers, are explained on MSDN at <a href="http://msdn.microsoft.com/en-us/library/ms190411.aspx" target="_blank">Introduction to Log Sequence Numbers</a>:</p>
<blockquote><p>Every record in the SQL Server transaction log is uniquely identified by a log sequence number (LSN). LSNs are ordered such that if LSN2 is greater than LSN1, the change described by the log record referred to by LSN2 occurred after the change described by the log record LSN.</p></blockquote>
<p>There are several places where LSNs are exposed. For example <a href="http://msdn.microsoft.com/en-us/library/ms178575.aspx" target="_blank"><tt>sys.database_recovery_status</tt></a> has the columns <tt>last_log_backup_lsn</tt> and <tt>fork_point_lsn</tt>, <a href="http://msdn.microsoft.com/en-us/library/ms178655.aspx" target="_blank"><tt>sys.database_mirroring</tt></a> has the <tt>mirroring_failover_lsn</tt> column and the <tt>msdb</tt> table <a href="http://msdn.microsoft.com/en-us/library/ms186299.aspx" target="_blank"><tt>backupset</tt></a> contains <tt>first_lsn</tt>, <tt>last_lsn</tt>, <tt>checkpoint_lsn</tt>, <tt>database_backup_lsn</tt>, <tt>fork_point_lsn</tt> and <tt>differential_base_lsn</tt>. Not surprisingly all these places where LSNs are exposed are related to backup and recovery (mirroring <i>is</i> a form of recovery). The LSN is exposed a <b>numeric(25,0)</b> value that can be compared: a bigger LSN number value means a later log sequence number and therefore it can indicate if more log needs to be backed up or recovered.</p>
<p>Yet we can dig deeper. Look at the LSN as exposed by the <tt>fn_dblog</tt> function:</p>
<p><code>
<pre>
select * from fn_dblog(null, null);
</pre>
<p></code></p>
<style type="text/css">
table.sample {
	border-width: 1px;
	border-spacing: 2px;
	border-style: none;
	border-color: gray;
	border-collapse: collapse;
	background-color: white;
}
table.sample th {
	border-width: 1px;
	padding: 1px 5px;
	border-style: solid;
	border-color: gray;
	background-color: #eeeeee;
}
table.sample td {
	border-width: 1px;
	padding: 1px 5px;
	border-style: solid;
	border-color: gray;
	background-color: white;
}
</style>
<pre>
<table class="sample">
<tr>
<th>Current LSN</th>
<th>Operation</th>
<th>Context</th>
<th>Transaction ID</th>
<th>...</th>
</tr>
<tr>
<td>00000014:00000061:0001</td>
<td>LOP_BEGIN_XACT</td>
<td>LCX_NULL</td>
<td>0000:000001e4</td>
<td/></tr>
<tr>
<td>00000014:00000061:0002</td>
<td>LOP_INSERT_ROWS</td>
<td>LCX_NULL</td>
<td>0000:000001e4</td>
<td/></tr>
<tr>
<td>...</td>
<td/>
<td/>
<td/>
<td/></tr>
<tr>
<td>00000014:00000061:02ea</td>
<td>LOP_INSERT_ROWS</td>
<td>LCX_NULL</td>
<td>0000:000001e4</td>
<td/></tr>
<tr>
<td>00000014:000000d9:0001</td>
<td>LOP_INSERT_ROWS</td>
<td>LCX_NULL</td>
<td>0000:000001e4</td>
<td/></tr>
<tr>
<td>00000014:000000d9:0002</td>
<td>LOP_INSERT_ROWS</td>
<td>LCX_NULL</td>
<td>0000:000001e4</td>
<td/></tr>
<tr>
<td>...</td>
<td/>
<td/>
<td/>
<td/></tr>
<tr>
<td>00000014:00000153:021e</td>
<td>LOP_INSERT_ROWS</td>
<td>LCX_NULL</td>
<td>0000:000001e4</td>
<td/></tr>
<tr>
<td>00000014:00000153:021f</td>
<td>LOP_COMMIT_XACT</td>
<td>LCX_NULL</td>
<td>0000:000001e4</td>
<td/></tr>
<tr>
<td>00000014:000001ab:0001</td>
<td>LOP_BEGIN_XACT</td>
<td>LCX_NULL</td>
<td>0000:000001ea</td>
<td/></tr>
<tr>
<td>...</td>
<td/>
<td/>
<td/>
<td/></tr>
<tr>
<td>00000014:000001ab:01ac</td>
<td>LOP_INSERT_ROWS</td>
<td>LCX_NULL</td>
<td>0000:000001ea</td>
<td/></tr>
<tr>
<td>00000015:00000010:0001</td>
<td>LOP_INSERT_ROWS</td>
<td>LCX_NULL</td>
<td>0000:000001ea</td>
<td/></tr>
</table>
</pre>
<p>The LSN is shown as a three part structure. The first part seems to stay the same (the 14), the middle part apparently has some erratic increases and the last part seems to increase monotonically but it resets back to 0 when the middle part changes. It is much easier to understand what&#8217;s happening when you know what the three parts are:</p>
<ul>
<li>the first part is the VLF sequence number.</li>
<li>the middle part is the offset to the log block</li>
<li>the last part is the slot number inside the log block</li>
</ul>
<h2>Virtual Log files</h2>
<p>SQL Server manages the log by splitting into regions called VLFs, Virtual Log Files, as explained in the <a href="http://msdn.microsoft.com/en-us/library/ms179355.aspx" target="_blank">Transaction Log Physical Architecture</a>. We can inspect the VLFs of a database using <tt>DBCC LOGINFO</tt>:</p>
<pre>
FileId FileSize    StartOffset  FSeqNo  Status      Parity CreateLSN
------ ----------- -------------------------------- ------ -----------------
2      253952      8192         20      2           64     0
2      253952      262144       21      2           64     0
2      270336      516096       22      2           64     20000000021700747
2      262144      786432       23      2           64     21000000013600747
2      262144      1048576      24      2           64     22000000024900748
...
2      393216      16973824     75      2           64     74000000013600748
2      393216      17367040     0       0           0      74000000013600748
2      393216      17760256     0       0           0      74000000013600748
2      524288      18153472     0       0           0      74000000013600748

(59 row(s) affected)
</pre>
<p>So back to our fn_dblog result: the <tt>Current LSN</tt> value of 00000014:00000061:0001 means the following log record:</p>
<ul>
<li>the VLF sequence number 0&#215;14 (20 decimal)</li>
<li>in the log block starting at offset 0&#215;61 in the VLF (measured in units of 512 bytes)</li>
<li>the slot number 1</li>
</ul>
<p>From the DBCC LOGINFO output we know that the VLF with sequence number 0&#215;14  is starting at offset 8192 in the LDF file and has a size of 253952. It is the first VLF in the log file. the next LSNs increase the slot number until the LSN 00000014:00000061:02ea and then the LSN changes to 00000014:000000d9:0001. This means that the block that starts at offset 0&#215;61 has filled up and the next block, from offset 0xd9, started to be used. If we do the math, 0xd9-0&#215;61 is 0&#215;78 (or 120 decimal), which is exactly 60Kb. You may remember that the maximum SQL Server log block size is 60Kb.</p>
<p>Our transaction continued to insert records until the LSN 00000014:00000153:021e then at the next LSN we have a commit operation (LOP_COMMIT_XACT). The next LSN, which is for the next transaction start, is at LSN 00000014:000001ab:0001. What does this tell us? The transaction terminated with the INSERT operation at LSN 00000014:00000153:021e and then it issued a COMMIT. The commit operation generated one more log record, the one at LSN 00000014:00000153:021f and then asked for the commit LSN to be hardened. This causes the log block containing this LSN (the log block starting at offset 0&#215;153 in the VFL 0&#215;14) to be closed and written to disk (along with any other not-yet-written log-block that is <i>ahead</i> of this block). The next block can start immediately after this block, so the next operation will have the LSN 00000014:000001ab:0001. If we do the math we can see that 0x1ab-0&#215;153 is 0&#215;58 (decimal 88) so this last log block had 44Kb.</p>
<p>I hope by now is clear what happened next in the <tt>fn_dblog</tt>output I shown: the transaction continues to insert records generating more LSNs. The entry at 00000014:000001ab:01ac is not only the last one in the current log block, but is also the last one in the current VLF. The next LSN is 00000015:00000010:0001, the LSN at slot 1 in the first log block (offset 0&#215;10) of the VLF with sequence number 0&#215;15.</p>
<h3>Decimal LSNs</h3>
<p>What about those LSN numbers like the 20000000021700747 CreateLSN value shown in DBCC LOGINFO output? They are still three part LSNs, but the values are represented in decimal. 20000000021700747 would be the LSN 00000014:000000d9:02eb. </p>
<h3>Log flush waits</h3>
<p>Look at the following fn_dblog output:</p>
<pre>
...
00000016:0000003c:0001  LOP_BEGIN_XACT      LCX_NULL    0000:0000057f
00000016:0000003c:0002  LOP_INSERT_ROWS     LCX_HEAP    0000:0000057f
00000016:0000003c:0003  LOP_COMMIT_XACT     LCX_NULL    0000:0000057f
00000016:0000003d:0001  LOP_BEGIN_XACT      LCX_NULL    0000:00000580
00000016:0000003d:0002  LOP_INSERT_ROWS     LCX_HEAP    0000:00000580
00000016:0000003d:0003  LOP_COMMIT_XACT     LCX_NULL    0000:00000580
00000016:0000003e:0001  LOP_BEGIN_XACT      LCX_NULL    0000:00000581
00000016:0000003e:0002  LOP_INSERT_ROWS     LCX_HEAP    0000:00000581
00000016:0000003e:0003  LOP_COMMIT_XACT     LCX_NULL    0000:00000581
00000016:0000003f:0001  LOP_BEGIN_XACT      LCX_NULL    0000:00000582
00000016:0000003f:0002  LOP_INSERT_ROWS     LCX_HEAP    0000:00000582
00000016:0000003f:0003  LOP_COMMIT_XACT     LCX_NULL    0000:00000582
...
</pre>
<p>Can you spot the problem? Notice how the LSN slot number stays at at 1,2,3 and the log block number grows very frequently: 3c, 3d, 3e, 3f&#8230; This is the log signature of a sequence of single row inserts that each committed individually. After each INSERT the application had to wait for the log block to be flushed to disk. The logs blocks were all of minimal size, 512 bytes.</p>
<p>Here is how I generated this log:</p>
<p><code>
<pre>
set nocount on;
declare @i int = 0;
while @i < 1000
begin
	insert into t (filler) values ('A');
	set @i += 1;
end
go
</pre>
<p></code></p>
<p>Had the application used a batch commit it would had solved several issues:</p>
<ul>
<li>only wait for one or few commits to flush the log.</li>
<li>write less log, since every transaction generates an LOP_BEGIN_XACT and an LOP_COMMIT_XACT (they add up!).</li>
<li>write the operations in few large IO requests as opposed to a lot of small IO requests.</li>
</ul>
<p>So lets add batch commits to our test script:</p>
<p><code>
<pre>
set nocount on;
declare @i int = 0;
begin transaction
while @i < 1000
begin
	insert into t (filler) values ('A');
	set @i += 1;
	if @i % 100 = 0
	begin
		commit;
		begin transaction;
	end
end
commit
go
</pre>
<p></code></p>
<p>and then lets look at the log:</p>
<pre>
00000016:0000011d:0001  LOP_BEGIN_XACT     LCX_NULL   0000:000005d9
00000016:0000011d:0002  LOP_INSERT_ROWS    LCX_HEAP   0000:000005d9
00000016:0000011d:0003  LOP_INSERT_ROWS    LCX_HEAP   0000:000005d9
...
00000016:0000011d:006f  LOP_INSERT_ROWS    LCX_HEAP   0000:000005d9
00000016:0000011d:0070  LOP_COMMIT_XACT    LCX_NULL   0000:000005d9
00000016:00000136:0001  LOP_BEGIN_XACT     LCX_NULL   0000:000005db
00000016:00000136:0002  LOP_INSERT_ROWS    LCX_HEAP   0000:000005db
...
00000016:00000136:0064  LOP_INSERT_ROWS    LCX_HEAP   0000:000005db
00000016:00000136:0065  LOP_INSERT_ROWS    LCX_HEAP   0000:000005db
00000016:00000136:0066  LOP_COMMIT_XACT    LCX_NULL   0000:000005db
00000016:0000014d:0001  LOP_BEGIN_XACT     LCX_NULL   0000:000005dc
00000016:0000014d:0002  LOP_INSERT_ROWS    LCX_HEAP   0000:000005dc
00000016:0000014d:0003  LOP_INSERT_ROWS    LCX_HEAP   0000:000005dc
...
00000016:0000014d:0066  LOP_INSERT_ROWS    LCX_HEAP   0000:000005dc
00000016:0000014d:0067  LOP_COMMIT_XACT    LCX_NULL   0000:000005dc
</pre>
<p>We can see how the batch commit has created fewer, larger, log blocks. The application had to wait fewer times for the log to harden, and on each wait it issued a larger IO request. The log blocks are also more densely filled with LOP_INSERT_ROWS operations and do not have the overhead of having to log the LOP_BEGIN_XACT/LOP_COMMIT_XACT for every row inserted.</p>
<h2>Conclusion</h2>
<p>This little example shows not only how to understand the LSN structure, but it also shows how to read into the fn_dblog output. I also wanted to show the typical log signature of a commit operation: the LOP_COMMIT_XACT operation is recorded in the log and the log block is closed and flushed to disk. The next LSN will usually have slot 1 in the next block. The last example even shows how reading the log can spot potential application performance problems.</p>
]]></content:encoded>
			<wfw:commentRss>http://rusanu.com/2012/01/17/what-is-an-lsn-log-sequence-number/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SQL Server table columns under the hood</title>
		<link>http://rusanu.com/2011/10/20/sql-server-table-columns-under-the-hood/</link>
		<comments>http://rusanu.com/2011/10/20/sql-server-table-columns-under-the-hood/#comments</comments>
		<pubDate>Thu, 20 Oct 2011 22:36:52 +0000</pubDate>
		<dc:creator>remus</dc:creator>
				<category><![CDATA[CodeProject]]></category>
		<category><![CDATA[Samples]]></category>
		<category><![CDATA[Troubleshooting]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://rusanu.com/?p=1301</guid>
		<description><![CDATA[You probably can easily answer a question like &#8216;What columns does this table have?&#8217;. Whether you use the SSMS object explorer, or sp_help, or you query sys.column, the answer is fairly easy to find. But what is I ask &#8216;What are the physical columns of this table?&#8217;. Huh? Is there any difference? Lets see. At [...]]]></description>
			<content:encoded><![CDATA[<p>You probably can easily answer a question like &#8216;What columns does this table have?&#8217;. Whether you use the SSMS object explorer, or <tt>sp_help</tt>, or you query <tt>sys.column</tt>, the answer is fairly easy to find. But what is I ask &#8216;What are the <b>physical</b> columns of this table?&#8217;. Huh? Is there any difference? Lets see.</p>
<p>At the logical layer tables have exactly the structure you declare it in your CREATE TABLE statement, and perhaps modifications from ALTER TABLE statements. This is the layer at which you can look into <tt>sys.columns</tt> and see the table structure, or look at the table in SSMS object explorer and so on and so forth. But there is also a lower layer, the physical layer of the storage engine where the table might have surprisingly different structure from what you expect.</p>
<h3>Inspecting the physical table structure</h3>
<p>To view the physical table structure you must use the undocumented system internals views: <a href="http://msdn.microsoft.com/en-us/library/ms189600.aspx"><tt>sys.system_internals_partitions</tt></a> and  <a href="http://msdn.microsoft.com/en-us/library/ms189600.aspx"><tt>sys.system_internals_partition_columns</tt></a>:</p>
<p><code>
<pre>
select p.index_id, p.partition_number,
	pc.leaf_null_bit,
	coalesce(cx.name, c.name) as column_name,
	pc.partition_column_id,
	pc.max_inrow_length,
	pc.max_length,
	pc.key_ordinal,
	pc.leaf_offset,
	pc.is_nullable,
	pc.is_dropped,
	pc.is_uniqueifier,
	pc.is_sparse,
	pc.is_anti_matter
from sys.system_internals_partitions p
join sys.system_internals_partition_columns pc
	on p.partition_id = pc.partition_id
left join sys.index_columns ic
	on p.object_id = ic.object_id
	and ic.index_id = p.index_id
	and ic.index_column_id = pc.partition_column_id
left join sys.columns c
	on p.object_id = c.object_id
	and ic.column_id = c.column_id
left join sys.columns cx
	on p.object_id = cx.object_id
	and p.index_id in (0,1)
	and pc.partition_column_id = cx.column_id
where p.object_id = object_id('...')
order by index_id, partition_number;
</pre>
<p></code></p>
<p>Lets inspect some simple table structures:</p>
<p><code>
<pre>
create table users (
	user_id int not null identity(1,1),
	first_name varchar(100) null,
	last_name varchar(100) null,
	birth_date datetime null);
</pre>
<p></code></p>
<p>Running our query after, of course, we specify <tt>object_id('users')</tt>:</p>
<p><a href="http://rusanu.com/wp-content/uploads/2011/09/physical-table-1.png"><img src="http://rusanu.com/wp-content/uploads/2011/09/physical-table-1.png" alt="" title="physical-table-1" width="600" class="aligncenter size-full wp-image-1304" /></a></p>
<p>We can see that the physical structure is very much as we expected: the physical rowset has 4 physical columns, of the expected types and sizes. One thing to notice is that, although the column order is the one we specified, the columns are layout on disk in a different order: the <tt>user_id</tt> is stored in row at offset 4, followed by the <tt>birth_date</tt> at offset 8 and then by the two variable length columns (<tt>first_name</tt> and <tt>last_name</tt>) that have negative offsets, an indication that they reside in the variable size portion of the row. This should be of no surprise as we know that the row format places all fixed length columns first in the row, ahead of the variable length columns.</p>
<p><H3>Adding a clustered index</h3>
<p>Our table right now is a heap, we should make <tt>user_id</tt> a primary key, and lets check the table structure afterward:</p>
<p><code>
<pre>
alter table users add constraint pk_users_user_id primary key (user_id);
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/09/physical-table-pk.png"><img src="http://rusanu.com/wp-content/uploads/2011/09/physical-table-pk.png" alt="" title="physical-table-pk" width="600" class="aligncenter size-full wp-image-1309" /></a></p>
<p>As we can see, the table has changed from a heap into a clustered index (index_id changed from 0 to 1) and the <tt>user_id</tt> column has become part of the key.</p>
<h3>Non-Unique clustered indexes</h3>
<p>Now lets say that we want a different clustered index, perhaps by <tt>birth_date</tt> (maybe our application requires frequent range scans on this field). We would change the primary key into a non-clustered primary key (remember, the primary key and the clustered index do <b>not</b> have to be the same key) and add a clustered index by the <tt>birth_date</tt> column:</p>
<p><code>
<pre>
alter table users drop constraint pk_users_user_id;
create clustered index cdx_users on users (birth_date);
alter table users add constraint
	pk_users_user_id primary key nonclustered  (user_id);
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/09/physical-table-cdx.png"><img src="http://rusanu.com/wp-content/uploads/2011/09/physical-table-cdx.png" alt="" title="physical-table-cdx" width="600" class="aligncenter size-full wp-image-1310" /></a></p>
<p>Several changes right there:</p>
<ul>
<li>A new index has appeared (index_id 2), which was expected, this is the non-clustered index that now enforces the primary key constraint on column <tt>user_id</tt>.</li>
<li>The table has a new physical column, with <tt>partition_column_id</tt> 0. This new column has no name, because it is not visible in the logical table representation (you cannot select it, nor update it). This is an <a href="http://msdn.microsoft.com/en-us/library/ms190639.aspx">uniqueifier column</a> (<tt>is_uniqueifier</tt> is 1) because we did not specify that our clustered index in unique:<br />
<blockquote><p>If the clustered index is not created with the UNIQUE property, the Database Engine automatically adds a 4-byte uniqueifier column to the table. When it is required, the Database Engine automatically adds a uniqueifier value to a row to make each key unique. This column and its values are used internally and cannot be seen or accessed by users.</p></blockquote>
<p><br/><br />
Klaus Aschenbrenner has <a href="http://www.csharp.at/blog/PermaLink,guid,98d6366f-9e37-4413-8435-793129ac87cb.aspx">a good series of articles that explain uniqueifier columns in more detail</a>.</li>
<li>The non-clustered index that enforces the primary key constraint has <b>3</b> physical columns: <tt>user_id</tt> and two unnamed ones. This is because each row in the non-clustered index contains the corresponding clustered index row key values, in our case the <tt>birth_date</tt> column and the uniqueifier column.</li>
</ul>
<h3>Online operations and anti-matter columns</h3>
<p>Is the uniqueifier column the only hidden column in a table? No. Lets rebuild our non-clustered index, making sure we specify it to be an online operation:</p>
<p><code>
<pre>
alter index pk_users_user_id on users rebuild with (online=on);
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/09/physical-table-anti-matter.png"><img src="http://rusanu.com/wp-content/uploads/2011/09/physical-table-anti-matter.png" alt="" title="physical-table-anti-matter" width="600" class="aligncenter size-full wp-image-1317" /></a></p>
<p>Notice how the non-clustered index now has one more column, a new column that has the <tt>is_antimatter</tt> value 1. As you probably guess, this is an anti-matter column. For an in-depth explanation of what is the purpose of the anti-matter column I recommend reading <a href="http://msdn.microsoft.com/en-us/library/cc966402.aspx">Online Indexing Operations in SQL Server 2005</a>:</p>
<blockquote><p>During the build phase, rows in the new &#8220;in-build&#8221; index may be in an intermediate state called antimatter. This mechanism allows concurrent DELETE statements to leave a trace for the index builder transaction to avoid inserting deleted rows. At the end of the index build operation all antimatter rows should be cleared.</p></blockquote>
<p>Note that even after the online operation finishes and the antimatter rows are removed, the antimatter <i>column</i> will be part of the physical rowset structure.</p>
<p>There could exist more hidden columns in the table, for example if we enable change tracking:</p>
<p><code>
<pre>
alter table users ENABLE CHANGE_TRACKING;
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/09/physical-table-xdes.png"><img src="http://rusanu.com/wp-content/uploads/2011/09/physical-table-xdes.png" alt="" title="physical-table-xdes" width="600" class="aligncenter size-full wp-image-1322" /></a></p>
<p>Enabling change tracking on our table has added one more hidden column.</p>
<h3>Table structure changes</h3>
<p>Next lets look at some table structure modifying operations: adding, dropping and modifying columns. Were going to start anew with a fresh table for our examples:</p>
<p><code>
<pre>
create table users  (
	user_id int not null identity(1,1) primary key,
	first_name char(100) null,
	last_name char(100) null,
	birth_date datetime null);
go
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed.png" alt="" title="physical-table-fixed" width="600" class="aligncenter size-full wp-image-1329" /></a></p>
<p>Next lets modify some columns: we want to make the birth_date not nullable and reduce the length of the first_name to 75:</p>
<p><code>
<pre>
alter table users alter column birth_date datetime not null;
alter table users alter column first_name char(75);
go
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-2.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-2.png" alt="" title="physical-table-fixed-2" width="600" class="aligncenter size-full wp-image-1330" /></a></p>
<p>Notice how the two alter operations ended up in adding a new column each, and dropping the old column. But also note how the null bit and the leaf_offset values have stayed <i>the same</i>. This means that the column was added &#8216;in place&#8217;, replacing the dropped column. This is a metadata only operation that did not modify any record in the table,  it simply changed how the data in the existing records is interpreted.</p>
<p>But now we figure out the 75 characters length is wrong and we want to change it back to 100. Also, the birth_date column probably doesn&#8217;t need hours, so we can change it to a date type:</p>
<p><code>
<pre>
alter table users alter column birth_date date not null;
alter table users alter column first_name char(100);
go
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-3.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-3.png" alt="" title="physical-table-fixed-3" width="600" class="aligncenter size-full wp-image-1334" /></a></p>
<p>The birth_date column has changed type and now it requires only 3 bytes, but the change occurred in-place, just as the nullability change we did before: it remains at the same offset in the record and it has the same null bit. However, the first_name column was moved from offset 8 to offset 211, and the null bit was changed from 4 to 5. Because the first_name column has <i>increased</i> in size it cannot occupy the same space as before in the record and the record has effectively increased to accommodate the <i>new</i> first_name column. This happened despite the fact that the first_name column was originally of size 100 so in theory it could reclaim the old space it used in the record, but the is simply a too corner case for the storage engine to consider.</p>
<p>By now we figure that the fixed length 100 for the first_name and last_name columns was a poor choice, so we would like to change them to more appropriate variable length columns:</p>
<p><code>
<pre>
alter table users alter column first_name varchar(100);
alter table users alter column last_name varchar(100);
go
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-4.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-4.png" alt="" title="physical-table-fixed-4" width="600" class="aligncenter size-full wp-image-1337" /></a></p>
<p>The type change from fixed length column to variable length column cannot be done in-place, so the first_name and last_name columns get new null bit values.They also have negative leaf_offset values, which is typical for variable length columns as they don&#8217;t occupy fixed positions in the record.</p>
<p>Next lets change the length of the variable columns:
<p>
<code>
<pre>
alter table users alter column first_name varchar(250);
alter table users alter column last_name varchar(250);
go
</code></pre>
<p><a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-5.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-5.png" alt="" title="physical-table-fixed-5" width="600" class="aligncenter size-full wp-image-1339" /></a></p>
<p>For this change the column length was modified <i>without</i> dropping the column and adding a new one. An increase of size for a variable length columns is one of the operations that is really <i>altering</i> the physical column, not dropping the column and adding a new one to replace it. However, a decrease in size, or a nullability change, does again drop and add the column, as we can quickly check now:</p>
<p><code>
<pre>
alter table users alter column first_name varchar(100);
alter table users alter column last_name varchar(250) not null;
go
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-6.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-6.png" alt="" title="physical-table-fixed-6" width="600" class="aligncenter size-full wp-image-1340" /></a></p>
<p>Finally, lets say we tried to add two more fixed length columns, but we we undecided on the name and length so we added a couple of columns, then deleted them and added again a new one:</p>
<p><code>
<pre>
alter table users add mid_name char(50);
alter table users add surname char(25);
alter table users drop column mid_name;
alter table users drop column surname;

alter table users add middle_name char(100);
go</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-8.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-8.png" alt="" title="physical-table-fixed-8" width="600" class="aligncenter size-full wp-image-1346" /></a></p>
<p>This case is interesting because it shows how adding a new fixed column <i>can</i> reuse the space left in the row by dropped fixed columns, but only if the dropped columns are the last fixed columns. In our table the columns <tt>mid_name</tt> and <tt>surname</tt> where originally added at offset 211 and 261 respectively and their length added up to 75 bytes. After we dropped them, the <tt>middle_name</tt> column we added is placed at offset 211, thus reusing the space formerly occupied by the dropped columns. This happens even though the length of the newly added column is 100 bytes, bigger than the 75 bytes occupied by the dropped columns before.</p>
<p>By now, our 5 column table has actually 15 columns in the physical storage format, 10 dropped columns and 5 usable columns. The table uses 412 bytes of fixed space for the 3 fixed length columns that have a total length of only 112 bytes. The variable length columns that now store the first_name and last_name are stored in the record <i>after</i> these 412 reserved bytes for fixed columns that are now dropped. Since the records always consume all the reserved fixed size, this is quite wasteful. How do we reclaim it? Rebuild the table:</p>
<p><code>
<pre>
alter table users rebuild;
go
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-7.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-fixed-7.png" alt="" title="physical-table-fixed-7" width="600" class="aligncenter size-full wp-image-1341" /></a></p>
<p>As you can see the rebuild operation got rid off the dropped columns and now the physical storage layout is compact, aligned with the logical layout. So whenever doing changes to a table structure remember that at the storage layer most changes are cumulative, they are most times implemented by dropping a column and adding a new column back, with the new type/length/nullability. Whenever possible the a modified column reuses the space in the record and newly added columns may reuse space previously used by dropped columns.</p>
<h3>Partitioned Tables</h3>
<p>When partitioning is taken into account another dimension of the physical table structure is revealed. To start, lets consider a typical partitioned table:</p>
<p><code>
<pre>
create partition function pf (date) as range for values ('20111001');
go

create partition scheme ps as partition pf all to ([PRIMARY]);
go

create table sales (
	sale_date date not null,
	item_id int not null,
	store_id int not null,
	price money not null)
	on ps(sale_date);
go
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-partitioned-1.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-partitioned-1.png" alt="" title="physical-table-partitioned-1" width="600" class="aligncenter size-full wp-image-1350" /></a></p>
<p>As we know, the partitioned tables are, from a physical point of view, a collection of rowsets that belong to the same logical object (the 'table'). Looking under the hood shows that our table has two rowsets, and they have identical column structure. Typically new partitions are added by the ETL process that uploads data into a staging table that gets switched in using a fast partition switch operation:</p>
<p><code>
<pre>
create table sales_staging (
	sale_date date not null,
	item_id int not null,
	store_id int not null,
	price numeric(10,2) not null,
	constraint check_partition_range
		check (sale_date = '20111002'))
	on [PRIMARY];

alter partition scheme ps next used [PRIMARY];
alter partition function pf() split range ('20111002');
go

alter table sales_staging switch to sales partition $PARTITION.PF('20111002');
go
<span style="color:red">Msg 4944, Level 16, State 1, Line 2</span>
</pre>
<p><span style="color:red">ALTER TABLE SWITCH statement failed because column 'price' has data type numeric(10,2) in source table 'test.dbo.sales_staging' which is different from its type money in target table 'test.dbo.sales'.</span><br />
</code></p>
<p>OK, so we made a mistake in the staging table, so lets correct it, then try to switch it in again:</p>
<p><code>
<pre>
alter table sales_staging alter column price money not null;
alter table sales_staging switch to sales partition $PARTITION.PF('20111002');
go
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-partitioned-2.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-partitioned-2.png" alt="" title="physical-table-partitioned-2" width="600" class="aligncenter size-full wp-image-1354" /></a></p>
<p>We see that the partition switch operation has switched in the rowset of the staging table into the second partition of the <tt>sales</tt> table. But since the staging table had an operation that modified a column type, which in effect has dropped the numeric price columns and added a new price column of the appropriate money type, the second rowset of the partitioned table now has 5 columns, including a dropped one. The partition switch operation brings into the partitioned table the staging table <i>as is</i>, dropped columns and all. What that means is that partitions of the partitioned table can have, under the hood, a completely different physical structure from one another. Normally this should be of little concern and just a courisity to woe the newbies at DBA cocktail parties, but this problem has a darker side, known as <a href="http://support.microsoft.com/kb/2504090">KB2504090</a>:</p>
<blockquote><p>This issue occurs because the accessor that SQL Server uses to insert data into different partitions recognizes the metadata changes incorrectly. When data is inserted into the new partition that is created after a column is dropped, the number of the maximum nullable columns in the new partition may be one less than the number of the maximum nullable columns in the old partition.</p></blockquote>
<p>If you ever find your partitioned tables not to have an uniform internal structure across all partitions, I recommend you rebuild the partition that is different. This simple query can be used to look at the number of physical columns in each partition of a table:</p>
<p><code>
<pre>
select count(*) as count_columns,
	index_id,
	partition_number
from sys.system_internals_partitions p
join sys.system_internals_partition_columns pc
	on p.partition_id = pc.partition_id
where p.object_id = object_id('sales')
group by index_id, partition_number;
go
</pre>
<p></code><br />
<a href="http://rusanu.com/wp-content/uploads/2011/10/physical-table-partitioned-3.png"><img src="http://rusanu.com/wp-content/uploads/2011/10/physical-table-partitioned-3.png" alt="" title="physical-table-partitioned-3" width="282" height="110" class="aligncenter size-full wp-image-1377" /></a></p>
<p>This query shows that partitions 1 and 3 have 4 physical columns, while partition 2 has 5. We can rebuild partition 2:</p>
<p><code>
<pre>
alter table sales rebuild partition = 2;
go
</pre>
<p></code></p>
<p>With this operation we have rebuild the partition number 2 from scratch and removed all dropped columns in the process.</p>
<p>Note that my query above would only detect differences in the number of physical columns, but two partitions can still have a very different physical layout even if they have the same number of physical columns, eg. one can have physical column number 9 dropped and physical column number 10 used, while the other can have the opposite. Use your judgement when looking at the internal physical column layout to understand if they are truly the same.</p>
]]></content:encoded>
			<wfw:commentRss>http://rusanu.com/2011/10/20/sql-server-table-columns-under-the-hood/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Understanding Hash, Sort and Exchange Spill events</title>
		<link>http://rusanu.com/2011/10/19/understanding-hash-sort-and-exchange-spill-events/</link>
		<comments>http://rusanu.com/2011/10/19/understanding-hash-sort-and-exchange-spill-events/#comments</comments>
		<pubDate>Wed, 19 Oct 2011 19:28:10 +0000</pubDate>
		<dc:creator>remus</dc:creator>
				<category><![CDATA[Troubleshooting]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[execution]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[query optimization]]></category>
		<category><![CDATA[sql server]]></category>

		<guid isPermaLink="false">http://rusanu.com/?p=1324</guid>
		<description><![CDATA[Certain SQL Server query execution operations are calibrated to perform best by using a (somewhat) large amount of memory as intermediate storage. The Query Optimizer will choose a plan and estimate the cost based on these operators using this memory scratch-pad. But this is, of course, only an estimate. At execution the estimates may prove [...]]]></description>
			<content:encoded><![CDATA[<p>Certain SQL Server query execution operations are calibrated to perform best by using a (somewhat) large amount of memory as intermediate storage. The Query Optimizer will choose a plan and estimate the cost based on these operators using this memory scratch-pad. But this is, of course, only an estimate. At execution the estimates may prove wrong and the plan must continue despite not having enough memory. In such an event, these operators <b>spill</b> to disk. When a spill occurs, the memory scratch-pad is flushed into <b>tempdb</b> and more data is accommodated in the (now) free memory. When the data flushed to tempdb is needed again, is read from disk. Needless to say, spilling into tempdb is order of magnitude slower than using just the memory scratch-pad. Monitoring for spilling is specially important in ETL jobs, since these occurrences may cause the ETL job to stretch for many more minutes, sometimes even hours. For an exhaustive discussion of ETL, including some references to spills, see <a href="http://msdn.microsoft.com/en-us/library/dd425070%28v=sql.100%29.aspx">The Data Loading Performance Guide</a>.</p>
<h3><a href="http://technet.microsoft.com/en-us/library/ms190736.aspx">Hash Warning Event</a></h3>
<blockquote><p>Hash recursion occurs when the build input does not fit into available memory, resulting in the split of input into multiple partitions that are processed separately. If any of these partitions still do not fit into available memory, it is split into subpartitions, which are also processed separately. This splitting process continues until each partition fits into available memory or until the maximum recursion level is reached (displayed in the IntegerData data column).
</p></blockquote>
<p>This is one of the most common spills. Hash Join is a darling of the Query Optimizer, as it a very fast operator that can join two unsorted sources efficiently, requiring a single pass over each source. Not only that, but it can also be used for duplicate removal (eg. <tt>DISTINCT</tt> clause) and grouping aggregates. See <a href="http://technet.microsoft.com/en-us/library/ms189313.aspx">Understanding Hash Joins</a> for more details.</p>
<p>Unfortunately it also requires a lot of memory. Hash spill occurrences are usually an indication of bad cardinality estimates that are in turn caused, most times, by missing or outdated statistics. The first action to do, when faced with Hash Join spill warnings, is to update (or create) stats on the columns involved. If the problem persists then you must resort to a different join type. Since the optimizer would had already picked a better join if it could, you need to look at the problem from a different angle: how could you help the optimizer to pick a different join, w/o <i>forcing</i> it?</p>
<p>The optimizer would pick a LOOP if it could seek the inner side for a reasonable number of times, so perhaps you need an index on the inner side to allow a seek and/or an index on the outer side that would filter the produced output early so it can reduce the number of probes in the inner side. See <a href="http://msdn.microsoft.com/en-us/library/ms191318.aspx">Understanding Nested Loops Joins</a> for more details.</p>
<p>The other alternative is a MERGE join, but merge requires the input on both sides to be sorted. Adding an index to each of the sources on both sides of the join that provides the order guarantee would likely woe the optimizer into using the MERGE join. See <a href="http://msdn.microsoft.com/en-us/library/ms190967.aspx">Understanding Merge Joins</a> for more details.</p>
<h3><a href="http://technet.microsoft.com/en-us/library/ms178041.aspx">Sort Warning Event</a></h3>
<blockquote><p>The Sort Warnings event class indicates that sort operations do not fit into memory. This does not include sort operations involving the creation of indexes, only sort operations within a query (such as an ORDER BY clause used in a SELECT statement).</p></blockquote>
<p>If a query requires an order guarantee (eg. it has an ORDER BY clause, or it projects a function like ROW_NUMBER) and there is no index to guarantee the order then there is little choice to have: the execution must sort the input before proceeding. If the input is small then the sort occurs in memory and is very cheap, but in this case no spill warning would occur. The Query Optimizer is usually smart to postpone the sorting until all filtering is complete, so if it still spills it usually means there is no more filtering that can be applied. The solution when faced with Sort spills is usually to add a covering index that provides the desired order.</p>
<p>Another case of Sort spill warning is when the Query Optimizer goes creative and adds a sort into a plan that technically does not require an order guarantee. Such events are though very rare and esoteric. The action to take really depends from case to case.</p>
<h3><a href="http://technet.microsoft.com/en-us/library/ms191514.aspx">Exchange Spill Event</a></h3>
<blockquote><p>The Exchange Spill event class indicates that communication buffers in a parallel query plan have been temporarily written to the tempdb database. This occurs rarely and only when a query plan has multiple range scans. </p></blockquote>
<p>You&#8217;ll probably never going to hit this problem. If you do, I recommend you go over the recommendations in the linked article: <a href="http://technet.microsoft.com/en-us/library/ms191514.aspx">Exchange Spill Event</a></p>
<h3>Conclusion</h3>
<p>Technically there is one more spill class: the spool spill. Since spools are *meant* to spill, the presence of a spool spill is usually less of a concern.</p>
<p>The purpose of this article is to show that there is ample documentation available on MSDN regarding these spill events. The tempdb spills are easily detectable and reasonably explained in the product documentation. Presence of spills may indicate potential performance problems as a spill involves disk reads and writes and is many times slower than the corresponding in-memory-only operation. They also add overload to tempdb and may cause contention.</p>
]]></content:encoded>
			<wfw:commentRss>http://rusanu.com/2011/10/19/understanding-hash-sort-and-exchange-spill-events/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>T-SQL functions do no imply a certain order of execution</title>
		<link>http://rusanu.com/2011/08/10/t-sql-functions-do-no-imply-a-certain-order-of-execution/</link>
		<comments>http://rusanu.com/2011/08/10/t-sql-functions-do-no-imply-a-certain-order-of-execution/#comments</comments>
		<pubDate>Wed, 10 Aug 2011 08:48:48 +0000</pubDate>
		<dc:creator>remus</dc:creator>
				<category><![CDATA[Samples]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://rusanu.com/?p=1290</guid>
		<description><![CDATA[Looking at this question on StackOverflow: Conversion failed when converting from a character string to uniqueidentifier error in SQL Server one can see a reproducible example where a string split UDF works fine in the SELECT statement, but it gives a conversion error in the DELETE statement. Certainly, a bug, right? Actually, not. The issue [...]]]></description>
			<content:encoded><![CDATA[<p>Looking at this question on StackOverflow: <a href="http://stackoverflow.com/questions/6989522/conversion-failed-when-converting-from-a-character-string-to-uniqueidentifier-err" target="_blank">Conversion failed when converting from a character string to uniqueidentifier error in SQL Server</a>  one can see a reproducible example where a string split UDF works fine in the SELECT statement, but it gives a conversion error in the DELETE statement. Certainly, a bug, right? Actually, not.</p>
<p>The issue at hand is in fact remarkably similar to another common misconception around T-SQL I had to debunk some time ago, see <a href="http://rusanu.com/2009/09/13/on-sql-server-boolean-operator-short-circuit/">On SQL Server boolean operator short-circuit</a>: that C like operator short-circuit is guaranteed in T-SQL (hint: it isn&#8217;t, read the linked article to see a clear counter example).</p>
<p>In the StackOverlow post the misconception is that order of declaration implies order of execution, that the function is evaluated somehow separately from the rest of the query and some sort of temporary result is created that is then used in the overall query execution. This understanding comes naturally to the imperative procedural language mindset of developers trained in C, C++, C# and other similar languages. But in the SQL Server declarative language that is T-SQL, your intuition is actually wrong. To illustrate I will give a simple counter-example, reusing the code from my earlier boolean short-circuit article:</p>
<pre><code>
create table eav (
    eav_id int identity(1,1) primary key,
    attribute varchar(50) not null,
    is_numeric bit not null,
    [value] sql_variant null);
create index eav_attribute on eav(attribute) include ([value]);
go

-- Fill the table with random values
set nocount on
declare @i int;
select @i = 0;
begin tran
while @i < 100000
begin
    declare @attribute varchar(50),
        @is_numeric bit,
        @value sql_variant;
    select @attribute = 'A' + cast(cast(rand()*1000 as  int) as varchar(3));
    select @is_numeric = case when rand() > 0.5 then 1 else 0 end;
    if 1=@is_numeric
        select @value = cast(rand() * 100 as int);
    else
        select @value = 'Lorem ipsum';
    insert into eav (attribute, is_numeric, [value])
    values (@attribute, @is_numeric, @value);
    select @i = @i+1;
    if (@i % 1000 = 0)
    begin
		commit;
		raiserror(N'Inserted %d', 0,0,@i);
		begin tran;
    end
end
commit
go

-- insert a 'trap'
insert into eav (attribute, is_numeric, [value])
values ('B1', 0, 'Gotch ya');
go
</code></pre>
<p>Now we&#8217;re going to define our inline table UDF:</p>
<pre><code>
create function udf_get_only_numerics()
returns table
with schemabinding
as return
	select [value], [attribute]
	from dbo.eav
	where is_numeric = 1;
go
</code></pre>
<p>And now lets innocently query our UDF:</p>
<pre><code>
select [value]
	from dbo.udf_get_only_numerics ()
    where attribute = 'B1'
    and cast([value] as int) > 50
go
</code></pre>
<p>Since we&#8217;re selecting from the UDF, we&#8217;re guaranteed that we&#8217;re only going to see values that have the <tt>is_numeric</tt> column value 1, so the <tt>cast([value] as int)</tt> must always succeed, right? Wrong, we&#8217;ll get a conversion exception:</p>
<pre><code>
<span style="color:red">Msg 245, Level 16, State 1, Line 3
Conversion failed when converting the varchar value 'Gotch ya' to data type int.</span>
</code></pre>
<p>So what&#8217;s going on? Lets insert a known value and inspect the execution plan:</p>
<pre></code>
insert into eav (attribute, is_numeric, [value])
values ('B2', 1, 65);
go

select [value]
	from dbo.udf_get_only_numerics ()
    where attribute = 'B2'
    and cast([value] as int) > 50
go
</code></pre>
<p><a href="http://rusanu.com/wp-content/uploads/2011/08/udf-eav-plan.png"><img src="http://rusanu.com/wp-content/uploads/2011/08/udf-eav-plan.png" alt="" title="udf-eav-plan" width="600" height="288" class="aligncenter size-full wp-image-1295" /></a></p>
<p>Looking at the plan we see that, just as in my boolean short-circuit example, the presence of an index on <tt>attribute</tt> that includes the <tt>value</tt> column but not the <tt>is_numeric</tt> was too good opportunity to pass for the optimizer. And this plan will evaluate the <tt>cast([value]as int)</tt> expression <b>before</b> it evaluate the WHERE clause inside the UDF. That&#8217;s right, the WHERE clause of the UDF has moved in the execution plan <i>above</i> the WHERE clause of the query using the UDF. The naive assumption that the function definition posses some sort of barrier for execution was proven wrong.</p>
<p>So how does this explains the failure seen in the StackOverflow question? The UDF, which was originally posted on <a href="http://www.sqlservercentral.com/articles/Tally+Table/72993/" target="_blank">Tally OH! An Improved SQL 8K “CSV Splitter” Function</a> article on sqlservercentral.com, contains WHERE clauses to that in effect restricts the output to only complete tokens. w/o the WHERE clause it would return non-complete token. We can easily test this, lets remove the WHERE clause and use the same input as the test in the StackOverflow question:</p>
<pre><code>
CREATE FUNCTION dbo.DelimitedSplit8K_noWhere
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
     -- enough to cover VARCHAR(8000)
 WITH E1(N) AS (
   SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
   SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
   SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (
                 SELECT 0 UNION ALL
                 SELECT TOP (DATALENGTH(ISNULL(@pString,1)))
                   ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (
                 SELECT t.N+1
                   FROM cteTally t
                )
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1),
        Item  = SUBSTRING(@pString,s.N1,
                ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000))
   FROM cteStart s;
</code></pre>
<p>When we run this the output contains not only the two token in the input, but also every substring of these tokens:</p>
<pre><code>
-- Test string that will be split into table in the DelimitedSplit8k function
declare @temp varchar(max) =
     '918E809E-EA7A-44B5-B230-776C42594D91,6F8DBB54-5159-4C22-9B0A-7842464360A5'
select Item from dbo.DelimitedSplit8K_nowhere(@temp, ',');
go
Item
-----------------------------------------
918E809E-EA7A-44B5-B230-776C42594D91
18E809E-EA7A-44B5-B230-776C42594D91
8E809E-EA7A-44B5-B230-776C42594D91
E809E-EA7A-44B5-B230-776C42594D91
809E-EA7A-44B5-B230-776C42594D91
09E-EA7A-44B5-B230-776C42594D91
9E-EA7A-44B5-B230-776C42594D91
E-EA7A-44B5-B230-776C42594D91
-EA7A-44B5-B230-776C42594D91
...
</code></pre>
<p>By now the explanation to the issue seen in the StackExchange question should be obvious: just as in my example, the WHERE clause was pulled out from the function and placed in the generated plan <i>above</i> the <tt>cast(Item as uniqueidentifier)</tt> expression. During execution SQL Server is asked to convert the string &#8217;18E809E-EA7A-44B5-B230-776C42594D91&#8242; to a uniqueidentifier and, understandably, it fails.</p>
<p>Do no make assumptions about order of executions when using inline table UDFs. Q.E.D.</p>
]]></content:encoded>
			<wfw:commentRss>http://rusanu.com/2011/08/10/t-sql-functions-do-no-imply-a-certain-order-of-execution/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Online Index Operations for indexes containing LOB columns</title>
		<link>http://rusanu.com/2011/08/05/online-index-operations-for-indexes-containing-lob-columns/</link>
		<comments>http://rusanu.com/2011/08/05/online-index-operations-for-indexes-containing-lob-columns/#comments</comments>
		<pubDate>Fri, 05 Aug 2011 21:26:44 +0000</pubDate>
		<dc:creator>remus</dc:creator>
				<category><![CDATA[Announcements]]></category>
		<category><![CDATA[CodeProject]]></category>
		<category><![CDATA[Denali]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[blob]]></category>
		<category><![CDATA[denali]]></category>
		<category><![CDATA[maintenance]]></category>
		<category><![CDATA[new features]]></category>
		<category><![CDATA[online index build]]></category>
		<category><![CDATA[sql server]]></category>

		<guid isPermaLink="false">http://rusanu.com/?p=1256</guid>
		<description><![CDATA[SQL Server supports online index and table rebuild operations which allow for maintenance operations to occur w/o significant downtime. While a table is being rebuild, or a new index is being built on it, the table is fully utilizable. It can be queried and any updates done to the table while the online rebuild operation [...]]]></description>
			<content:encoded><![CDATA[<p>SQL Server supports online index and table rebuild operations which allow for maintenance operations to occur w/o significant downtime. While a table is being rebuild, or a new index is being built on it, the table is fully utilizable. It can be queried and any updates done to the table while the online rebuild operation is occurring will be contained in the final rebuilt table. A detailed explanation on how these online rebuild operations work can be found in the <a href="http://technet.microsoft.com/en-us/library/cc966402.aspx" target="_blank">Online Indexing Operations in SQL Server 2005</a> white paper. But Online Index Build operations in SQL Server 2005, 2008 and 2008 R2 do not support tables that contain LOB columns, attempting to do so would trigger an error:</p>
<blockquote><p>
<span style="color:red">Msg 2725, Level 16, State 2, Line &#8230;<br />
An online operation cannot be performed for index &#8216;&#8230;&#8217; because the index contains column &#8216;&#8230;&#8217; of data type text, ntext, image, varchar(max), nvarchar(max), varbinary(max), xml, or large CLR type. For a non-clustered index, the column could be an include column of the index. For a clustered index, the column could be any column of the table. If DROP_EXISTING is used, the column could be part of a new or old index. The operation must be performed offline.</span>
</p></blockquote>
<p>To be accurate the restriction applies not to tables, but to any index or heap that contains an LOB column. That, of course, includes any clustered index or the base heap of a table if the table contains any LOB columns, but it would include any non-clustered index that includes a LOB column. In other words I can rebuild online non-clustered indexes of any table as long as they don&#8217;t use the INCLUDE clause to add a LOB column from the base table, but for sure I cannot rebuild online the table itself (meaning the clustered index or the heap). Nor can I add a clustered index to a base heap online, if the table contains LOB columns.</p>
<p>The question whether one should just use <tt>VARCHAR(MAX)</tt> and stop worrying about the chosen field size has came up on StackOverflow several times (<a href="http://stackoverflow.com/questions/2091284/varcharmax-everywhere" target="_blank">varchar(max) everywhere?</a>) and I always pointed out that there are at least some limitations (impossibility to do do online maintenance rebuild operations, impossibility to index such fields) and also all MAX types have a slight performance overhead, see <a href="http://rusanu.com/2010/03/22/performance-comparison-of-varcharmax-vs-varcharn/" target="_blank">Performance comparison of varchar(max) vs. varchar(N)</a>).</p>
<h2>Online Index Build, now with LOBs</h2>
<p>Starting with SQL Server 11 it is actually permitted to build (and rebuild) online indexes and heaps containing LOB columns. The old legacy types (<tt>text</tt>, <tt>ntext</tt> and <tt>image</tt>) are not supported, not surprising considering that these types are on the deprecation path.</p>
<p>To understand why the original online rebuild operations from previous versions did not support LOB columns we need to consider the SQL Server <a href="http://msdn.microsoft.com/en-us/library/ms189051.aspx" target="_blank">Table and Index Organization</a>. All indexes and tables consist of three allocation units: one for row data, one for overflow row data and one for LOB data.  We can see this if we inspect the <tt>sys.system_internals_allocation_units</tt> system catalog view:</p>
<pre>
<code>
create table test (id int not null identity(1,1),
	somevar1 varchar(6000),
	somevar2 varchar(6000),
	someblob varchar(max))
go

insert into test (somevar1, somevar2, someblob) values ('A', 'B', 'C');
insert into test (somevar1, somevar2, someblob) values
         (replicate('A', 6000), replicate('B', 6000), replicate('C', 8000))
go

select au.*
from sys.system_internals_allocation_units au
join sys.system_internals_partitions p on au.container_id = p.partition_id
where p.object_id = object_id('test');
go
</code>
</pre>
<p><a href="http://rusanu.com/wp-content/uploads/2011/08/oiblob-au.png"><img src="http://rusanu.com/wp-content/uploads/2011/08/oiblob-au.png" alt="" title="oiblob-au" width="600" height="77" class="aligncenter size-full wp-image-1263" /></a></p>
<p>Our test table shows three allocation units. Now lets rebuild our table and look again at our allocation units:</p>
<pre><code>
alter table test rebuild;
go

select au.*
from sys.system_internals_allocation_units au
join sys.system_internals_partitions p on au.container_id = p.partition_id
where p.object_id = object_id('test');
go
</code></pre>
<p><a href="http://rusanu.com/wp-content/uploads/2011/08/oiblob-au-offline.png"><img src="http://rusanu.com/wp-content/uploads/2011/08/oiblob-au-offline.png" alt="" title="oiblob-au-offline" width="600" height="75" class="aligncenter size-full wp-image-1266" /></a></p>
<p>We can see that our DATa and SLOB (aka. row overflow) allocation units have changed because they were rebuilt (they have different IDs and start at different pages). But the important thing is that the BLOB allocation unit has <b>not</b> changed. After the offline table rebuild, it has the same ID and starts at the same pages. This is because table and index rebuild operations do not rebuild the LOB data. They rebuild  the row data and the row-overflow data, but the newly built rows will simply point back to the same old LOB data. The idea is that tables with LOB columns have <i>large</i> LOB values and rebuilding the LOB data would be prohibitive, with little or no benefit.</p>
<p>Offline operations can avoid rebuilding the LOB data without problems, but for online index and table rebuilds this poses an issue: for the duration of the online rebuild operation both the old rowset (the old index/table) and the new rowset would point to the same LOB data <i>while updates are being made to rows</i>.</p>
<p>In SQL Server 11 this problem was solved and now online operations can rebuild indexes and tables with LOB columns while keeping the data in the LOB allocation unit in a consistent state. SQL Server will internally track how LOB data is referenced by both the old index and the new index being built and will take appropriate actions to manage the sharing of the LOB data.</p>
<p><a href="http://rusanu.com/wp-content/uploads/2011/08/oiblob-shared.png"><img src="http://rusanu.com/wp-content/uploads/2011/08/oiblob-shared.png" alt="" title="oiblob-shared" width="600" height="450" class="aligncenter size-full wp-image-1278" /></a></p>
<h2>Limitations</h2>
<p>The following restrictions and limitations apply <i>only for the duration of the Online Index Rebuild operation</i>:</p>
<dl>
<dt>Partial LOB <tt>.WRITE</tt> updates are transformed into full updates.</dt>
<dd>LOB data supports a highly efficient update mode, the <tt><a href="http://msdn.microsoft.com/en-us/library/ms177523.aspx" target="_blank" >.WRITE</a></tt> syntax. This is critical in creating streaming semantics, see  <a href="http://rusanu.com/2010/12/28/download-and-upload-images-from-sql-server-with-asp-net-mvc/">Download and Upload images from SQL Server via ASP.Net MVC</a>. When the <tt>.WRITE</tt> syntax is used on a LOB column belonging to an index that is being rebuilt online the generated plan will silently change it into a full value update, which generates significantly more log. If you rely heavily on this functionality be aware and schedule your online rebuilds accordingly.</dd>
<dt>DBCC CHECK operations will skip the consistency check of LOB allocation units belonging to indexes that are in the process of being rebuilt online.</dt>
<dd>During the online operation the LOB allocation unit is shared between the old index and the new index and is consistent if you consider <i>both</i> owners, however it may look inconsistent if considered from either one of the owner point of view.</dd>
<dt>File SHRINK operation will skip pages belonging to LOB allocation units  belonging to indexes that are in the process of being rebuilt online.</dt>
<dd>If LOB data is shrunk the pointers in the ROW data referencing the LOB data that had moved have to be updated. While an online index rebuild occurs there could be two sets of pointers referencing the same LOB data, one in the old rowset and one in the new rowset.</dd>
</dl>
]]></content:encoded>
			<wfw:commentRss>http://rusanu.com/2011/08/05/online-index-operations-for-indexes-containing-lob-columns/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to Multicast messages with SQL Server Service Broker</title>
		<link>http://rusanu.com/2011/07/20/how-to-multicast-messages-with-sql-server-service-broker/</link>
		<comments>http://rusanu.com/2011/07/20/how-to-multicast-messages-with-sql-server-service-broker/#comments</comments>
		<pubDate>Thu, 21 Jul 2011 01:40:39 +0000</pubDate>
		<dc:creator>remus</dc:creator>
				<category><![CDATA[CodeProject]]></category>
		<category><![CDATA[Denali]]></category>
		<category><![CDATA[Tutorials]]></category>

		<guid isPermaLink="false">http://rusanu.com/?p=1244</guid>
		<description><![CDATA[Starting with SQL Server 11 the the SEND verb has a new syntax and accepts multiple dialogs handles to send on: SEND ON CONVERSATION [(]conversation_handle [,.. @conversation_handle_n][)] [ MESSAGE TYPE message_type_name ] [ ( message_body_expression ) ] [ ; ] With this syntax enhancement you can send a message to multiple destinations. This is not [...]]]></description>
			<content:encoded><![CDATA[<p>Starting with SQL Server 11 the the <a href="http://msdn.microsoft.com/en-us/library/ms188407%28v=SQL.110%29.aspx"target="_blank"><tt>SEND</tt></a> verb has a new syntax and accepts multiple dialogs handles to send on:</p>
<pre>
SEND
   ON CONVERSATION [(]conversation_handle [,.. @conversation_handle_n][)]
   [ MESSAGE TYPE message_type_name ]
   [ ( message_body_expression ) ]
[ ; ]
</pre>
<p>With this syntax enhancement you can send a message to multiple destinations. This is not different from sending the same message multiple times. From the application point of view issuing one single SEND on 10 dialog handles is exactly the same as issuing 10 SEND statements on one dialog handle at a time. The improvement is in the <tt>sys.transmission_queue</tt>: issuing SEND multiple time would create multiple copies of the message body to be sent. By contrast the one single SEND on multiple handles will only store the message body once. We can see this if we look at the definition of sys.tranmission_queue in SQL Server 11:</p>
<pre>
sp_helptext 'sys.transmission_queue'

CREATE VIEW sys.transmission_queue AS
	SELECT conversation_handle = S.handle,
		to_service_name = Q.tosvc,
		to_broker_instance = Q.tobrkrinst,
		from_service_name = Q.fromsvc,
		service_contract_name = Q.svccontr,
		enqueue_time = Q.enqtime,
		message_sequence_number = Q.msgseqnum,
		message_type_name = Q.msgtype,
		is_conversation_error = sysconv(bit, Q.status &#038; 2),
		is_end_of_dialog = sysconv(bit, Q.status &#038; 4),
		<span style="background:yellow">message_body = ISNULL(Q.msgbody, B.msgbody)</span>,
		transmission_status = GET_TRANSMISSION_STATUS (S.handle),
		priority = R.priority
	FROM sys.sysxmitqueue Q
	<span style="background:yellow">LEFT JOIN sys.sysxmitbody B WITH (NOLOCK) ON Q.msgref = B.msgref</span>
	INNER JOIN sys.sysdesend S WITH (NOLOCK)
             ON Q.dlgid = S.diagid AND Q.finitiator = S.initiator
	INNER JOIN sys.sysdercv R WITH (NOLOCK)
             ON Q.dlgid = R.diagid AND Q.finitiator = R.initiator
	WHERE is_member('db_owner') = 1
</pre>
<p>Compare this with the same view definition in SQL Server 2008 R2:</p>
<pre>
CREATE VIEW sys.transmission_queue AS
	SELECT conversation_handle = S.handle,
		to_service_name = Q.tosvc,
		to_broker_instance = Q.tobrkrinst,
		from_service_name = Q.fromsvc,
		service_contract_name = Q.svccontr,
		enqueue_time = Q.enqtime,
		message_sequence_number = Q.msgseqnum,
		message_type_name = Q.msgtype,
		is_conversation_error = sysconv(bit, Q.status &#038; 2),
		is_end_of_dialog = sysconv(bit, Q.status &#038; 4),
		<span style="background:yellow">message_body = Q.msgbody</span>,
		transmission_status = GET_TRANSMISSION_STATUS (S.handle),
		priority = R.priority
	FROM sys.sysxmitqueue Q
	INNER JOIN sys.sysdesend S WITH (NOLOCK)
               ON Q.dlgid = S.diagid AND Q.finitiator = S.initiator
	INNER JOIN sys.sysdercv R WITH (NOLOCK)
               ON Q.dlgid = R.diagid AND Q.finitiator = R.initiator
	WHERE is_member('db_owner') = 1
</pre>
<p>You can see how in SQL Server 11 the message body was separated into a new system table (<tt>sys.sysxmitbody</tt>). Multicast SEND will create multiple entries in <tt>sys.sysxmitqueue</tt> (one for each dialog on which the message was multicasted) but only one entry in <tt>sys.sysxmitbody</tt>. Such a normalized storage scheme saves space consumed and, more importantly, amount of log generated during the SEND.</p>
<h2>The Reversed Dialog pattern in publish-subscribe</h2>
<p>The typical dialog pattern in pub-sub systems is for the subscriber to start the dialog and send an initial &#8216;subscribe&#8217; message, then the subscription content is being delivered from the target (the publisher) to the initiator (the subscriber). I call this the Reverse Dialog Pattern because messages flow from the target to the initiator. Lets show with an example. We&#8217;ll create a publisher service that broadcast some important content to which services can subscribe to receive it. To spice it up, we&#8217;ll use a tag system to subscribe to optional content: all content is distributed with a list of associated tags, all subscribers specify the tag they&#8217;re interested in. Tag matching is done using the <a href="http://msdn.microsoft.com/en-us/library/ms179859.aspx" target="_blank">LIKE</a> syntax, so that subscribers can specify <tt>'%'</tt> as a mean to subscribe to all content.</p>
<h3>The Publisher Service</h3>
<pre>
create message type subscription_request validation = none;
create message type subscription_content validation = well_formed_xml;

create contract distribution
	(subscription_request sent by initiator,
	subscription_content sent by target);

create queue publisher;
create service publisher on queue publisher (distribution);
go

create table subscriptions (
	subscription_id int not null identity(1,1),
	tag nvarchar(50) not null,
	conversation_handle uniqueidentifier not null,
	constraint pk_subscriptions primary key (subscription_id),
	constraint unq_conversation_handle unique (conversation_handle, tag));
go

create procedure usp_publisher_handler
as
begin
	declare @mt sysname, @dh uniqueidentifier, @mb varbinary(max);
	begin try
		begin transaction;
		receive top(1)
			@mt = message_type_name,
			@dh = conversation_handle,
			@mb = message_body
			from publisher;
		if (@mt = N'subscription_request')
		begin
			insert into subscriptions (conversation_handle, tag)
                                   values (@dh, cast(@mb as nvarchar(50)));
		end
		else if(@mt = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
			or @mt = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
		begin
			delete from subscriptions
				where conversation_handle  = @dh;
			end conversation @dh;
		end
		commit
	end try
	begin catch
		declare @xact_state int = xact_state();
		if @xact_state <> 0
		begin
			rollback;
		end
	end catch
end
go

alter queue publisher with activation (
	status = on,
	max_queue_readers = 1,
	procedure_name = usp_publisher_handler,
	execute as owner);
go
</pre>
<p>The publisher service is straight forward: it uses the <tt>subscriptions</tt> table to keep track of subscribers. The activated procedure associated with the publisher service processes the subscription_request messages and adds the request dialog to the subscriptions table. The request message body is the tag the subscriber is interested in.</p>
<h3>The Publish Content procedure</h3>
<pre>
create type publish_tags_type as table (
	tag nvarchar(50) not null primary key);
go

create procedure usp_publish_content
	@content xml,
	@tags publish_tags_type readonly
as
begin
	declare @sql nvarchar(max) = N'send on conversation (';
	declare @cnt int = 0;
	declare @dh uniqueidentifier;
	declare @comma nvarchar(2) = N'';

	declare crs cursor static read_only forward_only for
		select distinct conversation_handle
		from subscriptions s
		join @tags t on t.tag like s.tag;

	open crs;
	fetch next from crs into @dh;
	while 0 = @@fetch_status
	begin

		set @sql += @comma  + N'''' + cast(@dh as nvarchar(36)) + N'''';
		set @comma = N', ';
		set @cnt += 1;
		fetch next from crs into @dh;
	end
	close crs;
	deallocate crs;

	if @cnt > 0
	begin
		set @sql+= N') message type subscription_content (@content)';
		exec sp_executesql @sql, N'@content xml', @content;
	end
end
go
</pre>
<p>The publish content procedure takes a content to be distributed and the list of tags under which the content is distributed and sends the content to all interested subscribers. One single multicast SEND is used to reach all subscribers. Dynamic SQL is used to build the multicast SEND statement.</p>
<h3>Adding subscribers</h3>
<pre>
declare @i int = 0;
declare @sql nvarchar(max);
while @i < 10
begin
	set @sql = N'create queue subscriber_' + cast(@i as nvarchar(20)) + N';
		create service subscriber_' + cast(@i as nvarchar(20)) + N'
                            on queue subscriber_'+cast(@i as nvarchar(20)) + N';';
	exec sp_executesql @sql;
	set @sql = N'declare @dh uniqueidentifier;
		begin dialog @dh
			from service subscriber_' + cast(@i as nvarchar(20)) + N'
			to service N''publisher''
			on contract distribution
			with encryption = off;
		send on conversation @dh message type subscription_request
                    (''' +case @i%5 when 0 then N'%' else nchar(@i + 65) end + ''');';
	exec sp_executesql @sql;
	set @i += 1;
end
go
</pre>
<p>This snipped adds 10 subscribers interested in tags 'B', 'C', 'D' etc. The first and fifth subscribers are interested in everything ('%'). We can see the subscribers were added to the subscriptions table by the publisher activated procedure:</p>
<pre>
select * from subscriptions

subscription_id tag                               conversation_handle
--------------- ----------------- -------------------------------------
1               %                          AFC62EF2-35B3-E011-8EED-001C25160E57
2               B                          B3C62EF2-35B3-E011-8EED-001C25160E57
3               C                          B7C62EF2-35B3-E011-8EED-001C25160E57
4               D                          BBC62EF2-35B3-E011-8EED-001C25160E57
5               E                          BFC62EF2-35B3-E011-8EED-001C25160E57
6               %                          C3C62EF2-35B3-E011-8EED-001C25160E57
7               G                          C7C62EF2-35B3-E011-8EED-001C25160E57
8               H                          CBC62EF2-35B3-E011-8EED-001C25160E57
9               I                          CFC62EF2-35B3-E011-8EED-001C25160E57
10              J                          D3C62EF2-35B3-E011-8EED-001C25160E57
</pre>
<h3>A test multicast message</h3>
<pre>
declare @tags publish_tags_type;
insert into @tags (tag) values ('A'), ('B'), ('C');
exec usp_publish_content N'<Hello/>', @tags;
go
</pre>
<p>With this one call we notified all subscribers interested, with one single multicast SEND. We can check which of the subscribers got the content:</p>
<pre>
declare @i int = 0;
declare @sql nvarchar(max) = N'', @union nvarchar(20) = N'';
while @i < 10
begin
	set @sql += @union + N'select
              ''subscriber_' + cast(@i as nvarchar(20)) + N''' as subscriber,
               count(*) as count
               from subscriber_' + cast(@i as nvarchar(20));
	set @union = ' union all ';
	set @i += 1;
end
exec sp_executesql @sql;
go

subscriber   count
------------ -----------
subscriber_0 1
subscriber_1 1
subscriber_2 1
subscriber_3 0
subscriber_4 0
subscriber_5 1
subscriber_6 0
subscriber_7 0
subscriber_8 0
subscriber_9 0

(10 row(s) affected)
</pre>
<p>We can see that subscriber_2 and subscriber_3 each got a message since the tags they're interested are 'B' and 'C' which both match a tag set by the publisher. Subscribers 1 and 5 eahc got a message because they're interested in <i>any</i> tag.</p>
<p>This pattern of publish-subscribe is not new and similar applications could be built with SQL Server Service Broker in SQL Server 2005, 2008 and 2008R2. But with SQL Server 11 the distribution is more efficient and can scale and perform better as the message bodies are not inserted and deleted multiple times, once for each subscriber, in the publisher's transmission queue.</p>
]]></content:encoded>
			<wfw:commentRss>http://rusanu.com/2011/07/20/how-to-multicast-messages-with-sql-server-service-broker/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Online non-NULL with values column add in SQL Server 2012</title>
		<link>http://rusanu.com/2011/07/13/online-non-null-with-values-column-add-in-sql-server-11/</link>
		<comments>http://rusanu.com/2011/07/13/online-non-null-with-values-column-add-in-sql-server-11/#comments</comments>
		<pubDate>Thu, 14 Jul 2011 01:56:11 +0000</pubDate>
		<dc:creator>remus</dc:creator>
				<category><![CDATA[Announcements]]></category>
		<category><![CDATA[CodeProject]]></category>
		<category><![CDATA[Denali]]></category>
		<category><![CDATA[Samples]]></category>
		<category><![CDATA[Troubleshooting]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[alter table]]></category>
		<category><![CDATA[online]]></category>
		<category><![CDATA[sql]]></category>
		<category><![CDATA[sql server]]></category>

		<guid isPermaLink="false">http://rusanu.com/?p=1215</guid>
		<description><![CDATA[Prior to SQL Server 2012 when you add a new non-NULLable column with default values to an existing table a size-of data operation occurs: every row in the table is updated to add the default value of the new column. For small tables this is insignificant, but for large tables this can be so problematic [...]]]></description>
			<content:encoded><![CDATA[<p>Prior to SQL Server 2012 when you add a new non-NULLable column with default values to an existing table a size-of data operation occurs: every row in the table is updated to add the default value of the new column. For small tables this is insignificant, but for large tables this can be so problematic as to completely prohibit the operation. But starting with SQL Server 2012 the operation is, in most cases, instantaneous: only the table metadata is changed, no rows are being updated.</p>
<p>Lets look at a simple example, we&#8217;ll create a table with some rows and then add a non-NULL column with default values. First create and populate the table:</p>
<pre>
create table test (
	id int not null identity(1,1) primary key,
	someValue int not null);
go

set nocount on;
insert into test (someValue) values (rand()*1000);
go 1000
</pre>
<p>We can inspect the physical structure of the table&#8217;s records using DBCC PAGE. First lets find the page that contains the first record of the table:</p>
<pre>
select %%physloc%%, * from test where id = 1;
</pre>
<p>In my case this returned 0xD900000001000000, which means slot 0 on page 0xD9 (aka. 217) of file 1, and my test database has the DB_ID 6. Hence the parameters to DBCC PAGE</p>
<pre>
dbcc traceon (3604,-1)
dbcc page(6,1,217,3)

Page @0x0000000170D5E000

m_pageId = (1:217)                  m_headerVersion = 1                 m_type = 1
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x200
m_objId (AllocUnitId.idObj) = 84    m_indexId (AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594043432960
Metadata: PartitionId = 72057594039042048                                Metadata: IndexId = 1
Metadata: ObjectId = 245575913      m_prevPage = (0:0)                  m_nextPage = (1:220)
pminlen = 12                        m_slotCnt = 476                     m_freeCnt = 4
m_freeData = 7236                   m_reservedCnt = 0                   <span style="color:red">m_lsn = (30:71:25)</span>
m_xactReserved = 0                  m_xdesId = (0:0)                    m_ghostRecCnt = 0
m_tornBits = 2135435720             DB Frag ID = 1                      

Allocation Status

GAM (1:2) = ALLOCATED               SGAM (1:3) = ALLOCATED
PFS (1:1) = 0x60 MIXED_EXT ALLOCATED   0_PCT_FULL                        DIFF (1:6) = CHANGED
ML (1:7) = NOT MIN_LOGGED           

Slot 0 <span style="color:red">Offset 0x60 Length 15</span>

Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 15

Memory Dump @0x000000000AEBA060

0000000000000000:   <span style="color:red">10000c00 01000000 34020000 020000†††††††††††††........4......</span>

Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
id = 1
Slot 0 Column 2 Offset 0x8 Length 4 Length (physical) 4
someValue = 564
</pre>
<p>Note the last LSN that updated the page (30:71:25) and the size of the record in slot 0 (15 bytes). Now lets add a non-NULL column with default values:</p>
<pre>
alter table test add otherValue int not null default 42 with values;
</pre>
<p>We can select from the table and see that the table was changed and the rows have value 42 for the newly added column:</p>
<pre>
select top(2) * from test;

id          someValue   otherValue
----------- ----------- -----------
1           564         42
2           387         42
</pre>
<p>Yet if we inspect again the page, we can see that is unchanged:</p>
<pre>
dbcc traceon (3604,-1)
dbcc page(6,1,217,3)

Page @0x0000000170D5E000

m_pageId = (1:217)                  m_headerVersion = 1                 m_type = 1
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x200
m_objId (AllocUnitId.idObj) = 84    m_indexId (AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594043432960
Metadata: PartitionId = 72057594039042048                                Metadata: IndexId = 1
Metadata: ObjectId = 245575913      m_prevPage = (0:0)                  m_nextPage = (1:220)
pminlen = 12                        m_slotCnt = 476                     m_freeCnt = 4
m_freeData = 7236                   m_reservedCnt = 0                   <span style="color:red">m_lsn = (30:71:25)</span>
m_xactReserved = 0                  m_xdesId = (0:0)                    m_ghostRecCnt = 0
m_tornBits = 2135435720             DB Frag ID = 1                      

Allocation Status

GAM (1:2) = ALLOCATED               SGAM (1:3) = ALLOCATED
PFS (1:1) = 0x60 MIXED_EXT ALLOCATED   0_PCT_FULL                        DIFF (1:6) = CHANGED
ML (1:7) = NOT MIN_LOGGED           

Slot 0 <span style="color:red">Offset 0x60 Length 15</span>
Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 15

Memory Dump @0x000000000E83A060
0000000000000000:   <span style="color:red">10000c00 01000000 34020000 020000†††††††††††††........4......</span>

Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
id = 1                              

Slot 0 Column 2 Offset 0x8 Length 4 Length (physical) 4
someValue = 564                     

<span style="color:red">Slot 0 Column 3 Offset 0x0 Length 4 Length (physical) 0
otherValue = 42 </span>
</pre>
<p>The page header is unchanged, the last LSN is still (30:71:25), proof that the page was not modified, and the physical record is unchanged and has the same size as before. Yet DBCC shows a Column 3 and its value 42! If you pay attention you&#8217;ll notice that the Column 3 though has an Offset 0&#215;0 and a physical length of 0. Column 3 is somehow materialized out of thin air, as it does not physically exists in the record on this page. The &#8216;magic&#8217; is that the table metadata has changed and it now contains a column with a  &#8216;default&#8217; value:</p>
<pre>
select pc.* from sys.system_internals_partitions p
	join sys.system_internals_partition_columns pc on p.partition_id = pc.partition_id
	where p.object_id = object_id('test');
</pre>
<p><a href="http://rusanu.com/wp-content/uploads/2011/07/onlineschema.png"><img src="http://rusanu.com/wp-content/uploads/2011/07/onlineschema.png" alt="" title="onlineschema" width="400" class="aligncenter size-full wp-image-1219" /></a></p>
<p>Notice that <a href="http://msdn.microsoft.com/en-us/library/ms189600.aspx" target="_blank">sys.system_internals_partition_columns</a> now has two new columns that are SQL Server 2012 specific: <tt>has_default</tt> and <tt>default_value</tt>. The column we added to the <tt>test</tt> table (the third row in the image above) has a default with value 42. This is how SQL Server 2012 knows how to show a value for Column 3 for this record, even though is physically missing on the page. With this &#8216;magic&#8217; in place the ALTER TABLE will no longer have to update every row in the table and the operation is fast, metadata-only, no matter the number of rows in the table. This new behavior occurs automatically, no special syntax or setting is required, the engine will simply do the right thing. There is no penalty from having a missing value in a row. The &#8216;missing&#8217; value can be queried, updated, indexed, exactly as if the update during ALTER TABLE really occurred. There is no measurable performance penalty from having a default value.</p>
<p>What happens when we update a row? The &#8216;default&#8217; value is pushed into the row, even if the column was not modified. Consider this update:</p>
<pre>
update test set someValue = 565 where id = 1;
</pre>
<p>Although we did not touch the otherValue column, the row now was modified and it contains the materialized value:</p>
<pre>
dbcc page(6,1,217,3)

...
m_freeData = 7240                   m_reservedCnt = 0                   <span style="color:red">m_lsn = (31:271:2)</span>
...
Slot 0 <span style="color:red">Offset 0x1c35 Length 19</span>

Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 19

Memory Dump @0x000000000AB8BC35

0000000000000000:   <span style="color:red">10001000 01000000 35020000 <b>2a000000</b> 030000††††........5...*......</span>

Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
id = 1                              

Slot 0 Column 2 Offset 0x8 Length 4 Length (physical) 4
someValue = 565                     

<span style="color:red">Slot 0 Column 3 Offset 0xc Length 4 Length (physical) 4
otherValue = 42</span>         

KeyHashValue = (8194443284a0)
Slot 1 Offset 0x60 Length 15
Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 15

Memory Dump @0x000000000AB8A060
0000000000000000:   10000c00 02000000 83010000 020000†††††††††††††..............

Slot 1 Column 1 Offset 0x4 Length 4 Length (physical) 4
id = 2                              

Slot 1 Column 2 Offset 0x8 Length 4 Length (physical) 4
someValue = 387                     

<span style="color:blue">Slot 1 Column 3 Offset 0x0 Length 4 Length (physical) 0
otherValue = 42</span>
</pre>
<p>Notice how the physical record has increased in size (19 bytes vs. 15), the record has the value 42 in it (the hex 2a000000) and the Column 3 now has a real offset and physical size. So the update has trully materialized the default value in the row image. I intentionally copied the output of DBCC PAGE for the next slot in the page, to show that the record with id=2 was unaffected, it continues to have a smaller size of 15 bytes and Column 3 has no physical length.</p>
<h2>Default value vs. Default constraint</h2>
<p>Is worth saying that the new SQL Server 2012 default column value is not the same as the default value constraint. The default value is captured when the ALTER TABLE statement is run and can never change. Only rows existing in the table at the time of running ALTER TABLE statement will have missing &#8216;default&#8217; values. By contrast the default <b>constraint</b> can be dropped or modified and <i>new</i> rows inserted after the ALTER TABLE will  always have a value present in row for the new column. Any REBUILD operation on the table (or on the clustered index) will materialize all the missing values as the rows are being copied from the old hobt to the new hobt. The new hobt columns (<tt>sys.system_internals_partition_columns</tt>) will loose the <tt>has_default</tt> and <tt>default_value</tt> attributes, in effect loosing any trace that this column was added online. A default constraint by contrast will be preserved as a table is rebuilt.</p>
<h2>Restrictions</h2>
<p>Not all data types and default values can be added online. BLOB values like varchar(max), nvarchar(max), varbinary(max) and XML cannot be added online (and frankly I see no valid data model that has a non-NULL BLOB with a default&#8230;). Types that cannot be converted to sql_variant cannot be added online, like hierarchy_id, geometry and geography or user CLR based UDTs. Default expressions that require a different value for each row, like <tt>NEWID</tt> or <tt>NEWSEQUENTIALID</tt> cannot be added online (the default expression has to be a <i>runtime constant</i>, not to be confused with a deterministic expression, see <a href="http://blogs.msdn.com/b/conor_cunningham_msft/archive/2010/04/23/conor-vs-runtime-constant-functions.aspx" target="_blank">Conor vs. Runtime Constant Functions</a> for more details). In the case when the newly added column increases the maximum possible row size over the 8060 bytes limit the column cannot be added online. And is an Enterprise Edition only feature. For all the cases above the behavior will revert to adding the column &#8216;offline&#8217;, by updating every row in the table during the ALTER TABLE statement, creating a size-of-data update. When such a situation occurs a new XEvent is fired, which contains the reason why a size-of-data update occurred: <tt>alter_table_update_data</tt>.</p>
]]></content:encoded>
			<wfw:commentRss>http://rusanu.com/2011/07/13/online-non-null-with-values-column-add-in-sql-server-11/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

