{"id":1075,"date":"2011-06-21T16:30:00","date_gmt":"2011-06-21T14:30:00","guid":{"rendered":"https:\/\/www.sqlinthewild.co.za\/?p=1075"},"modified":"2011-06-06T16:33:22","modified_gmt":"2011-06-06T14:33:22","slug":"are-all-updates-split-into-delete-insert","status":"publish","type":"post","link":"https:\/\/www.sqlinthewild.co.za\/index.php\/2011\/06\/21\/are-all-updates-split-into-delete-insert\/","title":{"rendered":"Are all updates split into delete-insert?"},"content":{"rendered":"<p>This should be another quick one.<\/p>\n<p>Earlier I saw a forum post where someone asserted that SQL always executes an update as a delete of the row and then an insert of the row with the new values. Now I need another excuse to play with undocumented features, so let&#8217;s see if that&#8217;s true (or at least true for all the common cases I try)<\/p>\n<p>First up, a heap, no indexes at all, an update that does not change the size of the row.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE TABLE TestingUpdate1 (\r\nID INT IDENTITY,\r\nSomeString CHAR(50)\r\n)\r\n\r\nINSERT INTO TestingUpdate1 (SomeString)\r\nVALUES\r\n('One'),('Two'),('Three'),('Four'),('Five'),('Six'),('Seven'),('Eight'),('Nine')\r\n\r\nCHECKPOINT -- truncate the log, DB is in simple recovery.\r\n\r\nUPDATE TestingUpdate1\r\nSET SomeString = 'NotFour'\r\nWHERE ID = 4 -- one row\r\n\r\nSELECT Operation, Context, AllocUnitName, &#x5B;Transaction Name], Description FROM fn_dblog(NULL, NULL) AS TranLog\r\n\r\nDROP TABLE TestingUpdate1<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/UpdateHeapFixedSize.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;\" title=\"UpdateHeapFixedSize\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/UpdateHeapFixedSize_thumb.png\" border=\"0\" alt=\"UpdateHeapFixedSize\" width=\"484\" height=\"80\" \/><\/a><\/p>\n<p>The log operation here is Modify Row. so in this case, the update was done as an in-place update.<\/p>\n<p>Second test, a heap, no indexes at all, and an update that changes the row size (however plenty free space on the page, so no issues with forwarding pointers here)<\/p>\n<p><!--more--><\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE TABLE TestingUpdate2 (\r\nID INT IDENTITY,\r\nSomeString VARCHAR(50)\r\n)\r\n\r\nINSERT INTO TestingUpdate2 (SomeString)\r\nVALUES\r\n('One'),('Two'),('Three'),('Four'),('Five'),('Six'),('Seven'),('Eight'),('Nine')\r\n\r\nCHECKPOINT -- truncate the log, DB is in simple recovery.\r\n\r\nUPDATE TestingUpdate2\r\nSET SomeString = 'NotFour'\r\nWHERE ID = 4 -- one row\r\n\r\nSELECT Operation, Context, AllocUnitName, &#x5B;Transaction Name], Description FROM fn_dblog(NULL, NULL) AS TranLog\r\n\r\nDROP TABLE TestingUpdate2<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/UpdateHeapChangingSize.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;\" title=\"UpdateHeapChangingSize\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/UpdateHeapChangingSize_thumb.png\" border=\"0\" alt=\"UpdateHeapChangingSize\" width=\"484\" height=\"78\" \/><\/a><\/p>\n<p>Again we have a modify row, so that again was an in-place update.<\/p>\n<p>Last on the heap, let&#8217;s see if things change when there&#8217;s lots and lots of rows (I&#8217;m going to use an update that does not change the row size, I don&#8217;t want to involve forwarding pointers in the discussion here)<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE TABLE TestingUpdate3 (\r\nID INT IDENTITY,\r\nSomeString CHAR(50)\r\n)\r\n\r\nINSERT INTO TestingUpdate3 (SomeString)\r\nSELECT TOP (1000000) ' ' FROM msdb.sys.columns a CROSS JOIN msdb.sys.columns b\r\n\r\nCHECKPOINT -- truncate the log, DB is in simple recovery.\r\n\r\nUPDATE TestingUpdate3\r\nSET SomeString = 'Something'\r\n\r\nSELECT Operation, Context, AllocUnitName, &#x5B;Transaction Name], Description FROM fn_dblog(NULL, NULL) AS TranLog\r\n\r\nDROP TABLE TestingUpdate3<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/UpdateHeapLotsOfRows.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;\" title=\"UpdateHeapLotsOfRows\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/UpdateHeapLotsOfRows_thumb.png\" border=\"0\" alt=\"UpdateHeapLotsOfRows\" width=\"484\" height=\"216\" \/><\/a><\/p>\n<p>Still the modify row operation, so it&#8217;s not a case that lots of rows will cause SQL to split the update into a delete-insert pair.<\/p>\n<p>Moving on, let&#8217;s try a table with a clustered index, an update of a non-key column that does not change the size of the row. Anyone willing to bet what we&#8217;ll see?<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE TABLE TestingUpdate4 (\r\nID INT IDENTITY,\r\nSomeString CHAR(50)\r\n)\r\n\r\nCREATE CLUSTERED INDEX idx_ID ON TestingUpdate4 (ID)\r\n\r\nINSERT INTO TestingUpdate4 (SomeString)\r\nVALUES\r\n('One'),('Two'),('Three'),('Four'),('Five'),('Six'),('Seven'),('Eight'),('Nine')\r\n\r\nCHECKPOINT -- truncate the log, DB is in simple recovery.\r\n\r\nUPDATE TestingUpdate4\r\nSET SomeString = 'NotFour'\r\nWHERE ID = 4 -- one row\r\n\r\nSELECT Operation, Context, AllocUnitName, &#x5B;Transaction Name], Description FROM fn_dblog(NULL, NULL) AS TranLog\r\n\r\nDROP TABLE TestingUpdate4<\/pre>\n<p>The log records are a little more complex, there&#8217;s a few more than in the previous case, but the operation for the update is still the same &#8211; LOP_Modify_Row<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/UpdateClusterFixedSize.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;\" title=\"UpdateClusterFixedSize\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/UpdateClusterFixedSize_thumb.png\" border=\"0\" alt=\"UpdateClusterFixedSize\" width=\"484\" height=\"114\" \/><\/a><\/p>\n<p>Second test with a clustered index, an update of a non-key column that does change the size of the row.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE TABLE TestingUpdate5 (\r\nID INT IDENTITY,\r\nSomeString VARCHAR(50)\r\n)\r\n\r\nCREATE CLUSTERED INDEX idx_ID ON TestingUpdate5 (ID)\r\n\r\nINSERT INTO TestingUpdate5 (SomeString)\r\nVALUES\r\n('One'),('Two'),('Three'),('Four'),('Five'),('Six'),('Seven'),('Eight'),('Nine')\r\n\r\nCHECKPOINT -- truncate the log, DB is in simple recovery.\r\n\r\nUPDATE TestingUpdate5\r\nSET SomeString = 'NotFour'\r\nWHERE ID = 4 -- one row\r\n\r\nSELECT Operation, Context, AllocUnitName, &#x5B;Transaction Name], Description FROM fn_dblog(NULL, NULL) AS TranLog\r\n\r\nDROP TABLE TestingUpdate5<\/pre>\n<p>Still have a modify row operation here. Still an in-place update.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/UpdateClusterChangingSize.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;\" title=\"UpdateClusterChangingSize\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/UpdateClusterChangingSize_thumb.png\" border=\"0\" alt=\"UpdateClusterChangingSize\" width=\"484\" height=\"116\" \/><\/a><\/p>\n<p>Lastly an update of the clustered index key value.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE TABLE TestingUpdate6 (\r\nID INT,\r\nSomeString CHAR(50)\r\n)\r\n\r\nCREATE CLUSTERED INDEX idx_ID ON TestingUpdate6 (ID)\r\n\r\nINSERT INTO TestingUpdate6 (ID, SomeString)\r\nVALUES\r\n(1,'One'),(2,'Two'),(3,'Three'),(4,'Four'),(5,'Five'),(6,'Six'),(7,'Seven'),(8,'Eight'),(9,'Nine')\r\n\r\nCHECKPOINT -- truncate the log, DB is in simple recovery.\r\n\r\nUPDATE TestingUpdate6\r\nSET SomeString = 'NotFour',\r\nID = 42\r\nWHERE ID = 4 -- one row\r\n\r\nSELECT Operation, Context, AllocUnitName, &#x5B;Transaction Name], Description FROM fn_dblog(NULL, NULL) AS TranLog\r\n\r\nDROP TABLE TestingUpdate6<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/SplitUpdate.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;\" title=\"SplitUpdate\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/06\/SplitUpdate_thumb.png\" border=\"0\" alt=\"SplitUpdate\" width=\"484\" height=\"134\" \/><\/a><\/p>\n<p>Now we do have a split update. We&#8217;ve got a delete_rows and an insert_rows operation in the log. This was not done as an in-place update<\/p>\n<p>So what can we conclude here? Does SQL do all updates as split updates?<\/p>\n<p>It should be clear that, for cases where the index key is not changed, SQL can do updates as in-place updates. I&#8217;m not going to try and claim that it always will, that would be silly, there are lots of scenarios that I haven&#8217;t looked at (page splits and forwarded rows being among the most obvious), but it can and will do in-place updates.<\/p>\n<p>For updates that change the key values, SQL will not do those as in-place updates. Paul explained that in one of his debunking posts a while back &#8211; <a title=\"http:\/\/sqlskills.com\/BLOGS\/PAUL\/post\/Do-changes-to-index-keys-really-do-in-place-updates.aspx\" href=\"http:\/\/sqlskills.com\/BLOGS\/PAUL\/post\/Do-changes-to-index-keys-really-do-in-place-updates.aspx\">http:\/\/sqlskills.com\/BLOGS\/PAUL\/post\/Do-changes-to-index-keys-really-do-in-place-updates.aspx<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This should be another quick one. Earlier I saw a forum post where someone asserted that SQL always executes an update as a delete of the row and then an insert of the row with the new values. Now I&#8230; <a class=\"read-more-button\" href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2011\/06\/21\/are-all-updates-split-into-delete-insert\/\">(Read more)<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[29,15,16],"tags":[],"class_list":["post-1075","post","type-post","status-publish","format-standard","hentry","category-internals","category-sql-server","category-syndication"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p7h6n-hl","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/1075","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/comments?post=1075"}],"version-history":[{"count":0,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/1075\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/media?parent=1075"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/categories?post=1075"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/tags?post=1075"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}