{"id":602,"date":"2011-02-25T09:22:00","date_gmt":"2011-02-25T09:22:00","guid":{"rendered":"\/blogs\/paul\/post\/Do-changes-to-index-keys-really-do-in-place-updates.aspx"},"modified":"2017-04-13T09:53:37","modified_gmt":"2017-04-13T16:53:37","slug":"do-changes-to-index-keys-really-do-in-place-updates","status":"publish","type":"post","link":"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/","title":{"rendered":"Do changes to index keys really do in-place updates?"},"content":{"rendered":"<p>\n<font face=\"verdana,geneva\" size=\"2\">There was an interesting discussion on Twitter this morning (started by my good friend Erin Stellato (<\/font><a href=\"http:\/\/erinstellato.com\/\"><font face=\"verdana,geneva\" size=\"2\">blog<\/font><\/a><font face=\"verdana,geneva\" size=\"2\">|<\/font><a href=\"https:\/\/twitter.com\/erinstellato\"><font face=\"verdana,geneva\" size=\"2\">twitter<\/font><\/a><font face=\"verdana,geneva\" size=\"2\">)) about in-place updates for index records where the key value changes and the record remains on the same page. Various sources including the SQL Server 2008 Internals book (pg 361 &#8211; I didn&#39;t write or review that bit :-) describe the process, but they describe it incorrectly unfortunately. They say that the record will remain in the exact same location on the same page and only the key storage bytes need to change. <\/font>\n<\/p>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">This is not true. True in-place updates of index key values do not happen in 2005 onwards. Let&#39;s go down the rabbit hole&#8230;<\/font>\n<\/p>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">Here&#39;s a simple example: <\/font>\n<\/p>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">CREATE DATABASE KeyUpdateTest;<br \/>\n\tGO<br \/>\n\tUSE KeyUpdateTest;<br \/>\n\tGO<br \/>\n\tALTER DATABASE KeyUpdateTest SET RECOVERY SIMPLE;<br \/>\n\tALTER DATABASE KeyUpdateTest SET AUTO_CREATE_STATISTICS OFF;<br \/>\n\tALTER DATABASE KeyUpdateTest SET AUTO_UPDATE_STATISTICS OFF;<br \/>\n\tGO<br \/>\n\tCREATE TABLE test (c1 INT, c2 VARCHAR (2000));<br \/>\n\tGO<br \/>\n\tCREATE CLUSTERED INDEX test_cl ON test (c1);<br \/>\n\tGO<br \/>\n\tCHECKPOINT;<br \/>\n\tGO<br \/>\n\tINSERT INTO test VALUES (1, REPLICATE (&#39;Paul&#39;, 500));<br \/>\n\tGO<br \/>\n\tCHECKPOINT;<br \/>\n\tGO <\/font>\n\t<\/p>\n<\/blockquote>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">Now we look at the page itself (and you can get the stored-proc I use below from my old blog post <\/font><a href=\"https:\/\/www.sqlskills.com\/blogs\/paul\/inside-the-storage-engine-sp_allocationmetadata-putting-undocumented-system-catalog-views-to-work\/\"><font face=\"verdana,geneva\" size=\"2\">Inside The Storage Engine: sp_AllocationMetadata &#8211; putting undocumented system catalog views to work<\/font><\/a><font face=\"verdana,geneva\" size=\"2\">): <\/font>\n<\/p>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">EXEC sp_allocationMetadata &#39;test&#39;;<br \/>\n\tGO <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">Object Name Index ID Alloc Unit ID&nbsp;&nbsp;&nbsp;&nbsp; Alloc Unit Type First Page Root Page First IAM Page<br \/>\n\t&#8212;&#8212;&#8212;&#8211; &#8212;&#8212;&#8211; &#8212;&#8212;&#8212;&#8212;&#8212;&#8211; &#8212;&#8212;&#8212;&#8212;&#8212; &#8212;&#8212;&#8212;- &#8212;&#8212;&#8212; &#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n\ttest&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 72057594039762944 IN_ROW_DATA&nbsp;&nbsp;&nbsp;&nbsp; (1:144)&nbsp;&nbsp;&nbsp; (1:144)&nbsp;&nbsp; (1:145) <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">DBCC TRACEON (3604);<br \/>\n\tGO<br \/>\n\tDBCC PAGE (keyupdatetest, 1, 144, 2);<br \/>\n\tGO <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">Memory Dump @0x0000000016A6A000 <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">0000000016A6A000:&nbsp;&nbsp; 01010400 00c20001 00000000 00000800 &dagger;&#8230;..&Acirc;&#8230;&#8230;&#8230;. <br \/>\n\t0000000016A6A010:&nbsp;&nbsp; 00000000 00000100 1c000000 bd174108 &dagger;&#8230;&#8230;&#8230;&#8230;&frac12;.A. <br \/>\n\t0000000016A6A020:&nbsp;&nbsp; 90000000 01000000 1d000000 8a000000 &dagger;&#8230;&#8230;&#8230;&#8230;\u008a&#8230; <br \/>\n\t0000000016A6A030:&nbsp;&nbsp; 12000000 00000000 00000000 239dbd4a &dagger;&#8230;&#8230;&#8230;&#8230;#.&frac12;J <br \/>\n\t0000000016A6A040:&nbsp;&nbsp; 00000000 00000000 00000000 00000000 &dagger;&#8230;&#8230;&#8230;&#8230;&#8230;. <br \/>\n\t0000000016A6A050:&nbsp;&nbsp; 00000000 00000000 00000000 00000000 &dagger;&#8230;&#8230;&#8230;&#8230;&#8230;. <br \/>\n\t0000000016A6A060:&nbsp;&nbsp; 30000800 01000000 03000002 001100e1 &dagger;0&#8230;&#8230;&#8230;&#8230;..&aacute; <br \/>\n\t0000000016A6A070:&nbsp;&nbsp; 07506175 6c506175 6c506175 6c506175 &dagger;.PaulPaulPaulPau <br \/>\n\t0000000016A6A080:&nbsp;&nbsp; 6c506175 6c506175 6c506175 6c506175 &dagger;lPaulPaulPaulPau <br \/>\n\t0000000016A6A090:&nbsp;&nbsp; 6c506175 6c506175 6c506175 6c506175 &dagger;lPaulPaulPaulPau <br \/>\n\t0000000016A6A0A0:&nbsp;&nbsp; 6c506175 6c506175 6c506175 6c506175 &dagger;lPaulPaulPaulPau <br \/>\n\t0000000016A6A0B0:&nbsp;&nbsp; 6c506175 6c506175 6c506175 6c506175 &dagger;lPaulPaulPaulPau <br \/>\n\t&lt;snip&gt;<br \/>\n\t0000000016A6A820:&nbsp;&nbsp; 6c506175 6c506175 6c506175 6c506175 &dagger;lPaulPaulPaulPau <br \/>\n\t0000000016A6A830:&nbsp;&nbsp; 6c506175 6c506175 6c506175 6c506175 &dagger;lPaulPaulPaulPau <br \/>\n\t0000000016A6A840:&nbsp;&nbsp; 6c000021 21212121 21212121 21212121 &dagger;l..!!!!!!!!!!!!! <br \/>\n\t0000000016A6A850:&nbsp;&nbsp; 21212121 21212121 21212121 21212121 &dagger;!!!!!!!!!!!!!!!! <br \/>\n\t&lt;snip&gt;<br \/>\n\t0000000016A6BFE0:&nbsp;&nbsp; 21212121 21212121 21212121 21212121 &dagger;!!!!!!!!!!!!!!!! <br \/>\n\t0000000016A6BFF0:&nbsp;&nbsp; 21212121 21212121 21212121 21216000 &dagger;!!!!!!!!!!!!!!`. <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">OFFSET TABLE: <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">Row &#8211; Offset&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br \/>\n\t0 (0x0) &#8211; 96 (0x60) <\/font>\n\t<\/p>\n<\/blockquote>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">Now I&#39;ll update the record: <\/font>\n<\/p>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">UPDATE test SET c1 = 2 WHERE c1 =1;<br \/>\n\tGO <\/font>\n\t<\/p>\n<\/blockquote>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">And let&#39;s look at the log records generated (in the output below I&#39;ve removed all the extraneous stuff from the log that occurs before my transaction): <\/font>\n<\/p>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">SELECT [Current LSN], [Operation], [Context], <br \/>\n\t&nbsp;[Log Record Length], [Page ID], [Slot ID] FROM fn_dblog (NULL, NULL);<br \/>\n\tGO <\/font>\n\t<\/p>\n<\/blockquote>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">Current LSN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Operation&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Context&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log Record Length Page ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Slot ID<br \/>\n\t&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211; &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;- &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; &#8212;&#8212;&#8212;&#8212;&#8212;&#8211; &#8212;&#8212;&#8212;&#8212;&#8211; &#8212;&#8212;&#8212;&#8211;<br \/>\n\t0000001d:00000096:0011&nbsp; LOP_BEGIN_XACT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 112&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL<br \/>\n\t0000001d:00000096:0012&nbsp; LOP_DELETE_ROWS&nbsp;&nbsp;&nbsp;&nbsp; LCX_MARK_AS_GHOST&nbsp;&nbsp;&nbsp;&nbsp; 2116&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000090&nbsp; 0<br \/>\n\t0000001d:00000096:0013&nbsp; LOP_MODIFY_HEADER&nbsp;&nbsp; LCX_PFS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 76&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000001&nbsp; 0<br \/>\n\t0000001d:00000096:0014&nbsp; LOP_SET_BITS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_PFS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000001&nbsp; 0<br \/>\n\t0000001d:00000096:0015&nbsp; LOP_INSERT_ROWS&nbsp;&nbsp;&nbsp;&nbsp; LCX_CLUSTERED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2120&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000090&nbsp; 1<br \/>\n\t0000001d:00000096:0016&nbsp; LOP_COMMIT_XACT&nbsp;&nbsp;&nbsp;&nbsp; LCX_NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL<br \/>\n\t0000001d:000000a2:0001&nbsp; LOP_EXPUNGE_ROWS&nbsp;&nbsp;&nbsp; LCX_CLUSTERED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 64&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000090&nbsp; 0<br \/>\n\t0000001d:000000a2:0002&nbsp; LOP_SET_BITS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_PFS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 56&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000001&nbsp; 0<br \/>\n\t0000001d:000000a2:0003&nbsp; LOP_MODIFY_HEADER&nbsp;&nbsp; LCX_PFS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 76&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000001&nbsp; 0 <\/font>\n\t<\/p>\n<\/blockquote>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">The row is deleted and then inserted. Look at the log record lengths! And look at the slot numbers. The new row is in slot 1 (the <font face=\"courier new,courier\">LOP_INSERT_ROWS<\/font> record) and slot 0 is ghosted (the <font face=\"courier new,courier\">LOP_DELETE_ROWS<\/font> record), and then we see ghost cleanup running (the <font face=\"courier new,courier\">LOP_EXPUNGE_ROWS<\/font> record), which removes the old slot 0, and the slot array shifts down so the new record is slot 0 again. See my blog post <a href=\"https:\/\/www.sqlskills.com\/blogs\/paul\/inside-the-storage-engine-ghost-cleanup-in-depth\/\">Inside the Storage Engine: Ghost cleanup in depth<\/a> for info on ghost cleanup internals.<\/font>\n<\/p>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">But is it in the same physical place on the page? No &#8211; a whole new record was created. <\/font>\n<\/p>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">DBCC PAGE (keyupdatetest, 1, 144, 2);<br \/>\n\tGO <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">Memory Dump @0x0000000013CAC000 <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">0000000013CAC000:&nbsp;&nbsp; 01010400 00800001 00000000 00000800 &dagger;&#8230;&#8230;&#8230;&#8230;&#8230;. <br \/>\n\t0000000013CAC010:&nbsp;&nbsp; 00000000 00000100 1c000000 bd172210 &dagger;&#8230;&#8230;&#8230;&#8230;&frac12;.&quot;. <br \/>\n\t0000000013CAC020:&nbsp;&nbsp; 90000000 01000000 1d000000 a2000000 &dagger;&#8230;&#8230;&#8230;&#8230;&cent;&#8230; <br \/>\n\t0000000013CAC030:&nbsp;&nbsp; 01000000 76020000 00000000 239dbd4a &dagger;&#8230;.v&#8230;&#8230;.#.&frac12;J <br \/>\n\t0000000013CAC040:&nbsp;&nbsp; 01000000 00000000 00000000 00000000 &dagger;&#8230;&#8230;&#8230;&#8230;&#8230;. <br \/>\n\t0000000013CAC050:&nbsp;&nbsp; 00000000 00000000 00000000 00000000 &dagger;&#8230;&#8230;&#8230;&#8230;&#8230;. <br \/>\n\t0000000013CAC060:&nbsp;&nbsp; 3c000800 01000000 03000002 001100e1 &dagger;&lt;&#8230;&#8230;&#8230;&#8230;..&aacute; <br \/>\n\t0000000013CAC070:&nbsp;&nbsp; 07506175 6c506175 6c506175 6c506175 &dagger;.PaulPaulPaulPau <br \/>\n\t0000000013CAC080:&nbsp;&nbsp; 6c506175 6c506175 6c506175 6c506175 &dagger;lPaulPaulPaulPau <br \/>\n\t0000000013CAC090:&nbsp;&nbsp; 6c506175 6c506175 6c506175 6c506175 &dagger;lPaulPaulPaulPau <br \/>\n\t&lt;snip&gt;<br \/>\n\t0000000013CAC820:&nbsp;&nbsp; 6c506175 6c506175 6c506175 6c506175 &dagger;lPaulPaulPaulPau <br \/>\n\t0000000013CAC830:&nbsp;&nbsp; 6c506175 6c506175 6c506175 6c506175 &dagger;lPaulPaulPaulPau <br \/>\n\t0000000013CAC840:&nbsp;&nbsp; 6c300008 00020000 00030000 02001100 &dagger;l0&#8230;&#8230;&#8230;&#8230;.. <br \/>\n\t0000000013CAC850:&nbsp;&nbsp; e1075061 756c5061 756c5061 756c5061 &dagger;&aacute;.PaulPaulPaulPa <br \/>\n\t0000000013CAC860:&nbsp;&nbsp; 756c5061 756c5061 756c5061 756c5061 &dagger;ulPaulPaulPaulPa <br \/>\n\t&lt;snip&gt;<br \/>\n\t0000000013CADFE0:&nbsp;&nbsp; 21212121 21212121 21212121 21212121 &dagger;!!!!!!!!!!!!!!!! <br \/>\n\t0000000013CADFF0:&nbsp;&nbsp; 21212121 21212121 21212121 41084108 &dagger;!!!!!!!!!!!!A.A. <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">OFFSET TABLE: <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">Row &#8211; Offset&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br \/>\n\t0 (0x0) &#8211; 2113 (0x841)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/font>\n\t<\/p>\n<\/blockquote>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">Notice that where the original record ended (offset <font face=\"courier new,courier\">0x830<\/font> into the page). In the first page dump, everything after that is empty space. In the second page dump, the new record starts there. And look at the slot array (called the offset table by <font face=\"courier new,courier\">DBCC PAGE<\/font>). In the first page dump the original record started at offset <font face=\"courier new,courier\">0x60<\/font>, and in the second dump it starts at offset <font face=\"courier new,courier\">0x841<\/font>. Even though ghost cleanup ran, the record did NOT migrate to it&#39;s original location. <\/font>\n<\/p>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">It&#39;s very clear that an in-place update did not happen, and that the entire record is recreated in it&#39;s entirety. <\/font>\n<\/p>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">In this case, the record ended up being physical slot 0 again, but that&#39;s a pathological case because there&#39;s only one slot on the page. If you repeat the experiment with two records (e.g. c1=1 and c1=2) and then update c1 of the first record to 3, you&#39;ll see the new record being physical slot 2, then after ghost cleanup becoming physical slot 1. But again, it&#39;s deleted and reinserted in it&#39;s entirety. <\/font>\n<\/p>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">Now I&#39;ll show you a case where the row has to move to a new page &#8211; definitely not an in-place update! <\/font>\n<\/p>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">CREATE DATABASE KeyUpdateTest;<br \/>\n\tGO<br \/>\n\tUSE KeyUpdateTest;<br \/>\n\tGO<br \/>\n\tALTER DATABASE KeyUpdateTest SET RECOVERY SIMPLE;<br \/>\n\tALTER DATABASE KeyUpdateTest SET AUTO_CREATE_STATISTICS OFF;<br \/>\n\tALTER DATABASE KeyUpdateTest SET AUTO_UPDATE_STATISTICS OFF;<br \/>\n\tGO<br \/>\n\tCREATE TABLE test (c1 INT, c2 VARCHAR (4000));<br \/>\n\tGO<br \/>\n\tCREATE CLUSTERED INDEX test_cl ON test (c1);<br \/>\n\tGO<br \/>\n\tCHECKPOINT;<br \/>\n\tGO<br \/>\n\tINSERT INTO test VALUES (1, REPLICATE (&#39;Paul&#39;, 1000));<br \/>\n\tGO<br \/>\n\tINSERT INTO test VALUES (2, REPLICATE (&#39;Erin&#39;, 1000));<br \/>\n\tGO<br \/>\n\tCHECKPOINT;<br \/>\n\tGO <\/font>\n\t<\/p>\n<\/blockquote>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">I&#39;ve filled up the page with two rows. <\/font>\n<\/p>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">DBCC PAGE (keyupdatetest, 1, 144, 2);<br \/>\n\tGO <\/font>\n\t<\/p>\n<\/blockquote>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">&lt;snip&gt;<br \/>\n\tMetadata: ObjectId = 2105058535&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_prevPage = (0:0)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_nextPage = (0:0)<br \/>\n\tpminlen = 8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_slotCnt = 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_freeCnt = 58<br \/>\n\tm_freeData = 8130&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_reservedCnt = 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_lsn = (29:150:2)<br \/>\n\t&lt;snip&gt;<br \/>\n\t0000000016A6BFA0:&nbsp;&nbsp; 696e4572 696e4572 696e4572 696e4572 &dagger;inErinErinErinEr <br \/>\n\t0000000016A6BFB0:&nbsp;&nbsp; 696e4572 696e4572 696e4572 696e4572 &dagger;inErinErinErinEr <br \/>\n\t0000000016A6BFC0:&nbsp;&nbsp; 696e0000 21212121 21212121 21212121 &dagger;in..!!!!!!!!!!!! <br \/>\n\t0000000016A6BFD0:&nbsp;&nbsp; 21212121 21212121 21212121 21212121 &dagger;!!!!!!!!!!!!!!!! <br \/>\n\t0000000016A6BFE0:&nbsp;&nbsp; 21212121 21212121 21212121 21212121 &dagger;!!!!!!!!!!!!!!!! <br \/>\n\t0000000016A6BFF0:&nbsp;&nbsp; 21212121 21212121 21212121 11106000 &dagger;!!!!!!!!!!!!..`. <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">OFFSET TABLE: <\/font>\n\t<\/p>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">Row &#8211; Offset&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br \/>\n\t1 (0x1) &#8211; 4113 (0x1011)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br \/>\n\t0 (0x0) &#8211; 96 (0x60) <\/font>\n\t<\/p>\n<\/blockquote>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">Now I&#39;ll update the first row to have c1 = 3 so my row moves away from Erin&#39;s :-) <\/font>\n<\/p>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">UPDATE test SET c1 = 3 WHERE c1 =1;<br \/>\n\tGO <\/font>\n\t<\/p>\n<\/blockquote>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">And look at the log: <\/font>\n<\/p>\n<blockquote>\n<p>\n\t<font face=\"courier new,courier\" size=\"2\">SELECT [Current LSN], [Operation], [Context], <br \/>\n\t&nbsp;[Log Record Length], [Page ID], [Slot ID] FROM fn_dblog (NULL, NULL);<br \/>\n\tGO<\/p>\n<p>\tCurrent LSN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Operation&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Context&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Log Record Length Page ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Slot ID<br \/>\n\t&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211; &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;- &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;- &#8212;&#8212;&#8212;&#8212;&#8212;&#8211; &#8212;&#8212;&#8212;&#8212;&#8211; &#8212;&#8212;&#8212;&#8211;<br \/>\n\t0000001d:000000a3:0015&nbsp; LOP_BEGIN_XACT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 136&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL<br \/>\n\t0000001d:000000a3:0016&nbsp; LOP_MODIFY_ROW&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_PFS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 80&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000001&nbsp; 0<br \/>\n\t0000001d:000000a3:0017&nbsp; LOP_MODIFY_ROW&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_IAM&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 88&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000091&nbsp; 0<br \/>\n\t0000001d:000000a3:0018&nbsp; LOP_HOBT_DELTA&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 64&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL<br \/>\n\t0000001d:000000a3:0019&nbsp; LOP_FORMAT_PAGE&nbsp;&nbsp;&nbsp;&nbsp; LCX_INDEX_INTERIOR&nbsp;&nbsp;&nbsp;&nbsp; 84&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000092&nbsp; -1<br \/>\n\t0000001d:000000a3:001a&nbsp; LOP_INSERT_ROWS&nbsp;&nbsp;&nbsp;&nbsp; LCX_INDEX_INTERIOR&nbsp;&nbsp;&nbsp;&nbsp; 84&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000092&nbsp; 0<br \/>\n\t0000001d:000000a3:001b&nbsp; LOP_ROOT_CHANGE&nbsp;&nbsp;&nbsp;&nbsp; LCX_CLUSTERED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 96&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000082&nbsp; 52<br \/>\n\t0000001d:000000a3:001c&nbsp; LOP_COMMIT_XACT&nbsp;&nbsp;&nbsp;&nbsp; LCX_NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL<br \/>\n\t0000001d:000000a3:001d&nbsp; LOP_BEGIN_XACT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 120&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL<br \/>\n\t0000001d:000000a3:001e&nbsp; LOP_MODIFY_ROW&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_PFS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 80&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000001&nbsp; 0<br \/>\n\t0000001d:000000a3:001f&nbsp; LOP_MODIFY_ROW&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_IAM&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 88&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000091&nbsp; 0<br \/>\n\t0000001d:000000a3:0020&nbsp; LOP_HOBT_DELTA&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LCX_NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 64&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL<br \/>\n\t0000001d:000000a3:0021&nbsp; LOP_FORMAT_PAGE&nbsp;&nbsp;&nbsp;&nbsp; LCX_HEAP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 84&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000093&nbsp; -1<br \/>\n\t0000001d:000000a3:0022&nbsp; LOP_INSERT_ROWS&nbsp;&nbsp;&nbsp;&nbsp; LCX_CLUSTERED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4092&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000093&nbsp; 0<br \/>\n\t0000001d:000000a3:0023&nbsp; LOP_DELETE_SPLIT&nbsp;&nbsp;&nbsp; LCX_CLUSTERED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 60&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000090&nbsp; 1<br \/>\n\t0000001d:000000a3:0024&nbsp; LOP_MODIFY_HEADER&nbsp;&nbsp; LCX_HEAP&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 84&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000090&nbsp; 0<br \/>\n\t0000001d:000000a3:0025&nbsp; LOP_INSERT_ROWS&nbsp;&nbsp;&nbsp;&nbsp; LCX_INDEX_INTERIOR&nbsp;&nbsp;&nbsp;&nbsp; 88&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000092&nbsp; 1<br \/>\n\t0000001d:000000a3:0026&nbsp; LOP_COMMIT_XACT&nbsp;&nbsp;&nbsp;&nbsp; LCX_NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL<br \/>\n\t0000001d:000000a3:0027&nbsp; LOP_INSERT_ROWS&nbsp;&nbsp;&nbsp;&nbsp; LCX_CLUSTERED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4120&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0001:00000093&nbsp; 1<br \/>\n\t0000001d:000000a3:0028&nbsp; LOP_COMMIT_XACT&nbsp;&nbsp;&nbsp;&nbsp; LCX_NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 52&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL <\/font>\n\t<\/p>\n<\/blockquote>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">This is really interesting. Here&#39;s what happens (missing a few things for clarity): <\/font>\n<\/p>\n<ul>\n<li>\n<div>\n\t<font face=\"verdana,geneva\" size=\"2\"><font face=\"courier new,courier\">0000001d:000000a3:0016<\/font>: page <font face=\"courier new,courier\">(1:146)<\/font> is allocated to be the new root page for the clustered index <\/font>\n\t<\/div>\n<\/li>\n<li>\n<div>\n\t<font face=\"verdana,geneva\" size=\"2\"><font face=\"courier new,courier\">0000001d:000000a3:0019<\/font>: it gets minimally formatted (just the page header&nbsp;&#8211; although the page header is 96 bytes, there is 12 bytes of empty space at the end) <\/font>\n\t<\/div>\n<\/li>\n<li>\n<div>\n\t<font face=\"verdana,geneva\" size=\"2\"><font face=\"courier new,courier\">0000001d:000000a3:001a<\/font>: the index record pointing at <font face=\"courier new,courier\">(1:144)<\/font> is inserted in the root page<\/font>\n\t<\/div>\n<\/li>\n<li>\n<div>\n\t<font face=\"verdana,geneva\" size=\"2\"><font face=\"courier new,courier\">0000001d:000000a3:001b<\/font>: the index root os changedd from <font face=\"courier new,courier\">(1:144)<\/font> to <font face=\"courier new,courier\">(1:146)<\/font> in metadata <\/font>\n\t<\/div>\n<\/li>\n<li>\n<div>\n\t<font face=\"verdana,geneva\" size=\"2\"><font face=\"courier new,courier\">0000001d:000000a3:001e<\/font>: page <font face=\"courier new,courier\">(1:147)<\/font> is allocated to be the second data page at the leaf of the clustered index<\/font>\n\t<\/div>\n<\/li>\n<li>\n<div>\n\t<font face=\"verdana,geneva\" size=\"2\"><font face=\"courier new,courier\">0000001d:000000a3:0022<\/font>: a page split of page <font face=\"courier new,courier\">(1:144)<\/font> occurs, moving the Erin row to slot 0 of page <font face=\"courier new,courier\">(1:147)<\/font><\/font>\n\t<\/div>\n<\/li>\n<li>\n<div>\n\t<font face=\"verdana,geneva\" size=\"2\"><font face=\"courier new,courier\">0000001d:000000a3:0023<\/font>: the Erin row is removed from page <font face=\"courier new,courier\">(1:144)<\/font> &#8211; not ghosted<\/font>\n\t<\/div>\n<\/li>\n<li>\n<div>\n\t<font face=\"verdana,geneva\" size=\"2\"><font face=\"courier new,courier\">0000001d:000000a3:0025<\/font>: the index record pointing at <font face=\"courier new,courier\">(1:147)<\/font> is inserted in the root page<\/font>\n\t<\/div>\n<\/li>\n<li>\n<div>\n\t<font face=\"verdana,geneva\" size=\"2\"><font face=\"courier new,courier\">0000001d:000000a3:0027<\/font>: the updated Paul row is inserted as slot 1 of page <font face=\"courier new,courier\">(1:147)<\/font><\/font>\n\t<\/div>\n<\/li>\n<\/ul>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">As you can see, this is as far as you can get from doing an in-place update. Page <font face=\"courier new,courier\">(1:144)<\/font> is left with a single ghost-record on (the Paul record &#8211; the Erin record isn&#39;t ghosted because it was moved because of a split). The next access to the page will cause ghost cleanup to remove it and deallocate the page.<\/font>\n<\/p>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">So no, in-place updates of key values do not happen, even though they appear to at first glance. You can experiment to show the same thing happening in nonclustered indexes too.<\/font>\n<\/p>\n<p>\n<font face=\"verdana,geneva\" size=\"2\">Another misconception busted!<\/font>\n<\/p>\n<p>\n<font size=\"2\">PS Some of you may remember when such in-place updates really *did* happen. That was before 2005 when the Storage Engine (specifically my old Access Methods team) did the index maintenance (changing clustered and nonclustered index structures based on key value changes). A whole bunch of features in 2005 meant that we switched it over to the Query Processor to drive the index maintenance, and nifty short-cuts like we used to do aren&#39;t possible any more.<\/font><\/p>\n","protected":false},"excerpt":{"rendered":"<p>There was an interesting discussion on Twitter this morning (started by my good friend Erin Stellato (blog|twitter)) about in-place updates for index records where the key value changes and the record remains on the same page. Various sources including the SQL Server 2008 Internals book (pg 361 &#8211; I didn&#39;t write or review that bit [&hellip;]<\/p>\n","protected":false},"author":5,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[47,48,59,62],"tags":[],"class_list":["post-602","post","type-post","status-publish","format-standard","hentry","category-indexes-from-every-angle","category-inside-the-storage-engine","category-mcm","category-on-disk-structures"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Do changes to index keys really do in-place updates? - Paul S. Randal<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Do changes to index keys really do in-place updates? - Paul S. Randal\" \/>\n<meta property=\"og:description\" content=\"There was an interesting discussion on Twitter this morning (started by my good friend Erin Stellato (blog|twitter)) about in-place updates for index records where the key value changes and the record remains on the same page. Various sources including the SQL Server 2008 Internals book (pg 361 &#8211; I didn&#039;t write or review that bit [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/\" \/>\n<meta property=\"og:site_name\" content=\"Paul S. Randal\" \/>\n<meta property=\"article:published_time\" content=\"2011-02-25T09:22:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2017-04-13T16:53:37+00:00\" \/>\n<meta name=\"author\" content=\"Paul Randal\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Paul Randal\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/\",\"url\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/\",\"name\":\"Do changes to index keys really do in-place updates? - Paul S. Randal\",\"isPartOf\":{\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/#website\"},\"datePublished\":\"2011-02-25T09:22:00+00:00\",\"dateModified\":\"2017-04-13T16:53:37+00:00\",\"author\":{\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/#\/schema\/person\/ffcec826c18782e1e0adf173826a7fce\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Do changes to index keys really do in-place updates?\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/#website\",\"url\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/\",\"name\":\"Paul S. Randal\",\"description\":\"In Recovery...\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/#\/schema\/person\/ffcec826c18782e1e0adf173826a7fce\",\"name\":\"Paul Randal\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/0b6a266bba2f088f2551ef529293001bd73bf026bc1908b9866728c062beeeb6?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/0b6a266bba2f088f2551ef529293001bd73bf026bc1908b9866728c062beeeb6?s=96&d=mm&r=g\",\"caption\":\"Paul Randal\"},\"sameAs\":[\"http:\/\/3.209.169.194\/blogs\/paul\"],\"url\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/author\/paul\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Do changes to index keys really do in-place updates? - Paul S. Randal","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/","og_locale":"en_US","og_type":"article","og_title":"Do changes to index keys really do in-place updates? - Paul S. Randal","og_description":"There was an interesting discussion on Twitter this morning (started by my good friend Erin Stellato (blog|twitter)) about in-place updates for index records where the key value changes and the record remains on the same page. Various sources including the SQL Server 2008 Internals book (pg 361 &#8211; I didn&#39;t write or review that bit [&hellip;]","og_url":"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/","og_site_name":"Paul S. Randal","article_published_time":"2011-02-25T09:22:00+00:00","article_modified_time":"2017-04-13T16:53:37+00:00","author":"Paul Randal","twitter_misc":{"Written by":"Paul Randal","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/","url":"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/","name":"Do changes to index keys really do in-place updates? - Paul S. Randal","isPartOf":{"@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/#website"},"datePublished":"2011-02-25T09:22:00+00:00","dateModified":"2017-04-13T16:53:37+00:00","author":{"@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/#\/schema\/person\/ffcec826c18782e1e0adf173826a7fce"},"breadcrumb":{"@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/do-changes-to-index-keys-really-do-in-place-updates\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.sqlskills.com\/blogs\/paul\/"},{"@type":"ListItem","position":2,"name":"Do changes to index keys really do in-place updates?"}]},{"@type":"WebSite","@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/#website","url":"https:\/\/www.sqlskills.com\/blogs\/paul\/","name":"Paul S. Randal","description":"In Recovery...","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.sqlskills.com\/blogs\/paul\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/#\/schema\/person\/ffcec826c18782e1e0adf173826a7fce","name":"Paul Randal","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/0b6a266bba2f088f2551ef529293001bd73bf026bc1908b9866728c062beeeb6?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/0b6a266bba2f088f2551ef529293001bd73bf026bc1908b9866728c062beeeb6?s=96&d=mm&r=g","caption":"Paul Randal"},"sameAs":["http:\/\/3.209.169.194\/blogs\/paul"],"url":"https:\/\/www.sqlskills.com\/blogs\/paul\/author\/paul\/"}]}},"_links":{"self":[{"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/posts\/602","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/comments?post=602"}],"version-history":[{"count":0,"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/posts\/602\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/media?parent=602"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/categories?post=602"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/tags?post=602"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}