<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://sqlblog.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Search results matching tags 'transaction log' and 'internals'</title><link>http://sqlblog.com/search/SearchResults.aspx?o=DateDescending&amp;tag=transaction+log,internals&amp;orTags=0</link><description>Search results matching tags 'transaction log' and 'internals'</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP2 (Build: 61129.1)</generator><item><title>Geek City: What Triggered This Post?</title><link>http://sqlblog.com/blogs/kalen_delaney/archive/2012/12/31/what-triggered-this-post.aspx</link><pubDate>Tue, 01 Jan 2013 00:00:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:46911</guid><dc:creator>Kalen Delaney</dc:creator><description>&lt;p&gt;I’d really like to get another post up onto my much neglected blog before the end of 2012. This will also start one of my New Year’s resolutions, which is to write at least one blog post a month. I’m going to tell you about a change in SQL Server that wasn’t announced in any “What’s New” list that I ever saw, perhaps because it was just a chance in internal behavior, and nothing that required any change in user applications. &lt;/p&gt;  &lt;p&gt;Do you retest what you know is true for every new version? When I update my books, I do test all the scripts, but if there isn’t a script, I don’t retest every ‘fact’ that I have known for years is true. And sometimes, things change. And sometimes my reviewers notice those unreported changes, and sometimes they don’t. &lt;/p&gt;  &lt;p&gt;You might be aware of the fact that SQL Server can perform UPDATE operations in two different ways. The UPDATE can be performed as a two-step process: delete the old row and then insert a whole new row, or, the UPDATE can be performed (much more efficiently) as an update-in-place.&amp;nbsp; When the two-step UPDATE is performed, it is a LOT more work. Not only does SQL Server have to log the entire old row and the entire new row, but each nonclustered index is also modified twice, and each of those index changes also has to be logged. So it’s nice when an update-in-place is done, because only the bytes changed are logged, and only indexes on the updated columns are affected. &lt;/p&gt;  &lt;p&gt;Prior to SQL Server 7, there were actually four different ways that UPDATE could be done. The two-step UPDATE had some variations that could make it even slower in some cases! But that was a long time ago, so I’m not going to go into the details now. But I will say that back then, in order to get an update-in-place to occur, there was a big long list of prerequisites that had to be met and if you missed just one, you’d get one of the slower UPDATE operations. &lt;/p&gt;  &lt;p&gt;As of SQL Server 7, update-in-place became the default. The only time it doesn’t happen is when the row can’t stay in the same location (such as when you update a clustered index key column) or when SQL Server really needs the old and new versions of the row. &lt;/p&gt;  &lt;p&gt;In SQL 7, one of the places that SQL needed the old and new version of the updates rows was when processing triggers. Triggers need the transaction log to get the contents for the DELETED and INSERTED pseudo-tables. And because triggers needed the entire old and new versions of the updated rows, the UPDATE was performed as a two-step operation. DELETE the old row, log the entire old row, and the INSERT the new row with the new values, and log the entire new row. &lt;/p&gt;  &lt;p&gt;But as of 2005, we now have the version store, primarily used for SNAPSHOT isolation, but available for other uses as well. In SNAPSHOT isolation, the version stores stores ‘old versions’ of rows that have been updated or deleted.&amp;nbsp; I knew that the version store was also used for triggers, but it only occurred to me just recently that maybe, because the old and new versions of the row were not needed from the log, perhaps UPDATEs did not always need to be performed internally as a two-step UPDATE. &lt;/p&gt;  &lt;p&gt;So I decided to test it out.&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Consolas"&gt;-- DEMO: If there is an UPDATE trigger, are updates logged as DELETE + INSERT?        &lt;br&gt;-- First build a new database.&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;USE master;        &lt;br&gt;GO         &lt;br&gt;IF (SELECT db_id('TestTrigger')) IS NOT NULL         &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP DATABASE TestTrigger;         &lt;br&gt;GO         &lt;br&gt;CREATE DATABASE TestTrigger;         &lt;br&gt;GO         &lt;br&gt;ALTER DATABASE TestTrigger SET RECOVERY SIMPLE;         &lt;br&gt;GO         &lt;br&gt;SELECT db_id('TestTrigger');         &lt;br&gt;GO &lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;USE TestTrigger;        &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Consolas"&gt;-- Just for a warmup, look at the function fn_dblog, which works in the current database&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;SELECT * FROM fn_dblog(null, null);        &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;-- Create a new table to work with        &lt;br&gt;IF (SELECT object_id('objects')) IS NOT NULL         &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP TABLE objects;         &lt;br&gt;GO         &lt;br&gt;SELECT TOP 100 * INTO objects FROM sys.objects;         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;-- Create a clustered index on the table        &lt;br&gt;CREATE CLUSTERED INDEX objects_clustered on objects(name);         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;-- First examine an update we know is NOT done in place,        &lt;br&gt;-- i.e. updating a clustered key value&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;UPDATE objects SET name = 'newrowsets' WHERE name = 'sysrowsets';        &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;-- Look at last 10 rows; notice a LOP_DELETE_ROWS and LOP_INSERT_ROWS        &lt;br&gt;-- The AllocUniteName column shows the object affected is the clustered index on dbo.objects         &lt;br&gt;SELECT Operation, [Transaction ID], AllocUnitName FROM fn_dblog(null, null);         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Consolas"&gt;-- Now examine an update we know is&amp;nbsp; done in place,        &lt;br&gt;-- i.e. updating an unindexed column on a table with no triggers         &lt;br&gt;UPDATE objects SET parent_object_id = 1 WHERE name = 'sysfiles1';         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;-- Look at last 3 rows; notice a LOP_MODIFY_ROW on the dbo.objects allocation unit        &lt;br&gt;SELECT Operation, [Transaction ID], AllocUnitName FROM fn_dblog(null, null);         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;-- Create an update trigger        &lt;br&gt;-- Will the update be done with the siple LOP_MODIFY_ROW or with the LOP_DELETE_ROWS and LOP_INSERT_ROWS         &lt;br&gt;CREATE TRIGGER trg_update_objects ON objects FOR UPDATE         &lt;br&gt;as         &lt;br&gt;SELECT * FROM DELETED; SELECT * FROM INSERTED;         &lt;br&gt;RETURN;         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;-- Now perform update again        &lt;br&gt;UPDATE objects SET parent_object_id = 10 WHERE name = 'sysfiles1';         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;    &lt;p&gt;&lt;font face="Consolas"&gt;-- Look at last 3 rows; notice a LOP_MODIFY_ROW        &lt;br&gt;SELECT * FROM fn_dblog(null, null);         &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Since the database is in SIMPLE recovery model, you can issue a CHECKPOINT before each UPDATE if you want to reduce the number of rows in the log to make it easier to examine. &lt;/p&gt;  &lt;p&gt;So it seems that I need to update my course and some of my writings. There might also be special cases that still require that an two-step UPDATE be performed in the presence of triggers, but it seems like a two-step UPDATE is not ALWAYS required anymore. That is very good news!&lt;/p&gt;  &lt;p&gt;I hope you all have a wonder-filled and joyous New Year!&lt;/p&gt;  &lt;p&gt;&lt;font color="#800040" size="4"&gt;~Kalen&lt;/font&gt;&lt;/p&gt;</description></item><item><title>Page splits, extended events, index page allocation and all the fish</title><link>http://sqlblog.com/blogs/michael_zilberstein/archive/2011/04/25/35175.aspx</link><pubDate>Mon, 25 Apr 2011 19:49:45 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:35175</guid><dc:creator>mz1313</dc:creator><description>&lt;p&gt;2 years ago I wrote about &lt;a title="Monitoring page splits with Extended Events" href="http://sqlblog.com/blogs/michael_zilberstein/archive/2009/02/05/11734.aspx"&gt;monitoring page splits with Extended Events&lt;/a&gt;. Only &lt;a title="Jonathan Kehayias" href="http://sqlblog.com/blogs/jonathan_kehayias/"&gt;2&lt;/a&gt; &lt;a title="Bob Beauchemin" href="http://www.sqlskills.com/BLOGS/BOBB/"&gt;bloggers&lt;/a&gt; explored Extended Events feature at that time, so my post was more of a learning than for any practical matter. Yet one question remained open: according to straightforward calculations, after 3 splits of the first page, next split should have occurred on another page - but trace with extended events clearly showed 4 splits of initial page.&lt;/p&gt;  &lt;p&gt;Recently Pavel Nefyodov has drawn my attention to another curious fact: initially we had 1 page; after all those splits we had 10. But trace showed 10 splits, not 9. So where did additional split come from? And finally I've realized that there is another simple way to trace those splits - via transaction log. If we manually checkpoint the system before and perform insert in transaction afer - our transaction log will containg exactly the relevant data; and it'll remain this way all the time transaction stays open. We'll query transaction log using undocumented but widely known fn_dblog function.&lt;/p&gt;  &lt;p&gt;OK, Let's see some code (mostly taken from my old post which in turn used page split demo from &lt;a title="Geek City: Splitting a page into multiple pages" href="http://sqlblog.com/blogs/kalen_delaney/archive/2008/12/20/splitting-a-page-into-multiple-pages.aspx"&gt;Kalen Delaney's blog&lt;/a&gt;). Initial configuration (including trace definition):&lt;/p&gt;  &lt;p&gt;&lt;code style="font-size:12px;"&gt;&lt;span style="color:blue;"&gt;USE &lt;/span&gt;&lt;span style="color:black;"&gt;tempdb&lt;/span&gt;&lt;span style="color:gray;"&gt;;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO        &lt;br /&gt;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;CREATE TABLE &lt;/span&gt;&lt;span style="color:black;"&gt;split_page&amp;#160;&amp;#160; &lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:black;"&gt;id &lt;/span&gt;&lt;span style="color:blue;"&gt;INT &lt;/span&gt;&lt;span style="color:#434343;"&gt;IDENTITY&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:black;"&gt;0&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt;&lt;span style="color:black;"&gt;2&lt;/span&gt;&lt;span style="color:gray;"&gt;) &lt;/span&gt;&lt;span style="color:blue;"&gt;PRIMARY KEY&lt;/span&gt;&lt;span style="color:gray;"&gt;,        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;id2 bigint &lt;/span&gt;&lt;span style="color:blue;"&gt;DEFAULT &lt;/span&gt;&lt;span style="color:black;"&gt;0&lt;/span&gt;&lt;span style="color:gray;"&gt;,        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;data1 &lt;/span&gt;&lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:black;"&gt;33&lt;/span&gt;&lt;span style="color:gray;"&gt;) NULL,&amp;#160;&amp;#160; &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;data2 &lt;/span&gt;&lt;span style="color:blue;"&gt;VARCHAR&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:black;"&gt;8000&lt;/span&gt;&lt;span style="color:gray;"&gt;) NULL);        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO        &lt;br /&gt;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;INSERT INTO &lt;/span&gt;&lt;span style="color:black;"&gt;split_page &lt;/span&gt;&lt;span style="color:blue;"&gt;DEFAULT VALUES&lt;/span&gt;&lt;span style="color:gray;"&gt;;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO 385        &lt;br /&gt;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;CREATE &lt;/span&gt;&lt;span style="color:black;"&gt;EVENT SESSION MonitorPageSplits &lt;/span&gt;&lt;span style="color:blue;"&gt;ON &lt;/span&gt;&lt;span style="color:black;"&gt;SERVER        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;ADD &lt;/span&gt;&lt;span style="color:black;"&gt;EVENT sqlserver.page_split        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;(        &lt;br /&gt;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:black;"&gt;ACTION &lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:black;"&gt;sqlserver.database_id&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;sqlserver.sql_text&lt;/span&gt;&lt;span style="color:gray;"&gt;)&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:blue;"&gt;WHERE &lt;/span&gt;&lt;span style="color:black;"&gt;sqlserver.database_id &lt;/span&gt;&lt;span style="color:blue;"&gt;= &lt;/span&gt;&lt;span style="color:black;"&gt;2        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;)        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;ADD &lt;/span&gt;&lt;span style="color:black;"&gt;TARGET package0.asynchronous_file_target        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;(        &lt;br /&gt;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:blue;"&gt;SET&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:black;"&gt;filename &lt;/span&gt;&lt;span style="color:blue;"&gt;= &lt;/span&gt;&lt;span style="color:red;"&gt;N'c:\temp\MonitorPageSplits.etx'&lt;/span&gt;&lt;span style="color:gray;"&gt;,&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:black;"&gt;metadatafile &lt;/span&gt;&lt;span style="color:blue;"&gt;= &lt;/span&gt;&lt;span style="color:red;"&gt;N'c:\temp\MonitorPageSplits.mta'        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:gray;"&gt;);        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO        &lt;br /&gt;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;CHECKPOINT        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO        &lt;br /&gt;        &lt;br /&gt;&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Short glance at our table's allocation inside data file:&lt;/p&gt;  &lt;p&gt;&lt;code style="font-size:12px;"&gt;&lt;span style="color:blue;"&gt;DBCC &lt;/span&gt;&lt;span style="color:black;"&gt;IND&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:black;"&gt;tempdb&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;split_page&lt;/span&gt;&lt;span style="color:gray;"&gt;, -&lt;/span&gt;&lt;span style="color:black;"&gt;1&lt;/span&gt;&lt;span style="color:gray;"&gt;);        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;   &lt;table cellspacing="0" cellpadding="2"&gt;       &lt;tr&gt;         &lt;td align="center"&gt;&lt;b&gt;PagePID&lt;/b&gt;&lt;/td&gt;          &lt;td align="center"&gt;&lt;b&gt;IndexID&lt;/b&gt;&lt;/td&gt;          &lt;td align="center"&gt;&lt;b&gt;iam_chain_type&lt;/b&gt;&lt;/td&gt;          &lt;td align="center"&gt;&lt;b&gt;PageType&lt;/b&gt;&lt;/td&gt;          &lt;td align="center"&gt;&lt;b&gt;IndexLevel&lt;/b&gt;&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td&gt;175&lt;/td&gt;          &lt;td&gt;1&lt;/td&gt;          &lt;td&gt;In-row data&lt;/td&gt;          &lt;td&gt;10&lt;/td&gt;          &lt;td&gt;NULL&lt;/td&gt;       &lt;/tr&gt;        &lt;tr&gt;         &lt;td&gt;148&lt;/td&gt;          &lt;td&gt;1&lt;/td&gt;          &lt;td&gt;In-row data&lt;/td&gt;          &lt;td&gt;1&lt;/td&gt;          &lt;td&gt;0&lt;/td&gt;       &lt;/tr&gt;     &lt;/table&gt; &lt;/p&gt;  &lt;p&gt;So we have 1 IAM page and 1 data page. Now the split itself and - remember, we should query transaction log before committing transaction if we don't want accidental checkpoint to ruine all our efforts.&lt;/p&gt;  &lt;p&gt;&lt;code style="font-size:12px;"&gt;&lt;span style="color:blue;"&gt;ALTER &lt;/span&gt;&lt;span style="color:black;"&gt;EVENT SESSION MonitorPageSplits &lt;/span&gt;&lt;span style="color:blue;"&gt;ON &lt;/span&gt;&lt;span style="color:black;"&gt;SERVER STATE &lt;/span&gt;&lt;span style="color:blue;"&gt;= &lt;/span&gt;&lt;span style="color:black;"&gt;start&lt;/span&gt;&lt;span style="color:gray;"&gt;;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO        &lt;br /&gt;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;SET IDENTITY_INSERT &lt;/span&gt;&lt;span style="color:black;"&gt;split_page&amp;#160; &lt;/span&gt;&lt;span style="color:blue;"&gt;ON&lt;/span&gt;&lt;span style="color:gray;"&gt;;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;BEGIN TRAN        &lt;br /&gt;        &lt;br /&gt;INSERT INTO &lt;/span&gt;&lt;span style="color:black;"&gt;split_page &lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:black;"&gt;id&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;id2&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;data1&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;data2&lt;/span&gt;&lt;span style="color:gray;"&gt;)        &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:blue;"&gt;SELECT &lt;/span&gt;&lt;span style="color:black;"&gt;111&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;0&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:magenta;"&gt;REPLICATE&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:red;"&gt;'a'&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;33&lt;/span&gt;&lt;span style="color:gray;"&gt;), &lt;/span&gt;&lt;span style="color:magenta;"&gt;REPLICATE&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:red;"&gt;'b'&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;8000&lt;/span&gt;&lt;span style="color:gray;"&gt;);        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO        &lt;br /&gt;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;SET IDENTITY_INSERT &lt;/span&gt;&lt;span style="color:black;"&gt;split_page&amp;#160; &lt;/span&gt;&lt;span style="color:blue;"&gt;OFF&lt;/span&gt;&lt;span style="color:gray;"&gt;;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO        &lt;br /&gt;        &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;SELECT &lt;/span&gt;&lt;span style="color:black;"&gt;[Current LSN]&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;Operation&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;Context&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;span style="color:black;"&gt;[Page ID]&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:black;"&gt;[Description]          &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;FROM &lt;/span&gt;&lt;span style="color:darkred;"&gt;fn_dblog&lt;/span&gt;&lt;span style="color:gray;"&gt;(NULL, NULL)          &lt;br /&gt;          &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;COMMIT          &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO          &lt;br /&gt;          &lt;br /&gt;ALTER &lt;/span&gt;&lt;span style="color:black;"&gt;EVENT SESSION MonitorPageSplits &lt;/span&gt;&lt;span style="color:blue;"&gt;ON &lt;/span&gt;&lt;span style="color:black;"&gt;SERVER STATE &lt;/span&gt;&lt;span style="color:blue;"&gt;= &lt;/span&gt;&lt;span style="color:black;"&gt;STOP&lt;/span&gt;&lt;span style="color:gray;"&gt;;          &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO          &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;DROP &lt;/span&gt;&lt;span style="color:black;"&gt;EVENT SESSION MonitorPageSplits &lt;/span&gt;&lt;span style="color:blue;"&gt;ON &lt;/span&gt;&lt;span style="color:black;"&gt;SERVER&lt;/span&gt;&lt;span style="color:gray;"&gt;;          &lt;br /&gt;&lt;/span&gt;&lt;span style="color:black;"&gt;GO          &lt;br /&gt;          &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;SELECT&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:black;"&gt;split.value&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:red;"&gt;'(/event/data[@name=''file_id'']/value)[1]'&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt;&lt;span style="color:red;"&gt;'int'&lt;/span&gt;&lt;span style="color:gray;"&gt;) &lt;/span&gt;&lt;span style="color:blue;"&gt;AS &lt;/span&gt;&lt;span style="color:black;"&gt;[file_id]&lt;/span&gt;&lt;span style="color:gray;"&gt;,          &lt;br /&gt;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:black;"&gt;split.value&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:red;"&gt;'(/event/data[@name=''page_id'']/value)[1]'&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt;&lt;span style="color:red;"&gt;'int'&lt;/span&gt;&lt;span style="color:gray;"&gt;) &lt;/span&gt;&lt;span style="color:blue;"&gt;AS &lt;/span&gt;&lt;span style="color:black;"&gt;[page_id]&lt;/span&gt;&lt;span style="color:gray;"&gt;,          &lt;br /&gt;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:black;"&gt;split.value&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:red;"&gt;'(/event[@name=''page_split'']/@timestamp)[1]'&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt;&lt;span style="color:red;"&gt;'datetime'&lt;/span&gt;&lt;span style="color:gray;"&gt;) &lt;/span&gt;&lt;span style="color:blue;"&gt;AS &lt;/span&gt;&lt;span style="color:black;"&gt;[event_time]&lt;/span&gt;&lt;span style="color:gray;"&gt;,          &lt;br /&gt;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:black;"&gt;split.value&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:red;"&gt;'(/event/action[@name=''sql_text'']/value)[1]'&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt;&lt;span style="color:red;"&gt;'varchar(max)'&lt;/span&gt;&lt;span style="color:gray;"&gt;) &lt;/span&gt;&lt;span style="color:blue;"&gt;AS &lt;/span&gt;&lt;span style="color:black;"&gt;[sql_text]          &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;FROM          &lt;br /&gt;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:gray;"&gt;(&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:blue;"&gt;SELECT &lt;/span&gt;&lt;span style="color:magenta;"&gt;CAST&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:black;"&gt;event_data &lt;/span&gt;&lt;span style="color:blue;"&gt;AS XML&lt;/span&gt;&lt;span style="color:gray;"&gt;) &lt;/span&gt;&lt;span style="color:blue;"&gt;AS &lt;/span&gt;&lt;span style="color:black;"&gt;split          &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;/span&gt;&lt;span style="color:blue;"&gt;FROM &lt;/span&gt;&lt;span style="color:black;"&gt;sys.&lt;/span&gt;&lt;span style="color:darkred;"&gt;fn_xe_file_target_read_file&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:red;"&gt;'c:\temp\MonitorPageSplits*.etx'&lt;/span&gt;&lt;span style="color:gray;"&gt;, &lt;/span&gt;&lt;span style="color:red;"&gt;'c:\temp\MonitorPageSplits*.mta'&lt;/span&gt;&lt;span style="color:gray;"&gt;, NULL, NULL)          &lt;br /&gt;&amp;#160;&amp;#160; ) &lt;/span&gt;&lt;span style="color:blue;"&gt;AS &lt;/span&gt;&lt;span style="color:black;"&gt;t          &lt;br /&gt;&lt;/span&gt;&lt;span style="color:blue;"&gt;ORDER BY &lt;/span&gt;&lt;span style="color:black;"&gt;[event_time]          &lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;    &lt;p&gt;&lt;/span&gt;First let's take a look at our table now:&lt;/p&gt;    &lt;p&gt;     &lt;table cellspacing="0" cellpadding="2"&gt;         &lt;tr&gt;           &lt;td align="center"&gt;&lt;b&gt;PagePID&lt;/b&gt;&lt;/td&gt;            &lt;td align="center"&gt;&lt;b&gt;IndexID&lt;/b&gt;&lt;/td&gt;            &lt;td align="center"&gt;&lt;b&gt;iam_chain_type&lt;/b&gt;&lt;/td&gt;            &lt;td align="center"&gt;&lt;b&gt;PageType&lt;/b&gt;&lt;/td&gt;            &lt;td align="center"&gt;&lt;b&gt;IndexLevel&lt;/b&gt;&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;175&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;10&lt;/td&gt;            &lt;td&gt;NULL&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;148&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;0&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;184&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;2&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;185&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;0&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;187&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;0&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;188&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;0&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;189&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;0&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;190&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;0&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;191&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;0&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;392&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;0&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;393&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;0&lt;/td&gt;         &lt;/tr&gt;          &lt;tr&gt;           &lt;td&gt;394&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;In-row data&lt;/td&gt;            &lt;td&gt;1&lt;/td&gt;            &lt;td&gt;0&lt;/td&gt;         &lt;/tr&gt;       &lt;/table&gt;   &lt;/p&gt;    &lt;p&gt;Transaction log contains more rows than we are interested in, so I cleaned irrelevant ones out from the table below and correlated transaction log with extended events trace. I also transformed hexadecimal page numbers from transaction log to decimal ones - like we see in extended events trace.&lt;/p&gt;    &lt;p&gt;&lt;a href="http://sqlblog.com/blogs/michael_zilberstein/WindowsLiveWriter/Pagesplitsextendedeventsindexpageallocat_12E53/image_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="image" src="http://sqlblog.com/blogs/michael_zilberstein/WindowsLiveWriter/Pagesplitsextendedeventsindexpageallocat_12E53/image_thumb.png" width="770" height="355" /&gt;&lt;/a&gt; &lt;/p&gt;    &lt;p&gt;So we can see that indeed there have been 3 splits of the first page. From transaction log we see that first action was actually allocating of page 184 which is - look at the allocation table above - index page. According to transaction log this page is allocated - not splitted from any other page. Still extended events trace shows as if it has been splitted from the first data page of our table. Don't know whether this behavior can be considered as bug - probably not. Anyway - both unanswered question from my old post are resolved: first page indeed has been splitted 3 times and we had only 9 data page splits while 10th split belongs to index page allocation.&lt;/p&gt;&lt;p&gt;Have a nice week!&lt;/p&gt;</description></item><item><title>Geek City: What gets logged for SELECT INTO operations?</title><link>http://sqlblog.com/blogs/kalen_delaney/archive/2011/03/15/what-gets-logged-for-select-into.aspx</link><pubDate>Tue, 15 Mar 2011 19:41:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:34163</guid><dc:creator>Kalen Delaney</dc:creator><description>&lt;p&gt;Last week, I wrote about &lt;a href="http://sqlblog.com/blogs/kalen_delaney/archive/2011/03/08/what-gets-logged-for-index-rebuilds.aspx"&gt;logging for index rebuild operations.&lt;/a&gt;&amp;nbsp; I wanted to publish the result of that testing as soon as I could, because that dealt with a specific question I was trying to answer. However, I actually started out my testing by looking at the logging that was done for a different operation, and ended up generating some new questions for myself. &lt;/p&gt;
&lt;p&gt;Before I started testing the index rebuilds, I thought I would just get warmed up by observing the logging for SELECT INTO. I thought I knew what got logged, but I was wrong. My understanding was that as a minimally logged operation, in FULL recovery, SELECT INTO would log each 8K page as it was written, not each row like a normal INSERT would. In BULK_LOGGED, SELECT INTO would create a log record for each page, but it would not log the entire 8K page. And of course, it would also not log every row.&amp;nbsp; However, my first test turned up some surprising results.&lt;/p&gt;
&lt;p&gt;Here's what I did: First, I made sure the &lt;em&gt;AdventureWorks&lt;/em&gt; database was in FULL recovery, and dropped the &lt;em&gt;Details&lt;/em&gt; table if it existed. Then I created&amp;nbsp; a table called &lt;em&gt;SELECTINTOLoggingData&lt;/em&gt; to hold the results of calling &lt;em&gt;fn_dblog&lt;/em&gt;.&amp;nbsp; As in last week's script, the following information was captured:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Max log record size &lt;/li&gt;
&lt;li&gt;Sum of log record sizes &lt;/li&gt;
&lt;li&gt;Number of log records &lt;/li&gt;
&lt;li&gt;Number of log records larger than 8K &lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Finally, I backed up the &lt;em&gt;AdventureWorks&lt;/em&gt; database to give me a foundation for log backups. So here's the first part of the script:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font face="Consolas"&gt;ALTER DATABASE AdventureWorks SET RECOVERY FULL &lt;br&gt;GO &lt;br&gt;USE AdventureWorks; &lt;br&gt;GO &lt;br&gt;IF EXISTS (SELECT 1 FROM sys.tables&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE name = 'Details') &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP TABLE Details; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br&gt;IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'SELECTINTOLoggingData') &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP TABLE SELECTINTOLoggingData; &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Consolas"&gt;CREATE TABLE SELECTINTOLoggingData &lt;br&gt;(DBRecModel CHAR(12), MaxLogRowSize INT, NumLogRows INT, NumLargeRows INT); &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Consolas"&gt;BACKUP DATABASE AdventureWorks TO DISK = 'c:\backups\AW.bak' WITH INIT; &lt;br&gt;GO &lt;br&gt;&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The actual testing was be done first in FULL recovery, then in BULK_LOGGED. I backed up up the transaction log, then created the &lt;em&gt;Details&lt;/em&gt; table by executing a SELECT INTO from the &lt;em&gt;Sales.SalesOrderDetail&lt;/em&gt; table.&amp;nbsp; The &lt;em&gt;Details&lt;/em&gt; table has&amp;nbsp; 1495 pages and 121,317 rows. Finally, I inserted the relevant values from &lt;em&gt;fn_dblog&lt;/em&gt;&amp;nbsp; into the &lt;em&gt;SELECTINTOLoggingData &lt;/em&gt;table.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;----&amp;nbsp; &lt;font face="Consolas"&gt;FULL Recovery&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;font face="Consolas"&gt;BACKUP LOG AdventureWorks TO DISK = 'c:\backups\AW_log.bak' WITH INIT; &lt;br&gt;SELECT COUNT(*) FROM fn_dblog(null, null); &lt;br&gt;GO &lt;br&gt;SELECT * INTO Details &lt;br&gt;FROM Sales.SalesOrderDetail; &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;font face="Consolas"&gt;INSERT INTO SELECTINTOLoggingData &lt;br&gt;SELECT 'FULL', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MAX([Log Record Length]), &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; COUNT(*), &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (SELECT COUNT(*) FROM fn_dblog(null, null) WHERE [Log Record Length]&amp;nbsp; &amp;gt;8000) &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM fn_dblog(null, null); &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Consolas"&gt;DROP TABLE Details; &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Consolas"&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;font face="Consolas"&gt;-- BULK_LOGGED Recovery&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;font face="Consolas"&gt;ALTER DATABASE AdventureWorks SET RECOVERY BULK_LOGGED; &lt;br&gt;BACKUP LOG AdventureWorks TO DISK = 'c:\backups\AW_log.bak' WITH INIT; &lt;br&gt;SELECT COUNT(*)&amp;nbsp; FROM fn_dblog(null, null); &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Consolas"&gt;SELECT * INTO Details &lt;br&gt;FROM Sales.SalesOrderDetail; &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;font face="Consolas"&gt;INSERT INTO SELECTINTOLoggingData &lt;br&gt;SELECT 'BULK_LOGGED', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MAX([Log Record Length]), &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; COUNT(*), &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (SELECT COUNT(*) FROM fn_dblog(null, null) WHERE [Log Record Length]&amp;nbsp; &amp;gt;8000) &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM fn_dblog(null, null); &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Finally, I looked at the data I collected:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font face="Consolas"&gt;SELECT * FROM SELECTINTOLoggingData; &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Here are my results:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/kalen_delaney/image_6D32B977.png"&gt;&lt;img style="border-width:0px;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/kalen_delaney/image_thumb_2A073B4A.png" width="532" height="72"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Although the number of large (8K) log rows was as expected, I was quite surprised to see the total number of log records! (And yes, I know the column names use the term "Log Rows", but I'm calling them "Log Records".&amp;nbsp; I guess I just wanted a shorter column name.)&lt;/p&gt;
&lt;p&gt;I then ran the same test on a different version of the &lt;em&gt;Sales.SalesOrderDetail&lt;/em&gt; table that I use in some of my class examples. Here is the script to create the &lt;em&gt;Sales.SalesOrderDetail2&lt;/em&gt; table&lt;em&gt;:&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font face="Consolas"&gt;CREATE TABLE Sales.SalesOrderDetail2 ( &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [SalesOrderID] [int] NOT NULL, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [SalesOrderDetailID] [int] NOT NULL, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [CarrierTrackingNumber] [nvarchar](25) NULL, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [OrderQty] [smallint] NOT NULL, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [ProductID] [int] NOT NULL, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [SpecialOfferID] [int] NOT NULL, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [UnitPrice] [money] NOT NULL, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [UnitPriceDiscount] [money] NOT NULL , &lt;br&gt;CONSTRAINT [PK_SalesOrderDetail2_SalesOrderID_SalesOrderDetailID] PRIMARY KEY CLUSTERED &lt;br&gt;( &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [SalesOrderID] ASC, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; [SalesOrderDetailID] ASC &lt;br&gt;)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY] &lt;br&gt;) ON [PRIMARY]; &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Consolas"&gt;INSERT INTO Sales.SalesOrderDetail2 SELECT [SalesOrderID] &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ,[SalesOrderDetailID] &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ,[CarrierTrackingNumber] &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ,[OrderQty] &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ,[ProductID] &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ,[SpecialOfferID] &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ,[UnitPrice] &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ,[UnitPriceDiscount] &lt;br&gt;&amp;nbsp; FROM AdventureWorks.Sales.SalesOrderDetail; &lt;br&gt;GO&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;After setting the &lt;em&gt;AdventureWorks&lt;/em&gt; database back to FULL recovery, deleting the &lt;em&gt;Details&lt;/em&gt; table and truncating the &lt;em&gt;SELECTINTOLoggingData&lt;/em&gt; table,&amp;nbsp; I run the same test as above (starting at the "Full Recovery" comment, substituting &lt;em&gt;Sales.SalesOrderDetail2&lt;/em&gt; for &lt;em&gt;Sales.SalesOrderDetail&lt;/em&gt;. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font face="Consolas"&gt;ALTER DATABASE AdventureWorks SET RECOVERY FULL; &lt;br&gt;GO &lt;br&gt;DROP TABLE Details; &lt;br&gt;GO &lt;br&gt;TRUNCATE TABLE SELECTINTOLoggingData&lt;/font&gt;&lt;em&gt;&lt;font face="Consolas"&gt;; &lt;br&gt;GO&lt;/font&gt;&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Here are my new results:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/kalen_delaney/image_0913C8A3.png"&gt;&lt;img style="border-width:0px;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://sqlblog.com/blogs/kalen_delaney/image_thumb_28566F76.png" width="547" height="79"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So these are much more like the results I expected from a SELECT INTO operation! &lt;/p&gt;
&lt;p&gt;Why did the first example, selecting from &lt;em&gt;Sales.SalesOrderDetail&lt;/em&gt;, do so much more logging?&lt;/p&gt;
&lt;p&gt;It turns out that if you have a &lt;strong&gt;IDENTITY&lt;/strong&gt; column in the source table, SELECT INTO must then log every row as the IDENTITY value is generated. &lt;/p&gt;
&lt;p&gt;Does this mean you should rethink the beneift of using IDENTITY columns? Only if you're doing a lot of SELECT INTO operations to copy a table with IDENTITY columns. Normal INSERT statements, which is how IDENTITY values are usually generated, are going to have the same amount of logging whether or not the table has an IDENTITY column.&amp;nbsp; (Let me know if you think otherwise.)&lt;/p&gt;
&lt;p&gt;So now you know. &lt;/p&gt;
&lt;p&gt;&lt;font color="#ff00ff" size="4"&gt;~Kalen&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;p.s. I would like to thank Sunil Agarwal for his brilliant insights and encouragement. Thanks for everything, Sunil!&lt;/p&gt;</description></item><item><title>Geek City: What gets logged for index rebuild operations?</title><link>http://sqlblog.com/blogs/kalen_delaney/archive/2011/03/08/what-gets-logged-for-index-rebuilds.aspx</link><pubDate>Tue, 08 Mar 2011 18:54:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:33967</guid><dc:creator>Kalen Delaney</dc:creator><description>&lt;P&gt;This blog post was inspired by a question from a future student. Someone who was already booked for my &lt;A href="http://sqlserverinternals.com/classes.html"&gt;SQL Server Internals class&lt;/A&gt; in June asked for some information on a current problem he was having with transaction log writes causing excessive wait times during index rebuild operations when run in ONLINE mode. He wanted to know if switching to BULK_LOGGED recovery could help. &lt;/P&gt;
&lt;P&gt;I knew the difference between ALTER INDEX in FULL vs BULK_LOGGED recovery when doing normal OFFLINE rebuilds, but I wasn't sure about ONLINE. So I dug out some old scripts, massaged them a bit, and ran some tests.&lt;/P&gt;
&lt;P&gt;A minimally logged operation is one that does not always log every single row. In FULL mode, SQL Server will log the entire page as each page is filled, so that we end up with individual&amp;nbsp; log records of over 8K. In BULK_LOGGED mode, SQL Server just logs the fact that a new page was generated (and some information about the page) and doesn't log the full 8K. So the log space used is quite a bit less. &lt;/P&gt;
&lt;P&gt;I will use Microsoft's &lt;EM&gt;AdventureWorks&lt;/EM&gt; database that you can download from &lt;A href="http://msftdbprodsamples.codeplex.com/releases/view/4004"&gt;codeplex&lt;/A&gt;. I used the SQL Server 2005 version.&lt;/P&gt;
&lt;P&gt;After making sure the database is set to FULL recovery, I make a copy of the &lt;EM&gt;Sales.SalesOrderDetail&lt;/EM&gt; table (which I call &lt;EM&gt;Details&lt;/EM&gt;) and build a clustered index on it.&amp;nbsp; The table has about 1580 pages and 121,317 rows.&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT face=Consolas&gt;ALTER DATABASE AdventureWorks SET RECOVERY FULL &lt;BR&gt;GO &lt;BR&gt;USE AdventureWorks; &lt;BR&gt;GO &lt;BR&gt;IF EXISTS (SELECT 1 FROM sys.tables&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE name = 'Details') &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP TABLE Details; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp; &lt;BR&gt;SELECT * INTO Details &lt;BR&gt;FROM Sales.SalesOrderDetail; &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face=Consolas&gt;CREATE CLUSTERED INDEX CL_details_indx ON Details(SalesOrderID); &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;I then build a table to store log information that I will gather after the different index rebuild operations. After that I back up the database to make sure I have a base for making log backups.&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'AlterIndexLoggingData') &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP TABLE AlterIndexLoggingData; &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;CREATE TABLE AlterIndexLoggingData &lt;BR&gt;(DBRecModel CHAR(12), IndexBuildMode CHAR(3), MaxLogRowSize INT, SumLogSize BIGINT, NumLogRows INT, NumLargeRows INT); &lt;BR&gt;GO &lt;BR&gt;BACKUP DATABASE AdventureWorks TO DISK = 'c:\backups\AW.bak' WITH INIT; &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Now starts the testing. There are four sections, each of which basically does the same thing, with the four permutations of FULL vs BULK_LOGGED recovery, and ONLINE vs OFFLINE index builds.&amp;nbsp; Each section backs up the transaction log to truncate it, and then reports the number of rows in the log, just to verify we really have (logically) cleared it out. It then performs a rebuild of the index on the &lt;EM&gt;Details&lt;/EM&gt; table. Finally, it captures the following information from the log using &lt;EM&gt;fn_dblog&lt;/EM&gt;:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Max log record size &lt;/LI&gt;
&lt;LI&gt;Sum of log record sizes &lt;/LI&gt;
&lt;LI&gt;Number of log records &lt;/LI&gt;
&lt;LI&gt;Number of log records larger than 8K &lt;/LI&gt;&lt;/UL&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;----&amp;nbsp; FULL Recovery, ONLINE = OFF&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;BACKUP LOG AdventureWorks TO DISK = 'c:\backups\AW_log.bak' WITH INIT; &lt;BR&gt;SELECT COUNT(*) FROM fn_dblog(null, null); &lt;BR&gt;GO &lt;BR&gt;ALTER INDEX&amp;nbsp; CL_details_indx&amp;nbsp; ON Details REBUILD &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;INSERT INTO AlterIndexLoggingData &lt;BR&gt;SELECT 'FULL', 'OFF', &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MAX([Log Record Length]), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SUM([Log Record Length]), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; COUNT(*), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (SELECT COUNT(*) FROM fn_dblog(null, null) WHERE [Log Record Length]&amp;nbsp; &amp;gt; 8000) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM fn_dblog(null, null); &lt;BR&gt;GO &lt;BR&gt;&amp;nbsp; &lt;BR&gt;&amp;nbsp; &lt;BR&gt;--- FULL Recovery, ONLINE = ON&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT face=Consolas&gt;BACKUP LOG AdventureWorks TO DISK = 'c:\backups\AW_log.bak' WITH INIT; &lt;BR&gt;SELECT COUNT(*)&amp;nbsp; FROM fn_dblog(null, null); &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;ALTER INDEX&amp;nbsp; CL_details_indx&amp;nbsp; ON Details REBUILD &lt;BR&gt;WITH (ONLINE = ON) &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;INSERT INTO AlterIndexLoggingData &lt;BR&gt;SELECT 'FULL', 'ON', &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MAX([Log Record Length]), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SUM([Log Record Length]), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; COUNT(*), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (SELECT COUNT(*) FROM fn_dblog(null, null) WHERE [Log Record Length]&amp;nbsp; &amp;gt; 8000) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM fn_dblog(null, null); &lt;BR&gt;GO &lt;BR&gt;--SELECT * FROM AlterIndexLoggingData; &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;--- BULK_LOGGED Recovery; ONLINE = OFF&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT face=Consolas&gt;ALTER DATABASE AdventureWorks SET RECOVERY BULK_LOGGED; &lt;BR&gt;BACKUP LOG AdventureWorks TO DISK = 'c:\backups\AW_log.bak' WITH INIT; &lt;BR&gt;SELECT COUNT(*)&amp;nbsp; FROM fn_dblog(null, null); &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;ALTER INDEX&amp;nbsp; CL_details_indx&amp;nbsp; ON Details REBUILD &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;INSERT INTO AlterIndexLoggingData &lt;BR&gt;SELECT 'BULK_LOGGED', 'OFF', &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MAX([Log Record Length]), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SUM([Log Record Length]), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; COUNT(*), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (SELECT COUNT(*) FROM fn_dblog(null, null) WHERE [Log Record Length]&amp;nbsp; &amp;gt; 8000) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM fn_dblog(null, null); &lt;BR&gt;GO &lt;BR&gt;--SELECT * FROM AlterIndexLoggingData; &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;--- BULK_LOGGED Recovery, ONLINE = ON&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT face=Consolas&gt;BACKUP LOG AdventureWorks TO DISK = 'c:\backups\AW_log.bak' WITH INIT; &lt;BR&gt;SELECT COUNT(*)&amp;nbsp; FROM fn_dblog(null, null); &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;ALTER INDEX&amp;nbsp; CL_details_indx&amp;nbsp; ON Details REBUILD WITH (ONLINE = ON) &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Consolas&gt;INSERT INTO AlterIndexLoggingData &lt;BR&gt;SELECT 'BULK_LOGGED', 'ON', &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MAX([Log Record Length]), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SUM([Log Record Length]), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; COUNT(*), &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (SELECT COUNT(*) FROM fn_dblog(null, null) WHERE [Log Record Length]&amp;nbsp; &amp;gt; 8000) &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM fn_dblog(null, null); &lt;BR&gt;GO &lt;BR&gt;SELECT * FROM AlterIndexLoggingData; &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Here are my results:&lt;/P&gt;
&lt;P&gt;&lt;A href="http://sqlblog.com/blogs/kalen_delaney/image_7169DDEF.png"&gt;&lt;IMG style="BACKGROUND-IMAGE:none;BORDER-RIGHT-WIDTH:0px;PADDING-LEFT:0px;PADDING-RIGHT:0px;DISPLAY:inline;BORDER-TOP-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-LEFT-WIDTH:0px;PADDING-TOP:0px;" title=image border=0 alt=image src="http://sqlblog.com/blogs/kalen_delaney/image_thumb_16F35B51.png" width=650 height=141&gt;&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;Notice the following: &lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Only FULL recovery with an OFFLINE rebuild logged the 8K pages. &lt;/LI&gt;
&lt;LI&gt;FULL recovery with an ONLINE rebuild actually did log every single row; in fact, it looks like at least two log records were generated for each row. There is a LOT of logging for this set of options and by far, the largest amount of log space used. &lt;/LI&gt;
&lt;LI&gt;BULK_LOGGED mode used substantially less log space than FULL, but the difference between ONLINE and OFFLINE is even more pronounced. In FULL recovery, ONLINE used about twice the log space, in BULK_LOGGED, the logging required more than 8 times the space! &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;I'm sure there are more fun facts you can pull out of the results from &lt;EM&gt;fn_dblog&lt;/EM&gt;, but for my first look at the logging differences between ONLINE and OFFLINE index rebuilds, this is what I wanted to know. It's not a complete answer to the student's question, but it does indicate there is a lot more logging with ONLINE index builds, so it should not&amp;nbsp;be surprising if there are a lot more log write waits. And I would assume that switch to BULK_LOGGED recovery could make a noticeable difference. &lt;/P&gt;
&lt;P&gt;I hope this is useful!&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff00ff size=4&gt;~Kalen&lt;/FONT&gt;&lt;/P&gt;</description></item><item><title>Geek City: Exploring the Transaction Log Structure</title><link>http://sqlblog.com/blogs/kalen_delaney/archive/2009/12/21/exploring-the-transaction-log-structure.aspx</link><pubDate>Tue, 22 Dec 2009 00:48:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:20158</guid><dc:creator>Kalen Delaney</dc:creator><description>&lt;P&gt;As I mentioned last October, my PASS preconference seminar was a &lt;A href="http://sqlblog.com/blogs/kalen_delaney/archive/2009/10/31/a-whole-day-of-transaction-log-info.aspx" target=_blank&gt;whole day about the transaction log&lt;/A&gt;. I told the attendees all kinds of things about the structure of the log, and told them about one of the best tools for exploring the structure: the command DBCC LOGINFO. This command is officially undocumented, but numerous bloggers have discussed it in some detail, including &lt;A href="http://www.mssqltips.com/tip.asp?tip=1225" target=_blank&gt;here&lt;/A&gt;. I'm not going to go into all the details here, but I'll be writing some related blog posts over the next month or so.&amp;nbsp; To understand DBCC LOGINFO you need to know about VLFs (Virtual Log Files) and one of the best places to read about VLFs is in &lt;A href="http://technet.microsoft.com/en-us/magazine/2009.02.logging.aspx" target=_blank&gt;Paul Randal's TechNet article&lt;/A&gt;. The DBCC LOGINFO command returns one row per VLF including the following columns:&lt;/P&gt;
&lt;P&gt;FileId &lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;EM&gt;If you only have one physical log file, this value will be the same in every row. If you have multiple log files, you can use this value to explore the order that the SQL Server will fill up VLFs in multiple files. (More details in a future post.)&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;FileSize&amp;nbsp; &lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;EM&gt;This value is in bytes. If you have your log file growth set to a percentage (which, unfortunately is the default), you should notice this value getting progressively larger as more VLFs are added to your log file.&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;StartOffset &lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;EM&gt;This value is also in bytes, and is used as the sort column for the output. That is, the output from DBCC LOGINFO (if you only have single physical file) will be ordered by StartOffset. Notice that the first VLF always starts at offset 8192, which is the number of bytes in a page. The first page of the physical file is the file header, and does not store log records.&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;FSeqNo &lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;EM&gt;The File Sequence Number indicates the order of usage of the VLFs. The row with the highest FSeqNo value is the VLF where current log records are being written. Notice that the FSeqNo values are not always consistent with the StartOffset, as VLFs are reusable after the log has been truncate. I'll discuss this value more in a future post, or you can get some info from Paul's article on how and when a VLF is reused. A value of 0 for the FSeqNo means that this VLF has never been used at all yet.&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Status &lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;EM&gt;Status has two possible values: 0 and 2. I'll be discussing these values in a lot more detail later. For now, the simple definition is that a value of 2 means the VLF is not reusable and a value of 0 means it is.&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&lt;BR&gt;Parity &lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;EM&gt;Parity has two possible values: 64 and 128. Every time a VLF is reused, the parity value is switched. Paul Randal discusses the parity value in more detail in &lt;/EM&gt;&lt;A href="http://www.sqlskills.com/BLOGS/PAUL/post/Search-Engine-QA-24-Why-cant-the-transaction-log-use-instant-initialization.aspx" target=_blank&gt;&lt;EM&gt;this article.&lt;/EM&gt;&lt;/A&gt;&lt;EM&gt; &lt;BR&gt;&lt;/EM&gt;&amp;nbsp;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;CreateLSN &lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;EM&gt;This value indicates when the VLF was created. If the CreateLSN is 0, it means the VLF was original equipment, created when the database was created. You can use this value to see how many VLFs were added each time the log grew. VLFs with the same CreateLSN value were created at the same time. &lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;The whole point of describing all these columns was just to provide a lead-in to my little holiday gift to you. Here is a table that can be created in the master database, and then populated with the output of DBCC LOGINFO. The name of the table starts with sp_, which means you can use it from any database.&amp;nbsp; Once you have captured all the rows from DBCC LOGINFO, you can query them using any TSQL queries.&lt;/P&gt;
&lt;P&gt;Here the table creation script:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;FONT size=1 face="Courier New"&gt;USE master &lt;BR&gt;GO &lt;BR&gt;IF EXISTS&amp;nbsp; (SELECT 1 FROM sys.tables &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE name = 'sp_LOGINFO') &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DROP TABLE sp_loginfo; &lt;BR&gt;GO &lt;BR&gt;CREATE TABLE sp_LOGINFO &lt;BR&gt;(FileId tinyint, &lt;BR&gt;FileSize bigint, &lt;BR&gt;StartOffset bigint, &lt;BR&gt;FSeqNo int, &lt;BR&gt;Status tinyint, &lt;BR&gt;Parity tinyint, &lt;BR&gt;CreateLSN numeric(25,0) ); &lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;And here is how to populate it. I will use the &lt;EM&gt;AdventureWorks&lt;/EM&gt; database for the example:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;FONT size=1 face="Courier New"&gt;USE AdventureWorks; &lt;BR&gt;GO &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT size=1 face="Courier New"&gt;TRUNCATE TABLE sp_LOGINFO; &lt;BR&gt;INSERT INTO sp_LOGINFO &lt;BR&gt;&amp;nbsp;&amp;nbsp; EXEC ('DBCC LOGINFO');&lt;/FONT&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;You can select the VLFs from most recently used to the least:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT size=1 face="courier new"&gt;SELECT * FROM sp_LOGINFO &lt;BR&gt;ORDER BY FSeqNo DESC;&lt;/FONT&gt; &lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;You can count how many VLFs have each status value:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;FONT size=1 face="Courier New"&gt;SELECT status, COUNT(*) FROM sp_LOGINFO &lt;BR&gt;GROUP BY status;&lt;/FONT&gt; &lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;If you don't have a lot of VLFs, you can try to generate more by running SELECT INTO repeatedly. Make sure you are in FULL recovery to get the most log growth:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;FONT size=1 face="Courier New"&gt;SELECT * INTO dbo.Orders FROM AdventureWorks.Sales.SalesOrderDetail; &lt;BR&gt;DROP TABLE dbo.Orders; &lt;BR&gt;GO 5&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT size=1 face="Courier New"&gt;TRUNCATE TABLE sp_LOGINFO; &lt;BR&gt;INSERT INTO sp_LOGINFO &lt;BR&gt;&amp;nbsp;&amp;nbsp; EXEC ('DBCC LOGINFO');&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT size=1 face="Courier New"&gt;SELECT status, COUNT(*) FROM sp_LOGINFO &lt;BR&gt;GROUP BY status WITH ROLLUP;&lt;/FONT&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;You may be able to figure out other queries that provide useful or interesting information for you. I'll refer back to this table in my future discussions on DBCC LOGINFO and VLFs.&lt;/P&gt;
&lt;P&gt;Enjoy!&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff00ff size=4&gt;~Kalen&lt;/FONT&gt;&lt;/P&gt;</description></item></channel></rss>