{"id":4884,"date":"2018-04-17T12:05:30","date_gmt":"2018-04-17T19:05:30","guid":{"rendered":"http:\/\/3.209.169.194\/blogs\/paul\/?p=4884"},"modified":"2018-04-17T12:05:30","modified_gmt":"2018-04-17T19:05:30","slug":"read-committed-doesnt-guarantee-much","status":"publish","type":"post","link":"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/","title":{"rendered":"Read committed doesn&#8217;t guarantee much&#8230;"},"content":{"rendered":"<p style=\"text-align: justify;\">A while back\u00a0I was involved in an email thread where people were wondering about some \u2018weird\u2019 behavior from SQL Server. The problem was occurring on SQL Server 2016 and used the default isolation level of read committed. The scenario was as follows:<\/p>\n<ul style=\"text-align: justify;\">\n<li>Create a table with a few columns<\/li>\n<li>Batch 1: In one SSMS window, do the following (which takes 10 seconds to run):\n<ul>\n<li>Start a transaction<\/li>\n<li>Insert 1,000 rows into the table, with a 0.01 second\u00a0<em>WAITFOR DELAY<\/em>\u00a0between each insert<\/li>\n<li>Commit the transaction<\/li>\n<\/ul>\n<\/li>\n<li>Batch 2: In a second SSMS window:\n<ul>\n<li>Select * from the table<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">The \u2018weird\u2019 behavior is that when the \u201cBatch 2\u201d select completes, after having been blocked by the \u201cBatch 1\u201d transaction, it doesn\u2019t return all 1,000 rows (even though \u201cBatch 1\u201d has completed). Furthermore, depending on <strong><em>when<\/em><\/strong> the \u201cBatch 2\u201d select is started, during the 10-seconds that \u201cBatch 1\u201d executes, \u201cBatch 2\u201d returns different numbers of rows. This behavior had also been reported on earlier versions of SQL Server as well. It\u2019s easy to reproduce on SQL Server 2016\/2017 and can be reproduced in all earlier versions with a single configuration change (more details in a moment).<\/p>\n<p style=\"text-align: justify;\">Additionally, if the table has a clustered index created, 1,000 rows are returned every time, on all versions of SQL Server.<\/p>\n<p style=\"text-align: justify;\">So why is this weird? Many people expect that all 1,000 rows\u00a0will be returned every time AND that the structure of the table or the version of SQL Server should not make any difference.<\/p>\n<p style=\"text-align: justify;\">Unfortunately, that assumption is not correct when using read committed. The read committed isolation level guarantees that only committed data will be read; it does NOT guarantee how much of the committed data will be returned and is prone to inconsistent analysis problems. If you want to avoid inconsistent analysis, you\u2019d need to increase your isolation level (if using locking) or change to versioning for read committed (or even snapshot isolation).<\/p>\n<p style=\"text-align: justify;\">However, I do agree that the assumption is reasonable, even though it&#8217;s not correct.<\/p>\n<p style=\"text-align: justify;\">The definition of the requirement is straightforward however. SQL Server guarantees that only committed data will be read, not how much of it will be read, and so the amount of committed data returned in this case is variable. Furthermore, depending on the table structure, SQL Server version, and configuration option (<em>which I\u2019ve mentioned twice now, but haven\u2019t explained as I don\u2019t want to spoil my story\u2026<\/em>), the number of rows returned will vary wildly.<\/p>\n<p style=\"text-align: justify;\">So, what\u2019s going on? Why the discrepancy in the number of rows?<\/p>\n<p style=\"text-align: justify;\">It\u2019s because the table is a heap.<\/p>\n<p style=\"text-align: justify;\">The &#8216;weird&#8217; behavior manifests itself when the heap has an <a href=\"https:\/\/www.sqlskills.com\/blogs\/paul\/inside-the-storage-engine-anatomy-of-an-extent\/\" target=\"_blank\" rel=\"noopener noreferrer\">extent<\/a> allocated to it immediately, from which the first data pages are allocated. When the allocation-order scanner for the select starts, it looks at the <a href=\"https:\/\/www.sqlskills.com\/blogs\/paul\/inside-the-storage-engine-gam-sgam-pfs-and-other-allocation-maps\/\" target=\"_blank\" rel=\"noopener noreferrer\">PFS bytes<\/a> for the 8 pages in the extent to see which ones are allocated. These will be scanned. Depending on when the select starts within the 10 seconds that \u201cbatch 1\u201d executes, there will be more rows or fewer rows read by the scanner (and returned in the \u201cbatch 2\u201d results) because more or fewer of the PFS byte 0x40 bits will have been set indicating the page in the extent is allocated.<\/p>\n<p style=\"text-align: justify;\">For a table to have an extent allocated to it immediately, mixed pages have to be disabled, which is the default for SQL Server 2016 onward, which is why the &#8216;weird&#8217; behavior manifests with a small number of rows on SQL Server 2016. However, if you\u2019ve enabled trace flag 1118, you\u2019ll see this behavior in all versions. This is why some people report seeing the &#8216;weird&#8217; behavior on versions earlier than SQL Server 2016.<\/p>\n<p style=\"text-align: justify;\">When mixed extents are NOT disabled, i.e. in earlier versions than SQL Server 2016 when 1118 is not enabled, the first 8 pages allocated are mixed pages, and so the allocation order scanner has to pick them up individually and sees them all (without going into details of the synchronization around the single-page slot array on the <a href=\"https:\/\/www.sqlskills.com\/blogs\/paul\/inside-the-storage-engine-iam-pages-iam-chains-and-allocation-units\/\" target=\"_blank\" rel=\"noopener noreferrer\">first IAM page in the IAM chain\/allocation unit<\/a> for the table), and so as long as no more than 8 data pages are used by the insert, all the rows on them will be returned by the select in this example.<\/p>\n<p style=\"text-align: justify;\">So there you have it, the behavior is \u201cby design\u201d and hasn&#8217;t changed at all, but is understandably confusing unless you know exactly how the Engine is performing the select under the covers.<\/p>\n<p style=\"text-align: justify;\">And if you create a clustered index, the \u2018weird\u2019 behavior doesn&#8217;t occur for this example in any version. This is because the select gets hung up on the S(hare) row lock for the first row in the clustered index, and by the time the select is granted the S lock, the insert has completed and the index leaf scan picks up all 1,000 rows.<\/p>\n<p style=\"text-align: justify;\">Don\u2019t think that using a clustered index stops \u2018weird\u2019 behavior using read committed; it doesn\u2019t. However, it can reduce some forms of inconsistencies. In <a href=\"http:\/\/www.itprotoday.com\/microsoft-sql-server\/inconsistent-analysis-clustered-indexes\" target=\"_blank\" rel=\"noopener noreferrer\">this old blog post<\/a>, I demonstrate a scenario where a scan of clustered index returns four rows when only three rows exist, using read committed. Kimberly also describes and shows \u201cinconsistent analysis\u201d in these blog posts:<\/p>\n<ul style=\"text-align: justify;\">\n<li><a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/locking-isolation-and-read-consistency\/\" target=\"_blank\" rel=\"noopener noreferrer\">Locking, isolation, and read consistency<\/a><\/li>\n<li><a href=\"https:\/\/www.sqlskills.com\/blogs\/kimberly\/inconsistent-analysis-in-read-committed-using-locking\/\" target=\"_blank\" rel=\"noopener noreferrer\">Inconsistent analysis in read committed using locking<\/a><\/li>\n<\/ul>\n<p style=\"text-align: justify;\"><strong>So to summarize: <\/strong>the read committed isolation guarantees that only committed data is read; however, there are many forms of potential concerns (known as \u201cinconsistent analysis\u201d). It really pays to understand what these are and what the various isolation levels that SQL Server provides do and do not allow. In addition to the blog posts above, check out the MSDN lesson titled: <em>Understanding the Available Transaction Isolation Levels<\/em> <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/cc546518.aspx\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A while back\u00a0I was involved in an email thread where people were wondering about some \u2018weird\u2019 behavior from SQL Server. The problem was occurring on SQL Server 2016 and used the default isolation level of read committed. The scenario was as follows: Create a table with a few columns Batch 1: In one SSMS window, [&hellip;]<\/p>\n","protected":false},"author":5,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[48,61],"tags":[],"class_list":["post-4884","post","type-post","status-publish","format-standard","hentry","category-inside-the-storage-engine","category-misconceptions"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Read committed doesn&#039;t guarantee much... - 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\/read-committed-doesnt-guarantee-much\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Read committed doesn&#039;t guarantee much... - Paul S. Randal\" \/>\n<meta property=\"og:description\" content=\"A while back\u00a0I was involved in an email thread where people were wondering about some \u2018weird\u2019 behavior from SQL Server. The problem was occurring on SQL Server 2016 and used the default isolation level of read committed. The scenario was as follows: Create a table with a few columns Batch 1: In one SSMS window, [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/\" \/>\n<meta property=\"og:site_name\" content=\"Paul S. Randal\" \/>\n<meta property=\"article:published_time\" content=\"2018-04-17T19:05:30+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=\"5 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\/read-committed-doesnt-guarantee-much\/\",\"url\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/\",\"name\":\"Read committed doesn't guarantee much... - Paul S. Randal\",\"isPartOf\":{\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/#website\"},\"datePublished\":\"2018-04-17T19:05:30+00:00\",\"author\":{\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/#\/schema\/person\/ffcec826c18782e1e0adf173826a7fce\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.sqlskills.com\/blogs\/paul\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Read committed doesn&#8217;t guarantee much&#8230;\"}]},{\"@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":"Read committed doesn't guarantee much... - 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\/read-committed-doesnt-guarantee-much\/","og_locale":"en_US","og_type":"article","og_title":"Read committed doesn't guarantee much... - Paul S. Randal","og_description":"A while back\u00a0I was involved in an email thread where people were wondering about some \u2018weird\u2019 behavior from SQL Server. The problem was occurring on SQL Server 2016 and used the default isolation level of read committed. The scenario was as follows: Create a table with a few columns Batch 1: In one SSMS window, [&hellip;]","og_url":"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/","og_site_name":"Paul S. Randal","article_published_time":"2018-04-17T19:05:30+00:00","author":"Paul Randal","twitter_misc":{"Written by":"Paul Randal","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/","url":"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/","name":"Read committed doesn't guarantee much... - Paul S. Randal","isPartOf":{"@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/#website"},"datePublished":"2018-04-17T19:05:30+00:00","author":{"@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/#\/schema\/person\/ffcec826c18782e1e0adf173826a7fce"},"breadcrumb":{"@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.sqlskills.com\/blogs\/paul\/read-committed-doesnt-guarantee-much\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.sqlskills.com\/blogs\/paul\/"},{"@type":"ListItem","position":2,"name":"Read committed doesn&#8217;t guarantee much&#8230;"}]},{"@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\/4884","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=4884"}],"version-history":[{"count":0,"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/posts\/4884\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/media?parent=4884"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/categories?post=4884"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/paul\/wp-json\/wp\/v2\/tags?post=4884"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}