<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gunjit Chhatwal</title>
    <description>The latest articles on DEV Community by Gunjit Chhatwal (@gunjitchhatwal).</description>
    <link>https://dev.to/gunjitchhatwal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4235817%2F355d476c-a446-49d3-89a1-b3d6b8dc3d94.png</url>
      <title>DEV Community: Gunjit Chhatwal</title>
      <link>https://dev.to/gunjitchhatwal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gunjitchhatwal"/>
    <language>en</language>
    <item>
      <title>SQL Server Locks: A Performance and Deadlock Cheat Sheet</title>
      <dc:creator>Gunjit Chhatwal</dc:creator>
      <pubDate>Tue, 10 Nov 2026 04:08:34 +0000</pubDate>
      <link>https://dev.to/gunjitchhatwal/sql-server-locks-a-performance-and-deadlock-cheat-sheet-1am9</link>
      <guid>https://dev.to/gunjitchhatwal/sql-server-locks-a-performance-and-deadlock-cheat-sheet-1am9</guid>
      <description>&lt;p&gt;SQL Server uses locks to protect data while concurrent sessions read and modify it. Locks preserve transaction correctness, but they also create waiting, blocking, and deadlocks. When evaluating a slow query, ask: "Is it waiting for another transaction, and what resource does it need?"&lt;/p&gt;

&lt;p&gt;This cheat sheet explains the lock types you are most likely to encounter, how they affect query execution, and how to diagnose locking problems quickly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Locks are not latches.&lt;/strong&gt; Locks protect logical data and transaction consistency. Latches are lightweight internal synchronization mechanisms that protect in-memory structures such as data pages. A lock problem commonly appears as an &lt;code&gt;LCK_M_*&lt;/code&gt; wait; a latch problem uses waits such as &lt;code&gt;PAGELATCH_*&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The basic locking flow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F23ncwbqodmyoj7pbbuhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F23ncwbqodmyoj7pbbuhj.png" alt=" " width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A lock request is granted when compatible with locks already held on the resource. Otherwise, the session waits. Lock duration depends on its mode, isolation level, statement, and transaction scope. A common issue is an application modifying data and then remaining idle before committing; the statement finished, but its locks remain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lock modes you should recognize
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lock&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Typical purpose&lt;/th&gt;
&lt;th&gt;Performance implication&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Reading data&lt;/td&gt;
&lt;td&gt;Compatible with other shared locks, but blocked by exclusive locks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;U&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update&lt;/td&gt;
&lt;td&gt;Reading a row before updating it&lt;/td&gt;
&lt;td&gt;Reduces conversion deadlocks; only one update lock can target a resource&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exclusive&lt;/td&gt;
&lt;td&gt;Inserting, updating, or deleting data&lt;/td&gt;
&lt;td&gt;Blocks both readers and writers under locking-based isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Intent Shared&lt;/td&gt;
&lt;td&gt;Signals shared locks at a lower level&lt;/td&gt;
&lt;td&gt;Enables efficient compatibility checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Intent Exclusive&lt;/td&gt;
&lt;td&gt;Signals exclusive or update locks below&lt;/td&gt;
&lt;td&gt;Common while rows or pages are modified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SIX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shared with Intent Exclusive&lt;/td&gt;
&lt;td&gt;Reads the larger resource while modifying lower-level resources&lt;/td&gt;
&lt;td&gt;Restrictive; can create broad blocking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sch-S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Schema Stability&lt;/td&gt;
&lt;td&gt;Compiling and executing queries&lt;/td&gt;
&lt;td&gt;Compatible with normal data access, but conflicts with schema modification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sch-M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Schema Modification&lt;/td&gt;
&lt;td&gt;DDL and some metadata changes&lt;/td&gt;
&lt;td&gt;Highly restrictive; can block nearly all access to the object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BU&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bulk Update&lt;/td&gt;
&lt;td&gt;Bulk-loading data under qualifying options&lt;/td&gt;
&lt;td&gt;Allows optimized bulk operations but restricts competing access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Range*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key-range locks&lt;/td&gt;
&lt;td&gt;Protects index ranges under &lt;code&gt;SERIALIZABLE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Prevents phantom rows but can lock gaps and increase blocking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Shared, update, and exclusive locks
&lt;/h3&gt;

&lt;p&gt;A shared lock allows concurrent readers. An exclusive lock prevents another session from reading or changing the resource under conventional &lt;code&gt;READ COMMITTED&lt;/code&gt; locking. Update locks are an intermediate mode used when reading data that may be changed. They help avoid two sessions acquiring shared locks and both attempting to convert them to exclusive locks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgfif88809u5pj4ghmf61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgfif88809u5pj4ghmf61.png" alt=" " width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Intent locks
&lt;/h3&gt;

&lt;p&gt;Intent locks describe activity lower in the lock hierarchy. An &lt;code&gt;IX&lt;/code&gt; table lock usually means exclusive locks exist, or will exist, on pages, keys, or rows. It does not mean the whole table is exclusively locked. Intent modes quickly reveal whether a table-level request conflicts with lower-level activity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema locks
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Sch-S&lt;/code&gt; locks occur during query compilation and execution, even with &lt;code&gt;NOLOCK&lt;/code&gt;. &lt;code&gt;Sch-M&lt;/code&gt; locks occur during schema changes and conflict with &lt;code&gt;Sch-S&lt;/code&gt;. A long query can delay DDL, while schema modification can block ordinary queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key-range locks
&lt;/h3&gt;

&lt;p&gt;Under &lt;code&gt;SERIALIZABLE&lt;/code&gt;, SQL Server may lock index keys and gaps between them. This prevents inserts that would newly satisfy an earlier predicate. Key-range locks provide strong consistency but can greatly expand the protected area, especially without supporting indexes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lock granularity: what is being locked?
&lt;/h2&gt;

&lt;p&gt;SQL Server can lock resources at several levels:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs40421hqpzcmopnyxz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs40421hqpzcmopnyxz4.png" alt=" " width="720" height="844"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;KEY&lt;/strong&gt; locks protect index entries. Even queries described as "row locking" often show &lt;code&gt;KEY&lt;/code&gt; resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RID&lt;/strong&gt; locks protect rows in a heap, which is a table without a clustered index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PAGE&lt;/strong&gt; locks protect an 8 KB data or index page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OBJECT&lt;/strong&gt; locks protect a table or similar object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HoBT&lt;/strong&gt; locks protect a heap or B-tree, including an individual partition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;METADATA&lt;/strong&gt;, &lt;strong&gt;DATABASE&lt;/strong&gt;, and &lt;strong&gt;ALLOCATION_UNIT&lt;/strong&gt; locks protect broader internal resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fine-grained locks improve concurrency but consume memory. Coarser locks use fewer resources but block more operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lock escalation
&lt;/h2&gt;

&lt;p&gt;Lock escalation replaces many fine-grained locks with a coarser lock. SQL Server attempts escalation when a single statement acquires at least roughly 5,000 locks on one table or index reference, and it can also escalate under lock-memory pressure. Escalation is normally to a table lock; a partitioned table can escalate to a partition when &lt;code&gt;LOCK_ESCALATION = AUTO&lt;/code&gt;. SQL Server does not escalate row or key locks to page locks.&lt;/p&gt;

&lt;p&gt;If another session holds an incompatible lock that prevents escalation, the statement continues with fine-grained locks and SQL Server retries later. Escalation can turn a large modification into a whole-table blocker. Reduce its likelihood with shorter transactions, batched changes, and indexes that prevent unnecessary scans. Disabling escalation can exchange blocking for excessive lock memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compatibility at a glance
&lt;/h2&gt;

&lt;p&gt;The full compatibility matrix is extensive, but this reduced view covers common investigations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Held \ Requested&lt;/th&gt;
&lt;th&gt;&lt;code&gt;S&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;U&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;X&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;IS&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;IX&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;U&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compatibility alone does not explain every wait. Locks may cover different resources, and a request can wait behind another waiter because SQL Server generally preserves queue fairness.&lt;/p&gt;

&lt;h2&gt;
  
  
  How isolation levels change locking
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Isolation level&lt;/th&gt;
&lt;th&gt;Important locking behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;READ UNCOMMITTED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allows dirty reads and usually avoids shared data locks; rows can be missing, duplicated, or inconsistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;READ COMMITTED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SQL Server default; shared row/page locks are generally released as data is read, allowing later rereads to change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REPEATABLE READ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Holds shared locks until transaction end, preventing changed or deleted rereads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SERIALIZABLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Adds key-range protection and prevents phantoms; highest blocking risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SNAPSHOT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Uses row versions for reads; update conflicts are possible; writes still use locks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;READ COMMITTED SNAPSHOT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Changes &lt;code&gt;READ COMMITTED&lt;/code&gt; reads to use row versions; writers still lock&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;SNAPSHOT&lt;/code&gt; requires &lt;code&gt;ALLOW_SNAPSHOT_ISOLATION&lt;/code&gt; to be enabled. Azure SQL Database commonly enables &lt;code&gt;READ_COMMITTED_SNAPSHOT&lt;/code&gt; by default, while boxed SQL Server does not. Row versioning reduces reader-writer blocking but does not eliminate writer-writer blocking, schema conflicts, or poor transaction design. It also adds version-store usage, and competing snapshot updates can fail with error 3960.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;NOLOCK&lt;/code&gt; is not a performance fix.&lt;/strong&gt; It uses &lt;code&gt;READ UNCOMMITTED&lt;/code&gt;, permitting dirty, missing, or duplicated rows, and it still acquires schema-stability locks. Use it only when those correctness risks are explicitly acceptable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Blocking versus deadlocks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Blocking&lt;/strong&gt; is expected synchronization: one session waits until another releases an incompatible lock. &lt;strong&gt;A deadlock&lt;/strong&gt; is a cycle in which sessions wait for one another and none can proceed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgdgb58weoosod1udtgdl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgdgb58weoosod1udtgdl.png" alt=" " width="799" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SQL Server detects the cycle and chooses a victim, which receives error 1205 and is rolled back. Applications should use bounded retries for idempotent operations, but retries do not fix recurring deadlocks.&lt;/p&gt;

&lt;p&gt;Typical fixes include consistent object access order, shorter transactions, indexes that reduce scanned rows, no user interaction inside transactions, and smaller batches. The deadlock graph identifies participating processes, resources, lock modes, and statements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproducible locking labs
&lt;/h2&gt;

&lt;p&gt;Run these examples only on a development SQL Server where you can create a disposable database. They were validated on SQL Server 2022 Developer Edition. Open two query windows and label them &lt;strong&gt;Session A&lt;/strong&gt; and &lt;strong&gt;Session B&lt;/strong&gt;. Run this setup once; &lt;code&gt;DROP TABLE IF EXISTS&lt;/code&gt; requires SQL Server 2016 or later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="no"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;master&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="no"&gt;GO&lt;/span&gt;

&lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="no"&gt;DB_ID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'LockLab'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="kc"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="nc"&gt;LockLab&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="no"&gt;GO&lt;/span&gt;

&lt;span class="no"&gt;USE&lt;/span&gt; &lt;span class="nc"&gt;LockLab&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="no"&gt;GO&lt;/span&gt;

&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="kc"&gt;NULL&lt;/span&gt;
        &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="nc"&gt;PK_LockDemo&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;AccountName&lt;/span&gt; &lt;span class="kt"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="kc"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;Balance&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="kc"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;ReferenceCode&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="kc"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="nc"&gt;IX_LockDemo_ReferenceCode&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReferenceCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AccountName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ReferenceCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Alpha'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Beta'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Gamma'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_read_committed_snapshot_on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;snapshot_isolation_state_desc&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;databases&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DB_NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lab 1: an exclusive lock blocks a reader
&lt;/h3&gt;

&lt;p&gt;In &lt;strong&gt;Session A&lt;/strong&gt;, update a row but leave the transaction open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nc"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Leave this transaction open while running Session B.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;strong&gt;Session B&lt;/strong&gt;, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;READCOMMITTEDLOCK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;READCOMMITTEDLOCK&lt;/code&gt; makes the example deterministic even when &lt;code&gt;READ_COMMITTED_SNAPSHOT&lt;/code&gt; is enabled. Session B waits with &lt;code&gt;LCK_M_S&lt;/code&gt; because Session A holds an exclusive key lock. In a third window, run the diagnostic queries in the next section to see the waiter and blocker. Release the lock in Session A:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ROLLBACK&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Session B then completes and returns the original balance of 100 because Session A rolled back. To demonstrate row versioning, remove &lt;code&gt;READCOMMITTEDLOCK&lt;/code&gt; on a database with &lt;code&gt;READ_COMMITTED_SNAPSHOT&lt;/code&gt; enabled; the reader can return the previously committed version without waiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lab 2: create a two-row deadlock
&lt;/h3&gt;

&lt;p&gt;Run the following in &lt;strong&gt;Session A&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="no"&gt;XACT_ABORT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nc"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="no"&gt;WAITFOR&lt;/span&gt; &lt;span class="no"&gt;DELAY&lt;/span&gt; &lt;span class="s1"&gt;'00:00:15'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nc"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start this in &lt;strong&gt;Session B&lt;/strong&gt; within 15 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="no"&gt;XACT_ABORT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nc"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="no"&gt;WAITFOR&lt;/span&gt; &lt;span class="no"&gt;DELAY&lt;/span&gt; &lt;span class="s1"&gt;'00:00:15'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nc"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each session owns an exclusive lock needed by the other. SQL Server detects the cycle and terminates one session with error 1205; either session can be the victim. &lt;code&gt;DEADLOCK_PRIORITY&lt;/code&gt; is considered first, followed by estimated rollback cost. The survivor commits. The primary fix is to access rows in the same order—for example, always update &lt;code&gt;Id = 1&lt;/code&gt; before &lt;code&gt;Id = 2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a third window, inspect the newest graph from the built-in &lt;code&gt;system_health&lt;/code&gt; session. This requires &lt;code&gt;VIEW SERVER STATE&lt;/code&gt; and applies to SQL Server; Azure SQL Database exposes Extended Events through database-scoped targets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="no"&gt;QUOTED_IDENTIFIER&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nc"&gt;DeadlockEvents&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_data&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="kt"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;target_data&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_xe_session_targets&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_xe_sessions&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
        &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_session_address&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'system_health'&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'ring_buffer'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;event_xml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'(@timestamp)[1]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'datetime2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_xml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'(data/value/deadlock)[1]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;deadlock_graph&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nc"&gt;DeadlockEvents&lt;/span&gt;
&lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="no"&gt;APPLY&lt;/span&gt; &lt;span class="n"&gt;target_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'//RingBufferTarget/event[@name="xml_deadlock_report"]'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nf"&gt;events&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reset the data and connection setting in both windows before continuing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="no"&gt;XACT_STATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;ROLLBACK&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="no"&gt;XACT_ABORT&lt;/span&gt; &lt;span class="no"&gt;OFF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nc"&gt;Balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lab 3: a serializable range blocks an insert
&lt;/h3&gt;

&lt;p&gt;In &lt;strong&gt;Session A&lt;/strong&gt;, protect a range that currently contains one row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt; &lt;span class="no"&gt;ISOLATION&lt;/span&gt; &lt;span class="no"&gt;LEVEL&lt;/span&gt; &lt;span class="no"&gt;SERIALIZABLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nc"&gt;ReferenceCode&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Leave the transaction open while running Session B.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;strong&gt;Session B&lt;/strong&gt;, attempt to insert into the protected range:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AccountName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ReferenceCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Delta'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The insert waits on a range-insert lock. On SQL Server 2022 this appears as &lt;code&gt;LCK_M_RIn_NL&lt;/code&gt;; older versions and tools may display related range-insert names differently. Session A holds a &lt;code&gt;RangeS-S&lt;/code&gt; lock on the supporting index, protecting existing keys and gaps where qualifying rows could be inserted. Finish Session A:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COMMIT&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt; &lt;span class="no"&gt;ISOLATION&lt;/span&gt; &lt;span class="no"&gt;LEVEL&lt;/span&gt; &lt;span class="no"&gt;READ&lt;/span&gt; &lt;span class="no"&gt;COMMITTED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Session B can now insert the row. Remove it before repeating the lab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These labs illustrate three different diagnoses: incompatible locks causing ordinary blocking, a circular wait causing a deadlock, and an isolation-level guarantee deliberately expanding the locked resource range.&lt;/p&gt;

&lt;p&gt;After completing the labs, clean up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="no"&gt;USE&lt;/span&gt; &lt;span class="nc"&gt;LockLab&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="nd"&gt;@@TRANCOUNT&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;ROLLBACK&lt;/span&gt; &lt;span class="k"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LockDemo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Optional, from a window connected to master:&lt;/span&gt;
&lt;span class="c1"&gt;-- USE master;&lt;/span&gt;
&lt;span class="c1"&gt;-- DROP DATABASE LockLab;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick diagnostic queries
&lt;/h2&gt;

&lt;p&gt;The following server-scoped DMVs generally require &lt;code&gt;VIEW SERVER STATE&lt;/code&gt; (&lt;code&gt;VIEW SERVER PERFORMANCE STATE&lt;/code&gt; for some DMVs on SQL Server 2022+) or the corresponding Azure SQL database permission. &lt;code&gt;wait_time&lt;/code&gt; is reported in milliseconds.&lt;/p&gt;

&lt;p&gt;Find waiting requests and their blockers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blocking_session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;DB_NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;database_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;database_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;text&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;current_sql&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_exec_requests&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="no"&gt;APPLY&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dm_exec_sql_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sql_handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blocking_session_id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
   &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_type&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'LCK_M_%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect current locks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;OBJECT_NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_database_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;object_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_status&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_tran_locks&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partitions&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'PAGE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'RID'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'HOBT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hobt_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_associated_entity_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_database_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DB_ID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The head blocker is often sleeping and therefore absent from &lt;code&gt;sys.dm_exec_requests&lt;/code&gt;. Find its open transaction and most recent SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;login_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;program_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_transaction_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction_begin_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;text&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;most_recent_sql&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_exec_sessions&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_tran_session_transactions&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_tran_active_transactions&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction_id&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_exec_connections&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;
&lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="no"&gt;APPLY&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dm_exec_sql_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;most_recent_sql_handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;txt&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;blocking_session_id&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_exec_requests&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;blocking_session_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common waits provide a fast clue:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Wait&lt;/th&gt;
&lt;th&gt;Usually means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LCK_M_S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Waiting for a shared lock, often a reader behind a writer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;LCK_M_U&lt;/code&gt; / &lt;code&gt;LCK_M_X&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Waiting to update or exclusively modify data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LCK_M_SCH_M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Waiting for a schema modification lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LCK_M_RS_S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Waiting for a serializable range shared lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;LCK_M_RIn_NL&lt;/code&gt; and related &lt;code&gt;LCK_M_RI*&lt;/code&gt; waits&lt;/td&gt;
&lt;td&gt;Waiting to insert into a protected key range; the exact name varies by engine version&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use the results together. A granted lock is not automatically a problem; identify the waiter, head blocker, transaction age, and SQL. &lt;code&gt;sys.dm_os_waiting_tasks&lt;/code&gt; provides another resource-level view. For historical deadlocks, use &lt;code&gt;system_health&lt;/code&gt; or a dedicated Extended Events session capturing &lt;code&gt;xml_deadlock_report&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance review checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Confirm waiting:&lt;/strong&gt; Look for &lt;code&gt;LCK_M_*&lt;/code&gt; waits rather than assuming high duration means blocking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the head blocker:&lt;/strong&gt; Follow &lt;code&gt;blocking_session_id&lt;/code&gt; until reaching the session that is not itself blocked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check transaction scope:&lt;/strong&gt; Determine whether the blocker has an old open transaction or is idle while holding locks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify the resource and mode:&lt;/strong&gt; Use &lt;code&gt;wait_resource&lt;/code&gt;, &lt;code&gt;sys.dm_tran_locks&lt;/code&gt;, and the execution plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review access paths:&lt;/strong&gt; Scans lock more rows and hold locks longer than selective seeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check isolation requirements:&lt;/strong&gt; Use stronger isolation only when the business invariant requires it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capture the deadlock graph:&lt;/strong&gt; Do not diagnose recurring deadlocks from the victim statement alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix the cause, not the symptom:&lt;/strong&gt; Prefer indexing, consistent access order, batching, and shorter transactions over &lt;code&gt;NOLOCK&lt;/code&gt; or killing sessions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Locks are necessary and waiting can be normal. Prolonged blocking usually reflects transaction scope, access paths, or concurrency design. Diagnose the wait chain, then reduce the locked resource set or the time locks are held.&lt;/p&gt;

</description>
      <category>sqlserver</category>
      <category>database</category>
      <category>performance</category>
    </item>
    <item>
      <title>Microsoft.Testing.Platform: How .NET Testing Evolved</title>
      <dc:creator>Gunjit Chhatwal</dc:creator>
      <pubDate>Mon, 09 Nov 2026 20:35:04 +0000</pubDate>
      <link>https://dev.to/gunjitchhatwal/microsofttestingplatform-how-net-testing-evolved-5652</link>
      <guid>https://dev.to/gunjitchhatwal/microsofttestingplatform-how-net-testing-evolved-5652</guid>
      <description>&lt;p&gt;For most .NET developers, running tests has long appeared simple: add MSTest, NUnit, or xUnit.net, write a few test methods, and run &lt;code&gt;dotnet test&lt;/code&gt;. Under that command, however, sits a test platform responsible for discovering tests, loading adapters, starting processes, collecting results, and integrating with editors and CI systems.&lt;/p&gt;

&lt;p&gt;That distinction matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;test framework&lt;/strong&gt; defines how tests are written. Examples include MSTest, NUnit, xUnit.net, and TUnit.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;test platform&lt;/strong&gt; discovers and executes those tests and connects them to command-line tools, IDEs, and build pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many years, VSTest was the standard platform behind .NET testing. Microsoft.Testing.Platform (MTP) is its modern, lightweight alternative. It does not replace test frameworks; instead, it changes how their tests are hosted and executed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How .NET testing reached this point
&lt;/h2&gt;

&lt;p&gt;The earliest .NET test runners were closely associated with their frameworks or development environments. Tests often ran through a framework-specific console runner or directly inside Visual Studio. This worked well when applications primarily targeted the .NET Framework and ran on Windows.&lt;/p&gt;

&lt;p&gt;VSTest brought valuable consistency. It gave Visual Studio, command-line tools, CI systems, and third-party frameworks a common execution platform. Test adapters allowed MSTest, NUnit, xUnit.net, and other frameworks to participate in the same workflow.&lt;/p&gt;

&lt;p&gt;The arrival of .NET Core in 2016 changed the environment. .NET became cross-platform, side-by-side versioned, container-friendly, and increasingly optimized for deployment models such as trimming and Native AOT. Test projects could now target many runtimes and architectures, while runners had to handle more combinations of SDKs, hosts, adapters, extensions, and process boundaries.&lt;/p&gt;

&lt;p&gt;VSTest continued to evolve, but its architecture came from a period when an external runner loading test adapters was the natural model. Supporting modern .NET form factors within that model added complexity. The result could be subtle differences between local, IDE, and CI execution, along with difficult assembly-loading or extension-discovery problems.&lt;/p&gt;

&lt;p&gt;Microsoft introduced MTP through the MSTest Runner work and expanded it into a platform supported by the major .NET test frameworks. In .NET 10, &lt;code&gt;dotnet test&lt;/code&gt; gained native MTP-aware orchestration, making the new platform feel increasingly like a first-class part of the SDK rather than a separate experiment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8jrjxkbexmjjxv50oda4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8jrjxkbexmjjxv50oda4.png" alt="evolution of testing in dotnet" width="602" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The architectural shift: the test project becomes an application
&lt;/h2&gt;

&lt;p&gt;The most important MTP idea is surprisingly simple: a test project is built as an executable test application.&lt;/p&gt;

&lt;p&gt;With the traditional VSTest model, an external runner discovers an adapter, loads the test assembly, and coordinates execution. With MTP, the platform and framework runner are embedded into the test project. The resulting application can participate in &lt;code&gt;dotnet test&lt;/code&gt;, but it can also be started with &lt;code&gt;dotnet run&lt;/code&gt; or executed directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdz4y4t0bxbyj63irbezb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdz4y4t0bxbyj63irbezb.png" alt="test framework architecture" width="800" height="643"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This design reduces the number of hidden decisions made at run time. Frameworks and extensions are normally registered at build time, so the application knows what it contains before execution begins. The same test artifact can run locally or in CI without depending on a separately installed console runner.&lt;/p&gt;

&lt;h2&gt;
  
  
  What improves with Microsoft.Testing.Platform?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. More deterministic execution
&lt;/h3&gt;

&lt;p&gt;MTP emphasizes compile-time registration instead of discovering arbitrary plugins through reflection at run time. This makes configuration errors visible earlier and reduces environment-dependent behavior. The test application carries its intended runner and extensions with it, improving the chance that the same build behaves consistently on a developer machine and a build agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A smaller and more transparent runtime
&lt;/h3&gt;

&lt;p&gt;The MTP core is a single assembly with no dependencies beyond supported .NET runtimes. It avoids creating isolation mechanisms such as &lt;code&gt;AppDomain&lt;/code&gt; or &lt;code&gt;AssemblyLoadContext&lt;/code&gt; simply to coordinate a run, and it does not rely on custom assembly resolution for its core operation. That reduces interference between the platform and the framework executing the tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Executable-first workflows
&lt;/h3&gt;

&lt;p&gt;Because an MTP test project behaves like a normal .NET application, it works naturally with familiar application workflows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet &lt;span class="nb"&gt;test
&lt;/span&gt;dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; tests/MyApp.Tests
./tests/MyApp.Tests/bin/Release/net8.0/MyApp.Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for containers, specialized devices, browser-like hosts, startup-project debugging, and environments where installing or locating an external runner is undesirable.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Better alignment with modern .NET
&lt;/h3&gt;

&lt;p&gt;MTP was designed for current and future .NET form factors, including trimming and Native AOT scenarios. Its hostable architecture also allows the test runtime to run inside different application types rather than assuming every test must be controlled by a conventional console host.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Modular capabilities instead of a large default surface
&lt;/h3&gt;

&lt;p&gt;The platform focuses on orchestration and adds capabilities through extensions. Available extensions cover TRX, HTML, JUnit, CTRF and CI-native reporting, code coverage, crash and hang dumps, retries, Hot Reload, OpenTelemetry, Microsoft Fakes, and more.&lt;/p&gt;

&lt;p&gt;When standard MSBuild integration is enabled, installing an extension package is generally enough for it to be detected and registered. Teams can therefore include only the capabilities they need instead of paying for every feature in every test process.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Framework choice remains open
&lt;/h3&gt;

&lt;p&gt;MTP is not limited to MSTest. MSTest, NUnit, xUnit.net, and TUnit all support it, while TUnit is built directly on top of MTP. This lets teams modernize test execution without necessarily rewriting their test suites or abandoning their preferred assertion and test-authoring model.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical before-and-after example
&lt;/h2&gt;

&lt;p&gt;Consider a small pricing service. The production code applies a percentage discount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PriceCalculator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;ApplyDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;percentage&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;percentage&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its MSTest test is intentionally ordinary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.VisualStudio.TestTools.UnitTesting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestClass&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PriceCalculatorTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestMethod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ApplyDiscount_ReducesPrice&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PriceCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ApplyDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;15m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AreEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;170m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test itself does not need to change when moving from VSTest to MTP. The difference is in how the test project is assembled and executed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: a conventional VSTest project
&lt;/h3&gt;

&lt;p&gt;A representative VSTest-based project explicitly combines the .NET SDK, the test platform package, the framework, and its adapter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net10.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;IsTestProject&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/IsTestProject&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;IsPackable&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/IsPackable&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Test.Sdk"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"18.11.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"MSTest.TestAdapter"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"4.1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"MSTest.TestFramework"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"4.1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The typical CI command asks VSTest to load a logger and a data collector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--configuration&lt;/span&gt; Release &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--logger&lt;/span&gt; trx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--collect&lt;/span&gt; &lt;span class="s2"&gt;"Code Coverage;Format=cobertura"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a mature and widely supported workflow. However, the test artifact is primarily a library intended to be understood and launched by an external test platform. The runner must locate the adapter, interpret VSTest options, start a test host, and coordinate collection and reporting.&lt;/p&gt;

&lt;h3&gt;
  
  
  After: the same suite as an MTP application
&lt;/h3&gt;

&lt;p&gt;MSTest.Sdk bundles the recommended MSTest setup and uses MTP by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"MSTest.Sdk/4.1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net10.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For native MTP orchestration in .NET 10 or later, the repository selects the runner in &lt;code&gt;global.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"runner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft.Testing.Platform"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The equivalent CI command becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--configuration&lt;/span&gt; Release &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--report-trx&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--coverage&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--coverage-output-format&lt;/span&gt; cobertura
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The options now describe capabilities owned by the test application: TRX reporting and code coverage. With the default MSTest.Sdk profile, the required Microsoft extensions are already included. If a team uses a minimal profile, it can add only the extension packages it wants.&lt;/p&gt;

&lt;p&gt;More importantly, the compiled test application is not limited to &lt;code&gt;dotnet test&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Familiar solution or project orchestration&lt;/span&gt;
dotnet &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--project&lt;/span&gt; tests/Pricing.Tests/Pricing.Tests.csproj

&lt;span class="c"&gt;# Launch it like another .NET application&lt;/span&gt;
dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; tests/Pricing.Tests/Pricing.Tests.csproj

&lt;span class="c"&gt;# Build once, then run the produced test executable&lt;/span&gt;
dotnet build tests/Pricing.Tests &lt;span class="nt"&gt;-c&lt;/span&gt; Release
./tests/Pricing.Tests/bin/Release/net10.0/Pricing.Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, the final command uses &lt;code&gt;Pricing.Tests.exe&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6tukr492v268ha62w6o7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6tukr492v268ha62w6o7.png" alt="executable test artifacts" width="800" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What improved in this example?
&lt;/h3&gt;

&lt;p&gt;First, the project configuration has fewer independently managed test packages. The SDK supplies a coherent runner, framework, and default extension profile. Second, the test output is self-describing: the runner and registered extensions travel with the build instead of being discovered from a machine-specific adapter setup. Third, local debugging and CI execution are closer because both launch the same test application. Finally, stricter defaults expose mistakes sooner—for example, MTP returns a non-success exit code when a test application executes zero tests, while VSTest normally treats that situation as successful.&lt;/p&gt;

&lt;p&gt;The improvement is primarily architectural and operational. A single &lt;code&gt;Assert.AreEqual&lt;/code&gt; is not inherently faster because it runs on MTP. The gains appear around startup composition, predictable extension loading, artifact portability, parallel module orchestration, diagnostics, and reduced differences between execution environments. Actual performance still depends on the number of projects, framework behavior, test setup, I/O, and enabled extensions, so teams should benchmark their own suites rather than rely on a universal speed claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrying individual tests in a test pipeline
&lt;/h2&gt;

&lt;p&gt;Consider a long-running test pipeline that exercises a deployed service, including authentication, data import, background processing, and persistence. Such pipelines are expensive to repeat because each test can involve network calls, external resources, and significant setup.&lt;/p&gt;

&lt;p&gt;Before the migration, a task-level retry was used when a test failed. If one test failed near the end of the run, the pipeline repeated the entire task—including tests that had already passed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03ercwmcn4f48vb0bv4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03ercwmcn4f48vb0bv4t.png" alt="individual test retry" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After moving the test project to MTP, the suite can add the retry extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.Testing.Extensions.Retry"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline could then request up to two retries for failed tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--retry-failed-tests&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--retry-failed-tests-max-percentage&lt;/span&gt; 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The percentage limit prevents a broadly broken suite from repeatedly retrying a large number of failures. Smaller suites can choose a different policy because one failed test might already exceed a 10% threshold.&lt;/p&gt;

&lt;p&gt;This changes the retry boundary from the pipeline task to the individual test. A transient failure no longer requires every successful test to run again, reducing wasted agent time and repeated calls to deployed resources. A pipeline can also merge the per-attempt TRX files into a final report, retaining the final outcome while identifying tests that failed initially and passed only on retry.&lt;/p&gt;

&lt;p&gt;Per-test retries should not be used to normalize flaky tests. A test that passes on retry is still a reliability signal that should be visible and investigated. The value of the MTP extension is that retry behavior becomes an explicit, configurable part of the test application instead of a coarse pipeline-level rerun.&lt;/p&gt;

&lt;p&gt;This improvement is specifically attributable to adopting the MTP retry extension. By contrast, building test projects once and reusing their artifacts was already possible with VSTest; MTP makes those artifacts directly executable and more self-describing, but it did not introduce the concept of prebuilt tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  MTP compared with the common alternatives
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;VSTest&lt;/th&gt;
&lt;th&gt;Microsoft.Testing.Platform&lt;/th&gt;
&lt;th&gt;Framework-specific/custom runner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;External platform plus adapters&lt;/td&gt;
&lt;td&gt;Platform embedded in an executable test app&lt;/td&gt;
&lt;td&gt;Varies by framework or vendor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecosystem maturity&lt;/td&gt;
&lt;td&gt;Long-established and broadly integrated&lt;/td&gt;
&lt;td&gt;Rapidly growing, with major .NET frameworks supported&lt;/td&gt;
&lt;td&gt;Often strong within a narrower ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime behavior&lt;/td&gt;
&lt;td&gt;Compatibility-oriented and dynamic&lt;/td&gt;
&lt;td&gt;Explicit, modular, and build-time registered&lt;/td&gt;
&lt;td&gt;Depends on implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modern deployment&lt;/td&gt;
&lt;td&gt;Limited for Native AOT and trimming scenarios&lt;/td&gt;
&lt;td&gt;Designed for modern .NET form factors&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed-language testing&lt;/td&gt;
&lt;td&gt;Supports .NET and non-.NET adapter scenarios&lt;/td&gt;
&lt;td&gt;Focused on .NET&lt;/td&gt;
&lt;td&gt;Usually specialized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct execution&lt;/td&gt;
&lt;td&gt;Normally driven by VSTest tooling&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dotnet run&lt;/code&gt; or direct executable supported&lt;/td&gt;
&lt;td&gt;Often supported by dedicated commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI migration cost&lt;/td&gt;
&lt;td&gt;Usually lowest for existing pipelines&lt;/td&gt;
&lt;td&gt;Requires checking arguments, reports, and extensions&lt;/td&gt;
&lt;td&gt;May require vendor-specific integration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tools such as ReSharper, Rider, NCrunch, and IDE test explorers should not always be viewed as competing platforms. They are developer experiences that may integrate with one or more underlying test technologies. The practical question is not only “Which button runs my tests?” but also “Which execution model produces and controls the test process?”&lt;/p&gt;

&lt;h2&gt;
  
  
  Should every project migrate now?
&lt;/h2&gt;

&lt;p&gt;Not automatically. VSTest remains a sound choice when a repository depends on mature integrations, custom VSTest adapters, existing &lt;code&gt;.runsettings&lt;/code&gt; behavior, or mixed-language test execution such as C++ and JavaScript alongside .NET. Some third-party tooling still has deeper VSTest integration.&lt;/p&gt;

&lt;p&gt;MTP is especially compelling for new .NET test projects, teams targeting Native AOT or trimming, containerized test artifacts, or repositories experiencing VSTest-specific loading and extension problems. It is also attractive when reproducibility and explicit composition are more important than maximum backward compatibility.&lt;/p&gt;

&lt;p&gt;Migration should be deliberate. Choose one platform consistently for a solution or run configuration, verify framework support, map command-line arguments, and replace VSTest loggers or collectors with the corresponding MTP extensions. Do not assume every VSTest switch has an identical MTP equivalent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The larger improvement
&lt;/h2&gt;

&lt;p&gt;Microsoft.Testing.Platform is not merely a faster command or a new adapter. Its real contribution is making test execution look like the rest of modern .NET development: build an application with known dependencies, run the same artifact in different environments, and add capabilities through explicit packages.&lt;/p&gt;

&lt;p&gt;VSTest solved the earlier problem of unifying a fragmented testing ecosystem. MTP addresses the next problem: keeping that ecosystem predictable, portable, and extensible as .NET itself moves into more runtimes, deployment models, and development environments.&lt;/p&gt;

&lt;p&gt;For most teams, that means evolution rather than revolution. Existing test frameworks remain useful, &lt;code&gt;dotnet test&lt;/code&gt; remains familiar, and migration can happen project by project. What changes is the foundation underneath—and that foundation is now much closer to the application model .NET developers already understand.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>dotnet</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
