{"id":1749,"date":"2014-01-16T00:00:00","date_gmt":"2014-01-16T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/sql-server-deadlocks-by-example\/"},"modified":"2026-03-18T14:33:01","modified_gmt":"2026-03-18T14:33:01","slug":"sql-server-deadlocks-by-example","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/sql-server-deadlocks-by-example\/","title":{"rendered":"SQL Server Deadlocks: Types, Causes &#038; Prevention Guide"},"content":{"rendered":"<div class=\"article-content\">\n<p>A SQL Server deadlock occurs when two or more sessions form a circular lock chain &#8211; each session holds a lock that the other needs, and neither can proceed. SQL Server automatically detects deadlocks via its lock monitor and kills one session (the \u201cdeadlock victim,\u201d error 1205), rolling back its transaction. The key difference from blocking: blocking is transient (the head blocker will eventually finish), while a deadlock is permanent without intervention.<\/p>\n<p>This guide demonstrates the most common deadlock types &#8211; bookmark deadlocks, serializable key-range deadlocks, and cascading multi-session deadlocks &#8211; with reproducible code and deadlock graphs you can run on your own instance, plus strategies for preventing each type.<\/p>\n<h2>Introduction<\/h2>\n<p>For each type of deadlock, we&#8217;ll review &#8216;typical&#8217; deadlock graphs and discuss the signature that distinguishes each one, so that you can recognize it if you see it on your own systems. We&#8217;ll also consider the root causes of each type of deadlock, the code patterns that make them a possibility, how to avoid them recurring, and the need to deal with deadlocks, and all other SQL Server errors gracefully, with error handling and retries.<\/p>\n<h1>The Difference between Severe Blocking and Deadlocking<\/h1>\n<p>In my experience, developers and DBAs often think that their SQL Server instance is experiencing deadlocks when, really, it is experiencing severe blocking.<\/p>\n<p>Blocking occurs when session A requests a lock on a resource (typically a row, page or table), but SQL Server cannot grant that lock because session B already holds a non-compatible lock on that resource.<\/p>\n<p>For example, let&#8217;s assume that session B is in the process of modifying a row in the <code>Invoices<\/code> table. The session&#8217;s associated process (thread) currently holds an Intent-Exclusive (IX) lock on both the table and the page that contains the row, and an X lock on the row. Simultaneously, session A needs to read a few pages on same table. Its associated process acquires an Intent-Shared (IS) lock on the table (since IS and IX lock mode are compatible) and then attempts to acquire an S lock on the pages it needs to read. However, session B&#8217;s process holds an IX lock on one of the pages that contains some of the rows session A needs. S locks and IX locks are incompatible, and so session B&#8217;s thread blocks session A&#8217;s until the former completes its work and releases the locks.<\/p>\n<div class=\"tips note\">\n<h4>Lock modes and lock compatibility<\/h4>\n<p>I don&#8217;t have space in this piece for a fuller discussion of lock modes and compatibility. See <em>Further Reading<\/em> at the end of the article for some useful references.<\/p>\n<\/div>\n<p>This is a transient situation and can be completely resolved by the session B completing its work and releasing its locks. It is possible to have extensive blocking chains where multiple sessions are blocked waiting for a session that itself is blocked waiting for another session that is blocked and so on, repeating multiple time. However, at the head of the blocking chain will be a head &#8216;blocker&#8217; that is not waiting for a lock. It may be waiting for some other resource, such as a latch, memory, or IO, but at least one session will not be waiting for a lock, and the blocking chain will clear as soon as the head blocker can continue processing.<\/p>\n<p>A <strong>deadlock<\/strong> is different; it occurs when two or more sessions are waiting for each other, in such a way that none can complete. A deadlock can be viewed as a circular lock chain, where every single process in the blocking chain is waiting for one or more other processes in that same blocking chain.<\/p>\n<p>Consider the simplest possible deadlock, with two sessions, two processes and two resources (later sections will demonstrate deadlocks that are more complex). A deadlock occurs in two steps. In the first, each of the two processes requests and acquires a lock. This will be within a transaction, explicit or not, and hence neither process will release these locks immediately.<\/p>\n<p>In the second step, each of the two processes requests a lock on the resource on which the competing session holds a lock that is incompatible with the requested lock.<\/p>\n<p>At this point, process 1 cannot continue until it receives the lock that it wants on page 1:4224. It cannot get that lock until process 2 finishes and releases its lock on that page. Process 2 cannot continue until it receives the lock that it wants on page 1:1370. It cannot get that lock until process 1 finishes and releases its lock on that page.<\/p>\n<p>At this point, neither process can proceed; we have a deadlock. Without intervention, these two processes would sit forever waiting for each other. Fortunately, SQL Server automatically detects deadlocks and intervenes on our behalf.<\/p>\n<p><strong>Read also: <\/strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/the-blocked-process-report\/\">SQL Server blocked process report<\/a><\/p>\n<h2>SQL Server&#8217;s Automatic Deadlock Detection and Resolution<\/h2>\n<p>SQL Server&#8217;s lock monitor has a deadlock detector that periodically checks the locks to see if there are any circular locking chains. If it finds any, it selects one of the sessions associated with a suspended thread, kills it, rolls back its transaction and releases its locks. This allows the other session to continue executing.<\/p>\n<p>The killed session, known as the <strong>deadlock victim<\/strong>, receives error 1205:<\/p>\n<p class=\"note\"><span class=\"codered\">Transaction (Process ID 75) was deadlocked on resources with another process and has been chosen as the deadlock victim. Rerun the transaction.<\/span><\/p>\n<p>The lock monitor picks the deadlock victim based, firstly, on the setting of <code>DEADLOCK_PRIORITY<\/code> for each session and, secondly (in the event of a tie) on the amount of work that it will take to roll back each of the open transactions.<\/p>\n<p>The <code>DEADLOCK_PRIORITY<\/code> is a session-scoped setting that establishes the relative importance that the session completes its work should it become embroiled in a deadlock. It can be set to <code>HIGH<\/code>, <code>NORMAL<\/code> or <code>LOW<\/code>, with <code>NORMAL<\/code> being the default. If we&#8217;d prefer SQL Server not to pick a certain session as a deadlock victim, we can set its <code>DEADLOCK_PRIORITY<\/code> to high.<\/p>\n<div class=\"tips note\">\n<h4>Integer values for <code>DEADLOCK_PRIORITY<\/code><\/h4>\n<p>We can also set the <code>DEADLOCK_PRIORITY<\/code> to any integer value between -10 and +10, <code>HIGH<\/code> is equivalent to +5, <code>NORMAL<\/code> to 0 and <code>LOW<\/code> to -5. I recommend sticking with the named options. If someone is setting very fine-grained deadlock granularities, for example setting sessions to deadlock priorities 2, 3 or 7, there is likely a larger problem at play (<em>i.e. <\/em>lots of deadlocks and someone spending a lot of time prioritizing which processes are most important, rather than fixing what is causing the deadlocks).<\/p>\n<\/div>\n<p>If two sessions deadlock, the lock monitor will select as the deadlock victim the one with the lower value for <code>DEADLOCK_PRIORITY<\/code>. If each has identical values for <code>DEADLOCK_PRIORITY<\/code>, then the lock monitor considers the resources required to roll back the &#8216;competing&#8217; transactions; the one that requires the least work to roll back will be the deadlock victim. The lock monitor takes no account of how long a transaction has been running or how much work it has done; just the cost of rolling it back. If a deadlock occurs between session A, which has been running a <code>SELECT<\/code> for an hour, and session B that is running a single-row <code>UPDATE<\/code>, and both have the same <code>DEADLOCK_PRIORITY<\/code>, session A will be the deadlock victim as it made no data changes and hence costs nothing to roll back.<\/p>\n<p><strong>Read also:<br \/><\/strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/extended-events-as-a-private-investigator\/\">Extended Events for monitoring<\/a><br \/><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/the-default-trace-in-sql-server-the-power-of-performance-and-security-auditing\/\">The default trace for auditing<\/a><br \/><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/how-to-avoid-conditional-joins-in-t-sql\/\">How to avoid conditional JOINs<\/a><br \/><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/identifying-and-solving-index-scan-problems\/\">Identifying and solving index scan problems<\/a><\/p>\n<h2>Preventing Deadlocks<\/h2>\n<p>It&#8217;s convenient that SQL Server will detect and resolve deadlocks automatically, but that doesn&#8217;t mean a DBA can just ignore them. After all, the deadlock victim may well be an important business operation and its failure to run will cause considerable disruption.<\/p>\n<p>A DBA needs to know when a deadlock occurs in one of their SQL Server instances by alerting on 1205 errors, to capture the information, the <strong>deadlock graph<\/strong>, which will detail which processes were involved in the deadlock, and then set about trying to ensure that it does not occur again.<\/p>\n<h3>How to Capture a SQL Server Deadlock Graph<\/h3>\n<p>A deadlock graph shows us the sessions and resources that were involved in a deadlock. Rather than repeat information ably covered elsewhere, I&#8217;m going to refer you to Jonathan Kehayias&#8217; article, <a href=\"https:\/\/www.simple-talk.com\/sql\/database-administration\/handling-deadlocks-in-sql-server\/\">Handling Deadlocks in SQL Server<\/a>, for background details of the various techniques by which to capture deadlock graphs, including various Trace Flags, the Profiler deadlock graph event, Service Broker event notifications, and Extended Events.<\/p>\n<p>Prior to SQL Server 2008, if a deadlock occurred in SQL Server, we&#8217;d have to enable trace flags, or set up a server-side trace, and wait for the deadlock to recur. In SQL Server 2008 and later, we can retrieve deadlock graphs retrospectively from the extended events <code>system_health<\/code> session. I used the technique in this article, as frankly it is the most straightforward way to get the deadlock graphs, using Jonathan&#8217;s query. Listing 1 shows how to return the deadlock graph from the <code>ring_buffer<\/code> target the<code><\/code><code>system_health<\/code> event session (the code download also contains the equivalent code for the <code>event_file<\/code> target). This version of the query is specific to SQL Server 2012; see the previously referenced article for the 2008 version.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT XEvent.query('(event\/data\/value\/deadlock)[1]') AS DeadlockGraph \nFROM ( SELECT XEvent.query('.') AS XEvent \n       FROM ( SELECT CAST(target_data AS XML) AS TargetData \n              FROM sys.dm_xe_session_targets st \n                   JOIN sys.dm_xe_sessions s \n                   ON s.address = st.event_session_address \n              WHERE s.name = 'system_health' \n                    AND st.target_name = 'ring_buffer' \n              ) AS Data \n              CROSS APPLY \n                 TargetData.nodes \n                    ('RingBufferTarget\/event[@name=\"xml_deadlock_report\"]')\n              AS XEventData ( XEvent ) \n      ) AS src;<\/pre>\n<p class=\"caption\">Listing 1: Returning the deadlock graph from the <code>system_health<\/code> event session<\/p>\n<p>The deadlock graph obtained from the <code>system_health<\/code> extended events session is extremely similar both to the error log output for <code>traceflag 12<\/code><code>2<\/code><code>2<\/code> and to the XML captured by SQL Profiler when it is tracing for the deadlock graph event.<\/p>\n<h3>How to Read a Deadlock Graph<\/h3>\n<p>In the next section, we&#8217;ll start our tour of common types of deadlocks and their resolution. In order to follow along, you&#8217;ll need to know your way around a deadlock graph, so let&#8217;s take a brief tour.<\/p>\n<p>I generated an example deadlock by executing two stored procedures, <code>UpdateCustomerLatestOrderStatus<\/code> and <code>AddOrder<\/code> (which we&#8217;ll use again, and discuss in more detail, later) though any simple deadlock will do at this early stage, since we&#8217;re only interested at this stage in the overall structure of the resulting deadlock graph.<\/p>\n<p>Generate a deadlock and then run retrieve the deadlock graph, for example by running Listing 1 to retrieve it from the <code>system_health<\/code> event session. Figure 1 shows my deadlock graph, in XML format.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1926-8252646b-cc9d-4f3b-85a6-dff9c3af7003.png\" alt=\"1926-8252646b-cc9d-4f3b-85a6-dff9c3af700\" \/><\/p>\n<p class=\"caption\">Figure 1: A sample deadlock graph showing the processes and resources sections<\/p>\n<p>The Extended Events live data viewer, built into SSMS in SQL Server 2012, as well as tools such as Profiler, can present a GUI representation of the deadlock graph. However, I prefer looking at the &#8216;raw&#8217; XML version. While most of the information in the deadlock graph is accessible from the GUI representation, it&#8217;s not as easy to find and is not all in one place. I find myself clicking on the various sessions and resources multiple times to get the whole picture.<\/p>\n<p>An XML deadlock graph has two main sections, labelled (1) and (2) in Figure 1.<\/p>\n<ol>\n<li><strong>Processes<\/strong> section &#8211; details all the processes involved in the deadlock, what they were running, what isolation level they were in and more<\/li>\n<li><strong>Resources<\/strong> section &#8211; lists all the resources that were involved in the deadlock, which locks each process acquired and which locks they requested.<\/li>\n<\/ol>\n<p>I like to start by looking at the resources section, to see which resources the processes were fighting over and what types of locks they were requesting. Once I have a picture of what locks were involved, then I go back and look at the details of the processes to see what they were doing.<\/p>\n<p>There will be at least two entries here, but there may be more. Each entry starts with a description of the resource and then lists the processes that either held a lock on that resource or requested a lock on that resource. Locks here will mainly be key, RID, page or table, with more exotic range locks possible if the query was running under <code>SERIALIZABLE<\/code> isolation. It&#8217;s also possible to have non-lock related resources here, especially if either a query was running in parallel. Start by noting the granularity and mode of the locks and the objects involved. Are there table level locks held or requested? How many different tables are involved? How many indexes are involved?<\/p>\n<p>Next, we move on to the processes section, which contains an entry for every thread involved in the deadlock. Notice that I say thread, not session, because if a session is running a parallel query, it can appear multiple times in this section. This section of the graph provides a wealth of information, including login names, host names, isolation level, times, session settings and more.<\/p>\n<p>The most useful information, generally, is the isolation level under which each query was running and the details of which statement completed the deadlock. I say &#8216;completed&#8217; the deadlock, because the statement listed in the deadlock graph can be just the one that the session was running at the point that the deadlock detector identified that this session was part of a deadlock.<\/p>\n<h2>Common types of SQL Server Deadlocks<\/h2>\n<p>Here we begin our dissection of the most common types of SQL Server deadlock. We&#8217;ll review deadlock graphs for each, discuss what distinguishes each type, and then consider what causes them and how to avoid them in future.<\/p>\n<p>In each case, the deadlock graphs I present are representative of real graphs, produced on real SQL Server instances operating under concurrent workloads. Some require some tricks and contrivances to reproduce on an isolated test instance, other are simply very hard to reproduce &#8216;on demand. The code download for this article contains code samples that will allow you to reproduce the reader-writer, writer-writer, key lookup and serializable deadlock types.<\/p>\n<p>However, please bear in mind, firstly, that your output won&#8217;t match exactly what I present in this article, though the basic signature will be similar. Secondly, that the code download examples are contrived specifically to produce the deadlock. They in no way represent code you&#8217;d hope or expect to see on a production system.<\/p>\n<h3>Reader-Writer Deadlocks<\/h3>\n<p>A reader-writer deadlock is a deadlock between a statement that is reading and a statement that is performing some form of data modification.<\/p>\n<p>When you look at the resources involved, you&#8217;ll see that the signature of this form of deadlock is locks that are all either a shared (S) lock granted and an exclusive (X) lock requested or an X lock granted and an S lock requested.<\/p>\n<p>In its default isolation level (<code>READ COMMITTED<\/code>), SQL Server hold S locks only until the statement completes. As such, reader-writer deadlocks are most prevalent in code that has data modifications followed by a <code>select<\/code> query, within a transaction, or in code that has requested a higher isolation level, meaning that SQL Server holds S locks till the end of the transaction, either intentionally or because the client libraries default to a higher isolation level.<\/p>\n<p>There is one very easy fix for this form of deadlock, and that is to use a row-version based isolation level, either <code>READ COMMITTED SNAPSHOT<\/code> or <code>SNAPSHOT<\/code>. In the row-version based isolation levels, readers do not take locks and instead use row versions for isolation. No shared locks means no reader-writer deadlocks.<\/p>\n<div class=\"tips note\">\n<h4>Transaction Isolation levels<\/h4>\n<p>Again, there is not room in this article for a more detailed description of the different transaction isolation levels, and how each prevents read phenomena, such as dirty reads, non-repeatable reads and so on. See <em>Further Reading<\/em>.<\/p>\n<\/div>\n<p>The sample deadlock graph in Figure 1 is, in fact, one generated by a reader-writer deadlock and we&#8217;re going to drill into the details of that graph here. As discussed earlier, we&#8217;ll start with the resources section.<\/p>\n<pre class=\"listing prettyprint lang-xml\">&lt;resource-list&gt; \n   &lt;pagelock fileid=\"1\" pageid=\"649\" dbid=\"23\" objectname=\"\" id=\"lock5e00300\"\" \n          mode=\"X\" associatedObjectId=\"72057594038845440\"&gt; \n      &lt;owner-list&gt; &lt;owner id=\"process5c13048\"\"\"\" mode=\"X\" \/&gt; \n      &lt;\/owner-list&gt; \n      &lt;waiter-list&gt; \n         &lt;waiter id=\"process5c4ebc8\"\"\"\" mode=\"S\" requestType=\"wait\" \/&gt; \n      &lt;\/waiter-list&gt; \n   &lt;\/pagelock&gt; \n   &lt;pagelock fileid=\"1\" pageid=\"192\" dbid=\"23\" objectname=\"\" id=\"lock62da600\"\" \n             mode=\"X\" associatedObjectId=\"7205759403877904\"&gt; \n      &lt;owner-list&gt; \n         &lt;owner id=\"process5c4ebc8\"\"\"\" mode=\"X\" \/&gt; \n      &lt;\/owner-list&gt; \n      &lt;waiter-list&gt; \n         &lt;waiter id=\"process5c13048\"\"\"\" mode=\"S\" requestType=\"wait\" \/&gt; \n      &lt;\/waiter-list&gt; \n   &lt;\/pagelock&gt; \n&lt;\/resource-list&gt;\n<\/pre>\n<p class=\"caption\">Listing 2: The resources section for a reader-writer deadlock<\/p>\n<p>We have two processes here, for the moment I&#8217;m going to call them <code>48<\/code> and <code>c8<\/code> (the last two letters of their <code>owner_id<\/code>). There are two resources, page 649 in database 23 and page 192 in database 23. With no object name given for the page lock (key locks and object locks give the name), we&#8217;re going to have to do a little bit of work to do to identify the table.<\/p>\n<p>The first step, however, is to identify the database, using the <code>db_name()<\/code> function. Once we have the database name, we can use the <code>associatedObjectID<\/code> (which in this case is not an <code>objectID<\/code>) to get the table name.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT OBJECT_NAME(p.object_id) AS TableName , \n       i.name AS IndexName \nFROM sys.partitions AS p \n     INNER JOIN sys.indexes AS i ON p.object_id = i.object_id \n                                    AND p.index_id = i.index_id \nWHERE partition_id = 72057594038845440\n<\/pre>\n<p class=\"caption\">Listing 3: Obtaining object names from partition IDs.<\/p>\n<p>We can identify the second resource the same way and it turns out that the involved tables were <code>Customers<\/code> and <code>Orders<\/code>, the clustered index of both.<\/p>\n<p>To work out the sequence of events that lead to the deadlock, we look at the <code>owner-list<\/code> and <code>waiter-list<\/code> for each resource. The process listed in the <code>owner-list<\/code> is the one that had the lock, the process or processes in the <code>waiter-list<\/code> are the ones that had requested the lock and were waiting for it.<\/p>\n<p>Using those lists, we can see that <code>Process 48<\/code> had an exclusive lock on the page in <code>Orders<\/code> and <code>Process c8<\/code> had an exclusive lock on the page in <code>Customers<\/code>. That was the first step. Then <code>Process 48<\/code> requested a read lock on the locked page in <code>Customers<\/code> and <code>Process c8<\/code> requested a read lock on the locked page in <code>Orders<\/code>.<\/p>\n<p>At this point, even without looking at the processes section, there&#8217;s enough information to consider potential fixes. If the order of either, or both, sets of statements were reversed and the queries are running in <code>READ<\/code><code>COMMITTED<\/code>, then this deadlock wouldn&#8217;t occur because under that isolation level shared locks are released no later than the end of the query that requested the locks and so the shared locks would be released before the update started. If we could move either <code>SELECT<\/code> outside the transaction, then this deadlock wouldn&#8217;t occur. These won&#8217;t necessarily be the actual solutions implemented, but they&#8217;re worth keeping in mind.<\/p>\n<p>For now, however, let&#8217;s move on to the process section. In Listing 4, I&#8217;ve removed some bits from the XML to keep the size down and applied some manual formatting to make the listing easier to read on the page.<\/p>\n<pre class=\"listing prettyprint lang-xml\">&lt;process-list&gt; \n   &lt;process id=\"process5c4ebc8\"\"\"\" waitresource=\"PAGE: 23:1:649\"\n           waittime=\"2377\" ownerId=\"533054\" \n           transactionname=\"user_transaction\" \n           lasttranstarted=\"2013-09-27T17:38:55.823\" XDES=\"0x8a8c0e80\" \n           lockMode=\"S\" schedulerid=\"8\" kpid=\"7464\" status=\"suspended\" \n           spid=\"52\" sbid=\"0\" ecid=\"0\" priority=\"0\" trancount=\"2\" \n           lastbatchstarted=\"2013-09-27T17:38:55.823\" \n           lastbatchcompleted=\"2013-09-27T17:38:55.770\" \n           clientapp=\"Microsoft SQL Server Management Studio - Query\" \n           hostname=\"MyHost\" hostpid=\"6188\" loginname=\"MyLogin\" \n           isolationlevel=\"read committed (2)\" xactid=\"533054\" currentdb=\"23\" \n           lockTimeout=\"4294967295\" clientoption1=\"671090784\" \n           clientoption2=\"390200\"&gt; \n      &lt;executionStack&gt; \n      &lt;frame procname=\"\" line=\"18\" stmtstart=\"688\" stmtend=\"794\" \n             sqlhandle=\"0x030017005a33f607c1db0e0146a200000100000000000000\" \/&gt; \n      &lt;frame procname=\"\" line=\"2\" stmtstart=\"24\" stmtend=\"222\" \n             sqlhandle=\"0x0100170066684218307e8f8a000000000000000000000000\" \/&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n       EXEC dbo.UpdateCustomerLatestOrderStatus @CustomerID= 2831, \n                                                @OrderStatus = 'F' \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"process5c13048\"\"\"\" waitresource=\"PAGE: 23:1:192\" \n            waittime=\"6290\" ownerId=\"533059\" \n            transactionname=\"user_transaction\" \n            lasttranstarted=\"2013-09-27T17:39:01.863\" XDES=\"0x8c4eae80\" \n            lockMode=\"S\" schedulerid=\"2\" kpid=\"6180\" status=\"suspended\" \n            spid=\"57\" sbid=\"0\" ecid=\"0\" priority=\"0\" trancount=\"3\" \n            lastbatchstarted=\"2013-09-27T17:39:01.863\" \n            lastbatchcompleted=\"2013-09-27T17:39:00.773\" \n            clientapp=\"Microsoft SQL Server Management Studio - Query\" \n            hostname=\"MyHost\" hostpid=\"6188\" loginname=\"MyLogin\" \n            isolationlevel=\"read committed (2)\" xactid=\"533059\" currentdb=\"23\" \n            lockTimeout=\"4294967295\" clientoption1=\"671090784\" \n            clientoption2=\"390200\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"\" line=\"7\" stmtstart=\"204\" stmtend=\"452\" \n                sqlhandle=\"0x030017009357ea08c5db0e0146a200000100000000000000\" \/&gt; \n         &lt;frame procname=\"\" line=\"2\" stmtstart=\"24\" stmtend=\"142\" \n                sqlhandle=\"0x01001700f2826f05f07e548d000000000000000000000000\" \/&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n         EXEC dbo.AddOrder @CustomerID= 2831, @OrderTotal = 137.42 \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n&lt;\/process-list&gt;<\/pre>\n<p class=\"caption\">Listing 4: The processes section for a reader-writer deadlock<\/p>\n<p>There&#8217;s a lot of information in there. To start, the process ID matches the process IDs listed in the resources section. We can see the client application, host name and login name of both sessions. If multiple applications use the server, this can help narrow down the culprit. Occasionally, you may find that the deadlocks originate from ad-hoc queries from Management Studio. In this case, fixing the deadlock may be as simple as asking the user to stop running that query or to run it elsewhere or at another time.<\/p>\n<p>We can see that the <code>transactionname<\/code> is <code>user_transaction<\/code>, indicating that the code formed part of an unnamed, explicit transaction.<\/p>\n<p>The <code>wait<\/code><code>resource<\/code> shows the database, objects and pages on which the processes are deadlocked, and reflect what we saw in the resources section. If the <code>isolationlevel<\/code> indicates that an application or procedure has requested a higher isolation level, it&#8217;s worth investigating whether or not this is a true requirement, or just a default, but unnecessary, setting.<\/p>\n<p>The input buffer (<code>inputbuf<\/code>) lists which statements each session sent to SQL Server. In both cases, in this example, it&#8217;s a call to a stored procedure, so our next step is to investigate these procedures. Process <code>c8<\/code><code><\/code>calls the procedure <code>U<\/code><code>pdateCustomerLatestOrderStatus<\/code> and process <code>48<\/code> calls the procedure <code>AddOrder<\/code>.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE PROCEDURE UpdateCustomerLatestOrderStatus\n    (\n      @CustomerID INT ,\n      @OrderStatus CHAR(1)\n    )\nAS \n    BEGIN TRANSACTION\n    UPDATE  Customers\n    SET     LatestOrderStatus = @OrderStatus\n    WHERE   CustomerID = @CustomerID\n\n    SELECT  *\n    FROM    Orders\n    WHERE   CustomerID = @CustomerID \n    COMMIT\nGO\n\nCREATE PROCEDURE AddOrder\n    (\n      @CustomerID INT ,\n      @OrderTotal NUMERIC(10, 2)\n    )\nAS \n    BEGIN TRANSACTION\n\n    INSERT  INTO Orders\n            ( CustomerID ,\n              OrderDate ,\n              OrderTotal ,\n              OrderStatus\n            )\n    VALUES  ( @CustomerID,\n              GETDATE() ,\n              @OrderTotal ,\n              'A'\n            );\n\n    SELECT  CustomerID,\n            CustomerName ,\n            RegionID ,\n            OrderLimit ,\n            'A' AS LatestOrderStatus\n    FROM    dbo.Customers AS c\n    WHERE   CustomerID = @CustomerID\n\n    UPDATE  Customers\n    SET     LatestOrderStatus = 'A'\n    WHERE   CustomerID = @CustomerID;\n\n    COMMIT\nGO<\/pre>\n<p class=\"caption\">Listing 5: The <code>UpdateCustomerLatestOrderStatus<\/code> and <code>AddOrder<\/code> stored procedures<\/p>\n<p>For the purposes of this article, please try to ignore the complete lack of error handling in these procedures. If they were real production code, there would be a lot more verification, error handling and checks. If they also appear oddly written, that&#8217;s intentional as I wrote them in a way that ensured they would cause a deadlock.<\/p>\n<p>To understand the deadlock, we need to match the code that ran to the locks listed in the <code>resource<\/code><code>s<\/code> section of the deadlock graph.<\/p>\n<p>We&#8217;ll start with process <code>c8<\/code>, which the resources section told us had taken an exclusive (X) lock on a page in <code>Customer<\/code><code>s<\/code> and then requested a Shared (S) lock on a page in <code>Orders<\/code>. Since <code>UpdateCustomerLatestOrderStatus<\/code> contains only two queries, we can deduce easily that the X lock this process hold results from the <code>update<\/code> of <code>customers<\/code> and the shared lock it requested results from the <code>select<\/code> on <code>Orders<\/code>.<\/p>\n<p><code>Process <\/code><code>4<\/code><code>8<\/code> first took an X lock on the page in <code>Orders<\/code>, which would be the <code>INSERT<\/code> into <code>Orders<\/code>. It then requested an S lock to perform a <code>select<\/code> against <code>customers<\/code>, but could not proceed as process <code>c8<\/code> had an incompatible lock on the page it needed. As this point, we had a deadlock and process 48 never even reached the subsequent update of <code>customers<\/code>.<\/p>\n<p>Now we know what caused the deadlock, it&#8217;s relatively easy, in this case, to prevent it. Let&#8217;s start with the <code>UpdateCustomerLatestOrderStatus<\/code> stored procedure. The explicit transaction in this procedure is only necessary if there are multiple data modification statements that need to form an atomic unit, or if the result of the <code>update<\/code> modification could affect the result of the subsequent <code>select<\/code>. In fact, we have a single data modification followed by a <code>select<\/code> on a different table, which will return the same results regardless of whether the <code>update<\/code> commits or rolls back. We can also safely say that there&#8217;s no trigger because there are no indication of it in the deadlock graph (it would appear in the <code>executionStack<\/code> sub-section).<\/p>\n<p>In short, we can remove the explicit transaction, as shown in Listing 6.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE PROCEDURE UpdateCustomerLatestOrderStatus (\n   @CustomerID INT,\n   @OrderStatus CHAR(1)\n   )\nAS\n   UPDATE Customers SET LatestOrderStatus = @OrderStatus\n   WHERE CustomerID = @CustomerID\n   \n   SELECT * FROM Orders WHERE CustomerID = @CustomerID GO\n<\/pre>\n<p class=\"caption\">Listing 6: Remove the explicit transaction from <code>UpdateCustomerLatestOrderStatus<\/code><\/p>\n<p>This procedure can no longer cause the deadlock. However, to be sure, let&#8217;s fix the <code>AddOrder<\/code> procedure too. This one&#8217;s a little harder.<\/p>\n<p>The <code>select<\/code> is against the same rows as the <code>update<\/code> right after it. If we look at what it&#8217;s doing, the <code>select<\/code> is returning the customer row as it will be after the <code>update<\/code> completes (it&#8217;s specifying the <code>LatestOrder<\/code><code>Status<\/code> as <code>A<\/code>, which is the value to which the <code>update<\/code> sets it).<\/p>\n<p>There&#8217;s a potential bug here though. If that <code>update<\/code> never commits, the select may return &#8216;dirty&#8217; data. This may be the developer&#8217;s intent but probably isn&#8217;t, so I&#8217;ll move the <code>select<\/code> outside the transaction and remove the hardcoded value for <code>LatestOrderStatus<\/code>, just letting the select return the value that&#8217;s in the table.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE PROCEDURE AddOrder (@CustomerID INT, @OrderTotal NUMERIC(10,2))\nAS\n\nBEGIN TRANSACTION\n\n  INSERT INTO Orders (CustomerID, OrderDate, OrderTotal, OrderStatus)\n  VALUES (@CustomerID, GETDATE(), @OrderTotal, 'A');\n\n  UPDATE Customers SET LatestOrderStatus = 'A'\n    WHERE CustomerID = @CustomerID;\nCOMMIT\n\nSELECT  CustomerID ,\n        CustomerName  ,\n        RegionID  ,\n        OrderLimit  ,\n        LatestOrderStatus  \nFROM dbo.Customers AS c WHERE CustomerID= @CustomerIDGO\n<\/pre>\n<p class=\"caption\">Listing 7: Modifying the <code>AddOrder<\/code> stored procedure to prevent deadlocks<\/p>\n<p>If the exact current behavior is required and correct, I could instead move the <code>select<\/code> so that it runs as-is, after the transaction commits.<\/p>\n<p>Now these procedures will no longer deadlock, but there is still one potential problem. These two procedures still access the same objects but in different orders. <code>UpdateCustomerLatestOrderStatus<\/code> touches <code>Customers<\/code> first then <code>Orders<\/code>, and <code>AddOrder<\/code> does the reverse order. If a future change wraps the first procedure&#8217;s contents in a transaction again, they may well start deadlocking again.<\/p>\n<p>One general rule for preventing deadlocks is always access objects in the same order, so let&#8217;s make one more fix to <code>UpdateCustomerLatestOrderStatus<\/code>.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE PROCEDURE UpdateCustomerLatestOrderStatus (\n CustomerIDINT,\n  @OrderStatus CHAR(1)\n  )\nAS\n  SELECT * FROM Orders WHERE CustomerID= @CustomerID\n  \n  UPDATE Customers SET LatestOrderStatus = @OrderStatus\n  WHERE CustomerID= @CustomerID\n  GO<\/pre>\n<p class=\"caption\">Listing 8: Modifying <code>UpdateCustomerLatestOrderStatus<\/code> so that it accesses objects in the same order as <code>AddOrder<\/code><\/p>\n<p>That should ensure that these two procedures never deadlock again.<\/p>\n<h3>Writer-Writer Deadlocks<\/h3>\n<p>In a writer-writer deadlock both the granted lock and requested lock on a resource are update or exclusive locks. In other words, both operations attempt data modifications.<\/p>\n<p>One important thing to note when investigating writer-writer deadlocks is that SQL Server holds exclusive locks until the transaction commits, unlike shared locks which in the default <code>read committed<\/code> isolation level SQL Server holds no longer than the end of the statement (and can in fact be released as soon as it reads the row, before the statement completed).<\/p>\n<p>Note also that the Snapshot isolation levels won&#8217;t help us with writer-writer deadlocks, as these levels affect only SQL Server&#8217;s behavior with regard to S locks. SQL Server will still take exclusive locks for data modifications.<\/p>\n<p>This aside, we adopt more or less the same approach to fixing a writer-writer deadlock as we did for fixing a reader-writer deadlock, so I&#8217;m not going to go into immense detail. Again, we&#8217;ll start with the resources section of the deadlock graph.<\/p>\n<pre class=\"listing prettyprint lang-xml\">&lt;resource-list&gt; \n   &lt;pagelock fileid=\"1\" pageid=\"649\" dbid=\"23\" objectname=\"\" \n             id=\"lock61fb800\"\" mode=\"X\" associatedObjectId=\"72057594038845440\"&gt; \n      &lt;owner-list&gt; \n         &lt;owner id=\"process5e3ae08\"\"\"\" mode=\"X\" \/&gt; \n      &lt;\/owner-list&gt; \n      &lt;waiter-list&gt; \n         &lt;waiter id=\"process5e4ebc8\"\"\"\" mode=\"X\" requestType=\"wait\" \/&gt; \n      &lt;\/waiter-list&gt; \n   &lt;\/pagelock&gt; \n   &lt;pagelock fileid=\"1\" pageid=\"192\" dbid=\"23\" objectname=\"\" \n             id=\"lock61fa180\"\" mode=\"X\" associatedObjectId=\"72057594038779904\"&gt; \n      &lt;owner-list&gt; \n         &lt;owner id=\"process5e4ebc8\"\"\"\" mode=\"X\" \/&gt; \n      &lt;\/owner-list&gt; \n      &lt;waiter-list&gt; \n      &lt;waiter id=\"process5e3ae08\"\"\"\" mode=\"X\" requestType=\"wait\" \/&gt; \n      &lt;\/waiter-list&gt; \n   &lt;\/pagelock&gt; \n&lt;\/resource-list&gt;<\/pre>\n<p class=\"caption\">Listing 9: The resources section of a writer-writer deadlock<\/p>\n<p>All locks involved are exclusive (X) locks. This means we can&#8217;t consider one of the snapshot isolation levels, nor will we be able to fix this by moving statements outside of a transaction.<\/p>\n<p>Once again, we see that two processes (<code>c8<\/code> and <code>08<\/code>) engaged in the deadlock. Once again, we use the <code>db_name()<\/code> function and Listing 3 to identify the objects involved and it&#8217;s the tables <code>Orders<\/code> and <code>Customers<\/code>.<\/p>\n<p>According to the resource section, the order of events was as follows<\/p>\n<ol>\n<li>process <code>08<\/code> takes an exclusive lock on a page in <code>Orders<\/code><\/li>\n<li>process <code>c8<\/code> takes an exclusive lock on a page in <code>Customers<\/code><\/li>\n<li>process <code>08<\/code> requests an exclusive lock on a page in <code>Customers<\/code><\/li>\n<li>process <code>c8<\/code> requests an exclusive lock on a page in <code>Orders<\/code>.<\/li>\n<\/ol>\n<p>The processes section is near identical to the one for the reader-writer deadlock with the primary difference being in the content of the input buffers.<\/p>\n<pre class=\"listing prettyprint lang-xml\">&lt;process-list&gt; \n   &lt;process id=\"process5e4ebc8\"\"\"\" waitresource=\"PAGE: 23:1:649\" \n            waittime=\"2499\" transactionname=\"user_transaction\"\n            lockMode=\"X\" status=\"suspended\" spid=\"57\" \n            clientapp=\"Microsoft SQL Server Management Studio - Query\" \n            hostname=\"MyHost\" loginname=\"MyLogin\" \n            isolationlevel=\"read committed (2)\" currentdb=\"23\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"\" line=\"11\" stmtstart=\"412\" stmtend=\"554\" \n                sqlhandle=\"0x03001700cc7bde093cad600152a200000100000000000000\" \/&gt; \n         &lt;frame procname=\"\" line=\"2\" stmtstart=\"24\" stmtend=\"146\" \n                sqlhandle=\"0x0100170033cf901120240080000000000000000000000000\" \/&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n         EXEC dbo.DispatchOrder @CustomerID= 2831, @OrderID = 100097\n     &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"process5e3ae08\"\"\"\" waitresource=\"PAGE: 23:1:192\" \n            waittime=\"2500\" transactionname=\"user_transaction\" \n            lockMode=\"X\" schedulerid=\"6\" status=\"suspended\" spid=\"54\" \n            clientapp=\"Microsoft SQL Server Management Studio - Query\" \n            hostname=\"MyHost\" loginname=\"MyLogin\" \n            isolationlevel=\"read committed (2)\" currentdb=\"23\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"\" line=\"11\" stmtstart=\"512\" stmtend=\"672\" \n                sqlhandle=\"0x030017009357ea0869a9600152a200000100000000000000\" \/&gt; \n         &lt;frame procname=\"\" line=\"2\" stmtstart=\"24\" stmtend=\"142\" \n                sqlhandle=\"0x01001700f2826f05a0b8d485000000000000000000000000\" \/&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n         EXEC dbo.AddOrder @CustomerID= 2831, @OrderTotal = 137.42\n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n&lt;\/process-list&gt;<\/pre>\n<p class=\"caption\">Listing 10: Processes section of a deadlock graph for a writer-writer deadlock<\/p>\n<p>It&#8217;s our old friend <code>AddOrder<\/code>, fixed to prevent reader-writer deadlock, but now engaged in a writer-writer deadlock with a different procedure, <code>DispatchOrder<\/code>, shown in Listing 11.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">ALTER PROCEDURE dbo.DispatchOrder\n    (\n      @CustomerID INT ,\n      @OrderID INT\n    )\nAS\n    BEGIN TRANSACTION\n    UPDATE  Customers\n    SET     LatestOrderStatus = 'D'\n    WHERE   CustomerID= @CustomerID;\n\n    UPDATE  dbo.Orders\n    SET     OrderStatus = 'D'\n    WHERE   OrderID = @OrderID;\n\n    SELECT  *\n    FROM    dbo.Customers AS c\n            INNER JOIN dbo.Orders AS o ON c.CustomerID= o.CustomerID\n    WHERE   c.CustomerID= @CustomerID\n            AND OrderID = @OrderID;\n    COMMIT\n\nGO<\/pre>\n<p class=\"caption\">Listing 11: The <code>DispatchOrder<\/code> stored procedure<\/p>\n<p>The sequence of the deadlock is as follows<\/p>\n<ul>\n<li>Process <code>08<\/code>, running <code>AddOrder<\/code>, takes an exclusive lock on the page in <code>Orders<\/code>.<\/li>\n<li>Process <code>c8<\/code>, running <code>DispatchOrder<\/code>, takes an exclusive lock on the page in <code>Customers<\/code><\/li>\n<li>Process <code>08<\/code> requests an exclusive lock on the page in <code>Customers<\/code>.<\/li>\n<li>Process <code>c8<\/code><code><\/code>requests an exclusive lock on the page in <code>Orders<\/code>, resulting in a deadlock<\/li>\n<li>The <code>SELECT<\/code> in <code>DispatchOrder<\/code> was never reached<\/li>\n<\/ul>\n<p>Once again, the root cause is accessing the same objects in different orders. At this point, if this were a real system, I&#8217;d be making a change to the coding standards document mandating that all transactions access the <code>Orders<\/code> table before the <code>Customers<\/code> table.<\/p>\n<p>To fix this deadlock I&#8217;m going to make two changes. First, pull the <code>select<\/code> outside of the transaction so that it returns the committed data, rather than returning data containing modifications that might roll back. Second, I&#8217;m going to ensure that <code>DispatchOrder<\/code>, like the other two accesses <code>Orders<\/code> first and then <code>Customers<\/code>.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">ALTER PROCEDURE dbo.DispatchOrder\n    (\n      @CustomerID INT ,\n      @OrderID INT\n    )\nAS\n    BEGIN TRANSACTION\n\n    UPDATE  dbo.Orders\n    SET     OrderStatus = 'D'\n    WHERE   OrderID = @OrderID;\n\n    UPDATE  Customers\n    SET     LatestOrderStatus = 'D'\n    WHERE   CustomerID= @CustomerID;\n    COMMIT\n\n    SELECT  *\n    FROM    dbo.Customers AS c\n            INNER JOIN dbo.Orders AS o ON c.CustomerID= o.CustomerID\n    WHERE   c.CustomerID= @CustomerID\n             AND OrderID = @OrderID;\nGO<\/pre>\n<p class=\"caption\">Listing 12: Modifying <code>DispatchOrders<\/code> to avoid deadlocks<\/p>\n<p>Since a transaction is, by definition, an atomic operation, which of the two updates runs first or second has no impact on the overall results, at least in cases like this where the two updates are independent. As such, the change I&#8217;ve made won&#8217;t change the behavior of the procedure.<\/p>\n<p>So far, I&#8217;ve made no mention of <em>tuning<\/em> the procedures simply because the sample procedures that I wrote to simulate deadlock-prone code are already about as optimal as possible. However, when dealing with deadlocks generally, tuning the procedures is a <em>critical<\/em> part of the solution, even if there&#8217;s an obvious cause, as in these examples. Any time a query takes more locks than necessary, or holds locks longer than necessary, there&#8217;s an increased chance it will deadlock with another processes, and the busier the server, the higher the chance that will happen.<\/p>\n<p>Of course, query tuning is a huge topic, and not specific to deadlocks, so I&#8217;m not going to cover it here. See the <em>Further Reading<\/em> section at the end of this article for some useful references.<\/p>\n<h3>Key Lookup Deadlocks<\/h3>\n<p>Technically key-lookup deadlocks are a special case of reader-writer deadlocks, but they are worth addressing separately as, unlike the earlier examples, this type of deadlock does not require that the processes are running multiple statements within a transaction. With a key lookup deadlock, it&#8217;s perfectly possible for a session running a single <code>select<\/code> to deadlock with a session running a single <code>update<\/code> statement. The reason this is possible relates to both SQL Server&#8217;s index architecture and the order in which the query processor runs operations.<\/p>\n<p>Let&#8217;s look at an example deadlock graph for a key lookup deadlock. As is our custom, we&#8217;ll review the resources section first.<\/p>\n<pre class=\"listing prettyprint lang-xml\">&lt;resource-list&gt; \n   &lt;keylock hobtid=\"72057594039435264\" dbid=\"9 \n            objectname=\"Sales.dbo.Invoices\" \n            indexname=\"PK__Invoices__D796AAD530676176\" id=\"lock2d4163680\"\" \n            mode=\"X\" associatedObjectId=\"72057594039435264\"&gt; \n      &lt;owner-list&gt; \n         &lt;owner id=\"process2efb09498\"\"\"\" mode=\"X\" \/&gt; \n      &lt;\/owner-list&gt; \n      &lt;waiter-list&gt; \n         &lt;waiter id=\"process2f8025498\"\"\"\" mode=\"S\" requestType=\"wait\" \/&gt; \n      &lt;\/waiter-list&gt; \n   &lt;\/keylock&gt; \n   &lt;keylock hobtid=\"72057594039566336\" dbid=\"9\" \n            objectname=\"Sales.dbo.Invoices\" indexname=\"idx_InvoiceNumber\" \n            id=\"lock2d4125b00\"\" mode=\"S\" \n            associatedObjectId=\"72057594039566336\"&gt; \n      &lt;owner-list&gt; \n         &lt;owner id=\"process2f8025498\"\"\"\" mode=\"S\" \/&gt; \n      &lt;\/owner-list&gt; \n      &lt;waiter-list&gt; \n         &lt;waiter id=\"process2efb09498\"\"\"\" mode=\"X\" requestType=\"wait\" \/&gt; \n      &lt;\/waiter-list&gt; \n   &lt;\/keylock&gt; \n&lt;\/resource-list&gt; \n<\/pre>\n<p class=\"caption\">Listing 13: Resources section of a Key Lookup deadlock graph<\/p>\n<p>One key point to note about the resources section is that there&#8217;s only one table involved, and two indexes on that table:<\/p>\n<ul>\n<li>Process <code>2f8025498<\/code> holds a shared lock on the non-clustered index (<code>idx_InvoiceNumber<\/code>) and has requested a shared lock on the clustered index<\/li>\n<li>Process <code>2efb09498<\/code> holds an exclusive lock on the clustered index and has requested an exclusive lock on the non-clustered index<\/li>\n<\/ul>\n<p>Listing 14 shows the processes section of the deadlock graph.<\/p>\n<pre class=\"listing prettyprint lang-xml\">&lt;process-list&gt; \n   &lt;process id=\"process2f8025498\"\"\"\" \n            waitresource=\"KEY: 9:72057594039435264 (8dc78ed22838)\" \n            waittime=\"88\" transactionname=\"SELECT\" \n            lasttranstarted=\"2013-12-02T17:21:19.553\" lockMode=\"S\" \n            status=\"suspended\" spid=\"52\" trancount=\"0\"\n            lastbatchstarted=\"2013-12-02T17:21:19.553\" \n            lastbatchcompleted=\"2013-12-02T17:21:19.543\" \n            clientapp=\"Microsoft SQL Server Management Studio - Query\" \n            isolationlevel=\"read committed (2)\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"adhoc\" line=\"2\" stmtstart=\"36\"      \n            sqlhandle=\"0x0200000077d420286f1fc24c07ab8080072e05f2a9b935df \n                       0000000000000000000000000000000000000000\"&gt; \n          SELECT * FROM [dbo].[Invoices] [i] WHERE [InvoiceNumber]=@1 \n                                             AND [InvoiceStatus] = @2 \n         &lt;\/frame&gt; \n         &lt;frame procname=\"adhoc\" line=\"2\" stmtstart=\"4\" \n               sqlhandle=\"0x02000000960a0f2f4fd7272c5b1fee4358bbef0855443168 \n                          0000000000000000000000000000000000000000\"&gt; \n          SELECT * FROM [dbo].[Invoices] [i] WHERE [InvoiceNumber]=@1 \n                                             AND [InvoiceStatus] = @2 \n         &lt;\/frame&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; SELECT * FROM [dbo].[Invoices] [i] WHERE [InvoiceNumber]=@1 \n                                                          AND [InvoiceStatus] = @2 \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"process2efb09498\"\"\"\" taskpriority=\"0\" logused=\"464\" \n            waitresource=\"KEY: 9:72057594039566336 (33435b6b6461)\" \n            waittime=\"88\" ownerId=\"393249\" transactionname=\"UPDATE\" \n            lasttranstarted=\"2013-12-02T17:21:19.553\" XDES=\"0x2ef64cd28\" \n            lockMode=\"X\" schedulerid=\"4\" kpid=\"1372\" status=\"suspended\" \n            spid=\"54\" sbid=\"0\" ecid=\"0\" priority=\"0\" trancount=\"2\" \n            lastbatchstarted=\"2013-12-02T17:21:19.553\" lastbatchcompleted=\"2013-12-02T17:21:19.553\" \n            clientapp=\"Microsoft SQL Server Management Studio - Query\" \n            isolationlevel=\"read committed (2)\" xactid=\"393249\" \n            currentdb=\"9\" lockTimeout=\"4294967295\" \n            clientoption1=\"671090784\" clientoption2=\"390200\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"adhoc\" line=\"2\" stmtstart=\"118\" \n                sqlhandle=\"0x020000009c6576269d7106a424dbe6c5a8df7ee031492564 \n                           0000000000000000000000000000000000000000\"&gt; \n           UPDATE [dbo].[Invoices] set [InvoiceStatus] = @1 \n                                       WHERE [InvoiceID]=@2 \n         &lt;\/frame&gt; \n         &lt;frame procname=\"adhoc\" line=\"2\" stmtstart=\"4\" \n                sqlhandle=\"0x020000001da1c102d03e54b379f71b19ec42a91e6d0eaad7 \n                           0000000000000000000000000000000000000000\"&gt; \n           UPDATE [dbo].[Invoices] set [InvoiceStatus] = @1 \n                                       WHERE [InvoiceID]=@2 \n         &lt;\/frame&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n         UPDATE [dbo].[Invoices] set [InvoiceStatus] = @1 \n                                     WHERE [InvoiceID]=@2 \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n&lt;\/process-list&gt;\n<\/pre>\n<p class=\"caption\">Listing 14: Processes section of a Key Lookup deadlock graph<\/p>\n<p>The process list reveals only two statements, a query and an update. We can see from the <code>executionStack<\/code> that, this time, we&#8217;re dealing with ad-hoc SQL. We see each statement listed twice in the <code>executionStack<\/code> (once as the entire batch submitted, once as the statement that completed the deadlock) and again in the <code>i<\/code><code>n<\/code><code>putbuf<\/code> (as the command sent to SQL Server). The two statements are as follows:<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT * FROM [dbo].[Invoices] [i] WHERE [InvoiceNumber]=@1 \n                                   AND [InvoiceStatus] = @2\n<\/pre>\n<p>And:<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">UPDATE [dbo].[Invoices] set [InvoiceStatus] = @1\n                            WHERE [InvoiceID]=@2\n<\/pre>\n<p>There are no other queries involved here, no explicit transactions. These are two individual statements deadlocking.<\/p>\n<p>Process <code>2f8025498<\/code> is reading the <code>Invoices<\/code> table via the non-clustered index, which happens to be non-covering. As such, it also needs to retrieve data from the clustered index. In such cases, SQL Server&#8217;s query processor first takes a lock on the non-clustered index, reading the index rows, then takes locks on the associated rows in the clustered index and performs the lookup.<\/p>\n<p>When a data modification occurs, SQL Server first locks and modifies the rows in the clustered index, and then locates the non-clustered indexes that it needs to change, takes locks on those rows and modifying them. As a result, we can have a race condition that can lead to a deadlock.<\/p>\n<p>In this case, the <code>SELECT<\/code>, using a non-covering index on <code>InvoiceNumber<\/code>, took a shared lock on the index key for the <code>InvoiceNumber<\/code> passed. The <code>UPDATE<\/code> took an exclusive key lock on the clustered index for the <code>InvoiceID<\/code> it was passed. The <code>InvoiceNumber<\/code> and <code>InvoiceID<\/code> happened to belong to the same row. The select then requested a shared lock on the row in the clustered index to do its key lookup, but was blocked by the exclusive lock that the <code>UPDATE<\/code> held. The <code>update<\/code> then requested an exclusive lock on the non-clustered index to change the <code>InvoiceStatus<\/code>, which is a key column in that index, but the shared lock held by the <code>SELECT<\/code> meant that SQL Server could not grant the request. At this point, the deadlock detector identifies the deadlock and terminates the <code>select<\/code>.<\/p>\n<p>These deadlocks are annoying to fix, because technically the developer has done nothing wrong, nothing to cause the deadlock. To fix this, we need to make the non-clustered index a covering index. In the example, the query is a <code>SELECT *<\/code>, so this will be difficult and probably inefficient, and so fixing the deadlock will involve figuring out which columns are actually needed, replacing the <code>SELECT *<\/code> with a <code>SELECT<\/code> just of those columns and then making the index covering.<\/p>\n<p>If it were not possible to cover the query, then some retry logic on the <code>select<\/code> would work as well. The lock manager will not choose the <code>update<\/code> as the deadlock victim, by default, because it requires more work to undo than the <code>SELECT<\/code>.<\/p>\n<p>Since these are a special case of reader-writer deadlocks, using one of the row-versioning based isolation levels for the <code>SELECT<\/code> will also resolve this form of deadlock.<\/p>\n<h3>Parallelism-related deadlocks<\/h3>\n<p>Any deadlock can involve queries running in parallel. When that happens, the deadlock graph can become massively complex, leading to multiple additional entries in the resources section, with parallelism-related wait types, and multiple additional entries in the processes section. The key to interpreting a deadlock graph polluted with parallelism is to ignore all the parallelism-related sections.<\/p>\n<p>Listing 15 shows the resources section of a deadlock graph for a deadlock that involved queries running in parallel.<\/p>\n<pre class=\"listing prettyprint lang-xml\">&lt;resource-list&gt; \n   &lt;keylock hobtid=\"72057594548125696\" dbid=\"5\" objectname=\"SomeTable\" \n            indexname=\"idx_1\" id=\"lock3e2a900\"\" mode=\"X\" \n            associatedObjectId=\"72057594548125696\"&gt; \n      &lt;owner-list&gt; \n         &lt;owner id=\"process948e38\"\"\"\" mode=\"X\"\/&gt; \n      &lt;\/owner-list&gt; \n      &lt;waiter-list&gt; \n         &lt;waiter id=\"process948c58\"\"\"\" mode=\"U\" requestType=\"wait\"\/&gt; \n      &lt;\/waiter-list&gt; \n   &lt;\/keylock&gt; \n   &lt;exchangeEvent id=\"port3e4a300\"\" nodeId=\"6\"&gt; \n      &lt;owner-list&gt; \n         &lt;owner event=\"e_waitNone\" type=\"producer\" id=\"process948c58\"\"\"\"\/&gt; \n      &lt;\/owner-list&gt; \n      &lt;waiter-list&gt; \n         &lt;waiter event=\"e_waitPortOpen\" type=\"consumer\" id=\"processbf44d8\"\"\"\"\/&gt; \n         &lt;waiter event=\"e_waitPortOpen\" type=\"producer\" id=\"processbf4a78\"\"\"\/&gt; \n         &lt;waiter event=\"e_waitPortOpen\" type=\"producer\" id=\"process8bad48\"\"\"\/&gt; \n         &lt;waiter event=\"e_waitPortOpen\" type=\"producer\" id=\"process94e988\"\"\"\/&gt; \n      &lt;\/waiter-list&gt; \n   &lt;\/exchangeEvent&gt; \n   &lt;keylock hobtid=\"72057594548387840\" dbid=\"5\" objectname=\"SomeTable\" \n            indexname=\"idx_2\" id=\"lockcaf0340\"\" mode=\"X\" \n            associatedObjectId=\"72057594548387840\"&gt; \n      &lt;owner-list&gt; \n         &lt;owner id=\"processbf44d8\"\"\"\" mode=\"X\"\/&gt; \n      &lt;\/owner-list&gt; \n      &lt;waiter-list&gt; \n         &lt;waiter id=\"process948a78\"\"\"\" mode=\"U\" requestType=\"wait\"\/&gt; \n      &lt;\/waiter-list&gt; \n   &lt;\/keylock&gt; \n   &lt;exchangeEvent id=\"port3e4a200\"\" nodeId=\"5\"&gt; \n      &lt;owner-list&gt; \n         &lt;owner event=\"e_waitNone\" type=\"producer\" id=\"process948a78\"\"\"\"\/&gt; \n      &lt;\/owner-list&gt; \n      &lt;waiter-list&gt; \n         &lt;waiter event=\"e_waitPipeGetRow\" type=\"consumer\" id=\"process948e38\"\"\"\"\/&gt; \n      &lt;\/waiter-list&gt; \n   &lt;\/exchangeEvent&gt; \n&lt;\/resource-list&gt;<\/pre>\n<p class=\"caption\">Listing 15: Resources section for a parallelism-related deadlock graph<\/p>\n<p>The <code>exchangeEvent<\/code> resources are parallelism exchange operators. These extra entries can make the deadlock graph very confusing and a lot harder to read. However, notice that all the locks granted or requested are either X or U meaning that this is a &#8220;writer-writer&#8221; deadlock.<\/p>\n<p>There are two types of parallelism-related deadlocks. The first is a normal deadlock where one of more of the queries are running in parallel. The second is an intra-query parallelism deadlock and occurs when a single, parallelized query deadlocks with itself.<\/p>\n<p>To identify whether or not this is an intra-query parallelism deadlock, we examine the processes section and see how many different values there are for the <code>spid<\/code> attribute. Listing 16 shows the massively cut-down processes section of the deadlock graph.<\/p>\n<pre class=\"listing prettyprint lang-xml\">&lt;process-list&gt; \n   &lt;process id=\"process8bad48\"\"\" spid=\"52\" isolationlevel=\"read committed (2)\" \n            currentdb=\"5\" &gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"GenerateParallelDeadlock\" line=\"3\" \n                stmtstart=\"124\" stmtend=\"555\" \n                sqlhandle=\"0x03000500a994e2395b978e00549c00000000000000000000\"&gt; \n           -- redacted \n         &lt;\/frame&gt; \n         &lt;frame procname=\"adhoc\" line=\"1\" \n                sqlhandle=\"0x02000000d2106b13864677e720efec06d61e0c33c770b8ea\"&gt; \n           -- redacted \n         &lt;\/frame&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"process948a78\"\"\"\" \n            waitresource=\"KEY: 5:72057594548387840 (b70275b2ca0d)\" \n            transactionname=\"user_transaction\" lockMode=\"U\" \n            status=\"suspended\" spid=\"53\" isolationlevel=\"read committed (2)\"&gt; \n      &lt;executionStack&gt;  \n         &lt;frame procname=\"adhoc\" line=\"1\" \n                sqlhandle=\"0x020000000e23aa221ede7daa26ce1e248f8ff1bc2ed1d9ce\"&gt; \n            -- redacted \n         &lt;\/frame&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"process948c58\"\"\"\" \n            waitresource=\"KEY: 5:72057594548125696 (3803e55a1987)\" \n            transactionname=\"user_transaction\" status=\"suspended\" \n            spid=\"52\" isolationlevel=\"read committed (2&gt;\" \n      &lt;executionStack&gt; \n         &lt;frame procname=\"GenerateParallelDeadlock\" \n                line=\"8\" stmtstart=\"550\" stmtend=\"748\" \n                sqlhandle=\"0x03000500a994e2395b978e00549c00000000000000000000\"&gt; \n           -- redacted \n         &lt;\/frame&gt; \n         &lt;frame procname=\"adhoc\" line=\"1\" \n                sqlhandle=\"0x02000000d2106b13864677e720efec06d61e0c33c770b8ea\"&gt; \n            -- redacted \n         &lt;\/frame&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"process948e38\"\"\"\" status=\"suspended\" spid=\"53\" \n            isolationlevel=\"read committed (2)\" currentdb=\"5\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"adhoc\" line=\"1\" \n                sqlhandle=\"0x020000000e23aa221ede7daa26ce1e248f8ff1bc2ed1d9ce\"&gt; \n            -- redacted \n         &lt;\/frame&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n        -- redacted \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"process94e988\"\"\" status=\"suspended\" spid=\"52\" \n            isolationlevel=\"read committed (2)\" currentdb=\"5\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"GenerateParallelDeadlock\" \n                line=\"8\" stmtstart=\"550\" stmtend=\"748\" \n                sqlhandle=\"0x03000500a994e2395b978e00549c00000000000000000000\"&gt; \n           -- redacted \n         &lt;\/frame&gt; \n         &lt;frame procname=\"adhoc\" line=\"1\" \n                sqlhandle=\"0x02000000d2106b13864677e720efec06d61e0c33c770b8ea\"&gt; \n           -- redacted \n         &lt;\/frame&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"processbf44d8\"\"\"\" status=\"suspended\" spid=\"52\" \n            isolationlevel=\"read committed (2)\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"GenerateParallelDeadlock\" line=\"8\" \n                stmtstart=\"550\" stmtend=\"748\" \n                sqlhandle=\"0x03000500a994e2395b978e00549c00000000000000000000\"&gt;  \n           -- redacted \n         &lt;\/frame&gt; \n         &lt;frame procname=\"adhoc\" line=\"1\" \n                sqlhandle=\"0x02000000d2106b13864677e720efec06d61e0c33c770b8ea\"&gt;   \n           -- redacted \n         &lt;\/frame&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n         -- redacted \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"processbf4a78\"\"\" status=\"suspended\" spid=\"52\" \n            isolationlevel=\"read committed (2)\" currentdb=\"5\" &gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"GenerateParallelDeadlock\" line=\"8\" stmtstart=\"550\" stmtend=\"748\" \n                sqlhandle=\"0x03000500a994e2395b978e00549c00000000000000000000\"&gt; \n           -- redacted \n         &lt;\/frame&gt; \n         &lt;frame procname=\"adhoc\" line=\"1\" \n                sqlhandle=\"0x02000000d2106b13864677e720efec06d61e0c33c770b8ea\"&gt; \n           -- redacted \n         &lt;\/frame&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf&gt; \n      &lt;\/inputbuf&gt; \n   &lt;\/process&gt; \n&lt;\/process-list&gt;<\/pre>\n<p class=\"caption\">Listing 16: Processes section for a parallelism-related deadlock graph<\/p>\n<p>The process list reveals two processes, <code>spid 52<\/code> and <code>spid 53<\/code>. In effect, this is a normal writer-writer deadlock, but with one of the processes, <code>spid 52<\/code>, running in parallel, as indicated by the duplicated process entries. Just ignore the parallel resources and debug this in the same way as the previous writer-writer deadlock.<\/p>\n<p>Let&#8217;s compare that with the other kind of parallelism-related deadlock. The processes section of an intra-query parallelism deadlock graph (see Listing 17) will reveal only a single <code>spid<\/code>.<\/p>\n<pre class=\"prettyprint lang-xml listing\">&lt;process-list&gt; \n   &lt;process id=\"process1a167a508\"\" status=\"suspended\" spid=\"75\" \n            isolationlevel=\"read committed (2)\" &gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"\" line=\"10\" stmtstart=\"58\" stmtend=\"77\" \n                sqlhandle=\"0x03001400fb3f7b6d4772f600d4a100000100000000000000\" \/&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf \/&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"process6a258a988\"\" status=\"suspended\" spid=\"75\" \n            isolationlevel=\"read committed (2)\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"\" line=\"10\" stmtstart=\"58\" stmtend=\"77\" \n                sqlhandle=\"0x03001400fb3f7b6d4772f600d4a100000100000000000000\" \/&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf \/&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"process189753948\"\" status=\"suspended\" spid=\"75\" \n                isolationlevel=\"read committed (2)\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"\" line=\"10\" stmtstart=\"58\" stmtend=\"77\" \n                sqlhandle=\"0x03001400fb3f7b6d4772f600d4a100000100000000000000\" \/&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf \/&gt; \n   &lt;\/process&gt; \n   &lt;process id=\"process1a167b708\"\" status=\"suspended\" spid=\"75\" \n                isolationlevel=\"read committed (2)\"&gt; \n      &lt;executionStack&gt; \n         &lt;frame procname=\"\" line=\"10\" stmtstart=\"58\" stmtend=\"77\" \n                sqlhandle=\"0x03001400fb3f7b6d4772f600d4a100000100000000000000\" \/&gt; \n      &lt;\/executionStack&gt; \n      &lt;inputbuf \/&gt; \n   &lt;\/process&gt; \n&lt;\/process-list&gt;<\/pre>\n<p class=\"caption\">Listing 17: Processes section for an intra-query parallelism deadlock graph<\/p>\n<p>The fact that the intra-query variety of parallelism deadlock can occur at all is a &#8216;bug&#8217; in the query processor. Such deadlocks shouldn&#8217;t be able to occur, but there are cases where they will.<\/p>\n<p>If such a deadlock occurs regularly, the solution is first to tune the query, the idea being that if the query is more efficient then SQL Server won&#8217;t need to parallelize it as much or at all and perhaps that will remove the conditions that lead to the deadlock.<\/p>\n<p>If that doesn&#8217;t work, then adding a <code>MAXDOP<\/code> hint to the query to force it to run serially will remove any chance of an intra-query parallelism deadlock, though possibly at the cost of a longer execution time.<\/p>\n<p>Finally, it&#8217;s also a good idea to ensure that you&#8217;ve patched SQL Server to the latest service pack.<\/p>\n<h3>Range Scans and SERIALIZABLE deadlocks<\/h3>\n<p>The <code>SERIALIZABLE<\/code> isolation level is prone to deadlocks because it takes restrictive locks and holds all locks for the duration of the transaction. Any time you see in a deadlock graph a process with <code>isolationlevel=<\/code><code>\"<\/code><code>serializable<\/code><code>(4)<\/code><code>\"<\/code>, your first act in the debugging task should confirm whether or not that transaction really needs to run in <code>SERIALIZABLE<\/code> mode, and if not then whether switching to a lower isolation level resolves the deadlock.<\/p>\n<p>If use of <code>SERIALIZABLE<\/code> is unavoidable then we&#8217;re back to an exercise in tuning the statements and structures that are involved in the deadlock. In this regard, one very common query pattern is very prone to deadlocks resulting from the <code>SERIALIZABLE<\/code> range locks.<\/p>\n<p>When a transaction runs in <code>SERIALIZABLE<\/code> isolation level, SQL Server has to prevent phantom rows from appearing in a resultset. It acquires Range locks in order to prevent another transaction from inserting rows into ranges that the <code>SERIALIZABLE<\/code> transaction is reading. It acquires these locks on a range of the relevant index, or locks the entire table if there isn&#8217;t a relevant index.<\/p>\n<p>These range locks appear as <code>RangeS-S<\/code>, <code>RangeS-U<\/code>, <code>RangeI-N<\/code> or <code>RangeX-X<\/code> locks (again, see <em>Further Reading<\/em> for more information on these lock types). For our deadlock example, we are interested, specifically in the <code>RangeS-S<\/code> and <code>RangeI-N<\/code> locks.<\/p>\n<p>SQL Server acquires the <code>RangeS-S<\/code> lock when a <code>select<\/code> query requests a specific row, and is running under <code>SERIALIZABLE<\/code> isolation.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">\tSELECT CustomerIDFROM Customers WHERE CustomerName = @p1\n<\/pre>\n<p>Under <code>SERIALIZABLE<\/code> isolation level, if that query returns zero rows, then any time it is run again within the transaction it must also return zero rows. Locking the rows where <code>CustomerName = @p1<\/code> doesn&#8217;t suffice and so SQL Server locks the range in the index which contains the value <code>@p1<\/code>, it locks the entire range between the index key value before <code>@p1<\/code> to the index key value after <code>@p1<\/code>. Any insert into that range, for values of <code>CustomerName = @p1<\/code>, or any other value in the range, is blocked.<\/p>\n<p>Likewise, an <code>INSERT<\/code> running under <code>SERIALIZABLE<\/code> isolation level has to acquire, briefly, <code>RangeI-N<\/code> locks until it finds the correct location to insert the new row. If a <code>SELECT<\/code> statement already holds a <code>RangeS-S<\/code> lock on the same range, it will block the acquisition of the <code>RangeI-N<\/code> locks, since <code>RangeS-S<\/code> and <code>RangeI-N<\/code> locks are incompatible. Now imagine multiple transactions trying to read and then insert into the same range; it&#8217;s a recipe for deadlocks.<\/p>\n<p>Having set the scene, let&#8217;s look at an example of the problematic pattern.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;\n\nBEGIN TRANSACTION\nIF EXISTS ( SELECT  1\n            FROM    [dbo].[Customers] WITH ( ROWLOCK )\n            WHERE   CustomerName = @p1 )\n    UPDATE  dbo.Customers\n    SET     LatestOrderStatus = NULL ,\n            OrderLimit = 0\n    WHERE   CustomerName = @p1;\nELSE\n    INSERT  INTO dbo.Customers\n            ( CustomerName ,\n              RegionID ,\n              OrderLimit ,\n              LatestOrderStatus\n            )\n    VALUES  ( @p1 ,\n              0 ,\n              0 ,\n              NULL\n            );\nCOMMIT TRANSACTION\n<\/pre>\n<p class=\"caption\">Listing 18: The &#8216;Upsert&#8217; pattern<\/p>\n<p>Initially it looks fine (other than a lack of error handling). It&#8217;s a traditional &#8216;upsert&#8217; operation where we insert the row if it doesn&#8217;t exist, or update it if it does. There&#8217;s a unique index on <code>CustomerName<\/code> and the <code>rowlock<\/code> hint ensures that SQL will only lock a single row. Right?<\/p>\n<p>Well, no, not under <code>SERIALIZABLE<\/code> isolation level. Let&#8217;s say that the <code>CustomerName<\/code> that we&#8217;re checking is &#8216;Matthew Green&#8217;, the value immediately below that in the index is &#8216;Leonard Kotz&#8217; and the value immediately above it is &#8216;Nicolas van Schalkwyk&#8217;. Under <code>SERIALIZABLE<\/code> isolation level, SQL will lock the entire range from &#8216;Leonard Kotz&#8217; right through to &#8216;Nicolas van Schalkwyk&#8217; and subsequently block any attempt to <code>insert<\/code> any value within that range.<\/p>\n<p>If we have two different sessions trying to <code>insert<\/code> different customers in that range then both sessions will request and receive <code>RangeS-S<\/code> locks (it&#8217;s a shared lock so multiple sessions can hold it). Both sessions then try to <code>insert<\/code> the customer rows, with different customer names. To do so, each needs a <code>RangeI-N<\/code> lock. This lock is not compatible with the <code>RangeS-S<\/code> that both sessions hold over the same range and so we get a deadlock.<\/p>\n<p>Listing 19 shows a typical deadlock graph.<\/p>\n<pre class=\"listing prettyprint lang-xml\">&lt;deadlock&gt; \n   &lt;victim-list&gt; \n      &lt;victimProcess id=\"process2f8025c38\"\"\"\"\" \/&gt; \n   &lt;\/victim-list&gt; \n   &lt;process-list&gt; \n      &lt;process id=\"process2f8025c38\"\"\"\"\" \n               waitresource=\"PAGE: 6:1:204 \" \n               waittime=\"4590\" ownerId=\"98391\" \n               transactionname=\"user_transaction\" \n               lasttranstarted=\"2013-12-30T12:16:12.017\" \n               XDES=\"0x2f28f63a8\" lockMode=\"X\" schedulerid=\"3\" kpid=\"3308\" \n               status=\"suspended\" spid=\"52\" sbid=\"0\" ecid=\"0\" priority=\"0\" \n               trancount=\"2\" lastbatchstarted=\"2013-12-30T12:16:12.017\" \n               lastbatchcompleted=\"2013-12-30T12:16:11.283\" \n               lastattention=\"1900-01-01T00:00:00.283\" \n               hostpid=\"4240\" \n               isolationlevel=\"serializable (4)\" xactid=\"98391\" currentdb=\"6\" \n               lockTimeout=\"4294967295\" clientoption1=\"671090784\" \n               clientoption2=\"390200\"&gt; \n         &lt;executionStack&gt; \n            &lt;frame procname=\"adhoc\" line=\"12\" stmtstart=\"64\" \n                   sqlhandle=\"0x02000000af049c2b891efce091617388896879874b38bff \n                              00000000000000000000000000000000000000000\"&gt; \n              INSERT INTO [dbo].[Customers]([CustomerName],[RegionID],\n                                            [OrderLimit],[LatestOrderStatus]) \n              values(@1,@2,@3,NULL) \n            &lt;\/frame&gt; \n            &lt;frame procname=\"adhoc\" line=\"12\" stmtstart=\"684\" stmtend=\"1062\" \n                  sqlhandle=\"0x02000000d01754207294e73fb558a4345b0aa169447175ef \n                             0000000000000000000000000000000000000000\"&gt; \n             INSERT INTO dbo.Customers \n             ( CustomerName , \n             RegionID , \n             OrderLimit , \n             LatestOrderStatus \n             ) \n             VALUES ('Bianca Meyer', 0, 0, NULL) \n            &lt;\/frame&gt; \n         &lt;\/executionStack&gt; \n         &lt;inputbuf&gt; \n           SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; \n           \n           BEGIN TRANSACTION \n           IF EXISTS (SELECT 1 FROM [dbo].[Customers] WITH (ROWLOCK) \n                      WHERE CustomerName = 'Bianca Meyer') \n           UPDATE dbo.Customers SET LatestOrderStatus = NULL, OrderLimit = 0 \n                                  WHERE CustomerName = 'Bianca Meyer'; \n           ELSE \n           INSERT INTO dbo.Customers \n           ( CustomerName , \n           RegionID , \n           OrderLimit , \n           LatestOrderStatus \n           ) \n           VALUES ('Bianca Meyer', 0, 0, NULL) \n           COMMIT TRANSACTION \n         &lt;\/inputbuf&gt; \n      &lt;\/process&gt; \n      &lt;process id=\"process2ec302188\"\"\"\"\" taskpriority=\"0\" logused=\"276\" \n               waitresource=\"KEY: 6:72057594038976512 (e3bf93c3ba86)\" \n               waittime=\"6594\" ownerId=\"98387\" \n               transactionname=\"user_transaction\" \n               lasttranstarted=\"2013-12-30T12:16:10.013\" XDES=\"0x2f28f76a8\" \n               lockMode=\"RangeI-N\" schedulerid=\"4\" kpid=\"5940\" \n               status=\"suspended\" spid=\"55\" sbid=\"0\" ecid=\"0\" priority=\"0\" \n               trancount=\"2\" lastbatchstarted=\"2013-12-30T12:16:10.010\" \n               lastbatchcompleted=\"2013-12-30T12:11:06.583\" \n               lastattention=\"1900-01-01T00:00:00.583\" \n               isolationlevel=\"serializable (4)\" xactid=\"98387\" \n               lockTimeout=\"4294967295\" clientoption1=\"671090784\" \n               clientoption2=\"390200\"&gt; \n         &lt;executionStack&gt; \n            &lt;frame procname=\"adhoc\" line=\"12\" stmtstart=\"64\" \n                   sqlhandle=\"0x02000000af049c2b891efce091617388896879874b38bff \n                              00000000000000000000000000000000000000000\"&gt; \n              INSERT INTO [dbo].[Customers]([CustomerName],[RegionID],\n                                            [OrderLimit],[LatestOrderStatus]) \n              values(@1,@2,@3,NULL) \n            &lt;\/frame&gt; \n            &lt;frame procname=\"adhoc\" line=\"12\" stmtstart=\"684\" stmtend=\"1062\" \n                   sqlhandle=\"0x0200000094bb05051933a06c504efa475d7eeffcc116e699 \n                              0000000000000000000000000000000000000000\"&gt; \n              INSERT INTO dbo.Customers \n              ( CustomerName , \n              RegionID , \n              OrderLimit , \n              LatestOrderStatus \n              ) \n              VALUES ('Bey Whiticer', 0, 0, NULL) \n            &lt;\/frame&gt; \n         &lt;\/executionStack&gt; \n         &lt;inputbuf&gt; \n           BEGIN TRANSACTION \n           IF EXISTS (SELECT 1 FROM [dbo].[Customers] WITH (ROWLOCK) \n                      WHERE CustomerName = 'Bey Whiticer') \n           UPDATE dbo.Customers SET LatestOrderStatus = NULL, OrderLimit = 0 \n                                 WHERE CustomerName = 'Bey Whiticer'; \n           ELSE \n           INSERT INTO dbo.Customers \n           ( CustomerName , \n           RegionID , \n           OrderLimit , \n           LatestOrderStatus \n           ) \n           VALUES ('Bey Whiticer', 0, 0, NULL) \n           COMMIT TRANSACTION \n         &lt;\/inputbuf&gt; \n      &lt;\/process&gt; \n   &lt;\/process-list&gt; \n   &lt;resource-list&gt; \n      &lt;pagelock fileid=\"1\" pageid=\"204\" dbid=\"6\" subresource=\"FULL\" \n                objectname=\"Deadlocks.dbo.Customers\" id=\"lock2ec9be480\"\" mode=\"X\" \n                associatedObjectId=\"72057594038779904\"&gt; \n         &lt;owner-list&gt; \n            &lt;owner id=\"process2ec302188\"\"\"\"\" mode=\"X\" \/&gt; \n         &lt;\/owner-list&gt; \n         &lt;waiter-list&gt; \n            &lt;waiter id=\"process2f8025c38\"\"\"\"\" mode=\"X\" requestType=\"wait\" \/&gt; \n         &lt;\/waiter-list&gt; \n      &lt;\/pagelock&gt; \n      &lt;keylock hobtid=\"72057594038976512\" dbid=\"6\" \n               objectname=\"Deadlocks.dbo.Customers\" \n               indexname=\"idx_Customers_CustomerName\" id=\"lock2ec9be580\"\" \n               mode=\"RangeS-S\" associatedObjectId=\"72057594038976512\"&gt; \n         &lt;owner-list&gt; \n            &lt;owner id=\"process2f8025c38\"\"\"\"\" mode=\"RangeS-S\" \/&gt; \n         &lt;\/owner-list&gt; \n         &lt;waiter-list&gt; \n            &lt;waiter id=\"process2ec302188\"\"\"\"\" mode=\"RangeI-N\" requestType=\"convert\" \/&gt; \n         &lt;\/waiter-list&gt; \n      &lt;\/keylock&gt; \n   &lt;\/resource-list&gt; \n&lt;\/deadlock&gt;<\/pre>\n<p class=\"caption\">Listing 19: Deadlock graph for Range scan and <code>SERIALIZABLE<\/code> deadlock<\/p>\n<p>The key indicator that we have a deadlock resulting from <code>SERIALIZABLE<\/code> range locks is the presence of this row:<\/p>\n<pre class=\"listing lang-xml\">\t&lt;waiter id=\"process2ec302188\"\"\"\"\" mode=\"RangeI-N\" requestType=\"convert\" \/&gt;\n<\/pre>\n<p>The process is attempting to convert a lock from <code>RangeS-S<\/code> to <code>RangeI-N<\/code> to do the insert and it can&#8217;t.<\/p>\n<p>So how do we fix this problem? There are a couple of ways. One way is to ditch the entire <code>IF EXISTS<\/code><code>...<\/code> construct and instead use a <code>MERGE<\/code> statement. A <code>MERGE<\/code> operation as a single atomic operation, it doesn&#8217;t need <code>SERIALIZABLE<\/code> isolation level to work correctly and isn&#8217;t prone to these deadlocks. On the downside, there are <a href=\"http:\/\/sqlblog.com\/blogs\/paul_white\/archive\/tags\/Merge\/default.aspx\">some annoying bugs<\/a> with <code>MERGE<\/code>.<\/p>\n<p>Another option is to ditch the <code>IF EXISTS<\/code> and use two statements, an <code>update<\/code> and an <code>insert<\/code>.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">UPDATE  dbo.Customers\nSET     LatestOrderStatus = NULL ,\n        OrderLimit = 0\nWHERE   CustomerName = @p1;\n\nINSERT  INTO dbo.Customers\n        ( CustomerName ,\n          RegionID ,\n          OrderLimit ,\n          LatestOrderStatus\n        )\n        SELECT  @p1~ ,\n                0 ,\n                0 ,\n                NULL\n        WHERE   NOT EXISTS ( SELECT 1\n                             FROM   dbo.Customers AS c\n                             WHERE  CustomerName = @p1 )\n<\/pre>\n<p class=\"caption\">Listing 20: Replace <code>IF EXISTS<\/code> with separate <code>UPDATE<\/code> and <code>INSERT<\/code> statements<\/p>\n<p>This may look strange, especially the <code>SELECT<\/code> with a <code>WHERE<\/code> but no <code>FROM<\/code>, but this is valid. Now that we have two separate statements without a transaction, the worst that can happen is that the <code>INSERT<\/code> is blocked; it can no longer cause deadlocks.<\/p>\n<p>Finally, if we want to keep the <code>IF EXISTS<\/code> structure, we can add another locking hint.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">BEGIN TRANSACTION\nIF EXISTS ( SELECT  1\n            FROM    [dbo].[Customers] WITH ( ROWLOCK, UPDLOCK )\n            WHERE   CustomerName = @p1 )\n    UPDATE  dbo.Customers\n    SET     LatestOrderStatus = NULL ,\n            OrderLimit = 0\n    WHERE   CustomerName = @p1;\nELSE\n    INSERT  INTO dbo.Customers\n            ( CustomerName ,\n              RegionID ,\n              OrderLimit ,\n              LatestOrderStatus\n            )\n    VALUES  ( @p1 ,\n              0 ,\n              0 ,\n              NULL\n            );\nCOMMIT TRANSACTION\n<\/pre>\n<p class=\"caption\">Listing 21: Using the <code>UPDLOCK<\/code> hint<\/p>\n<p>Update locks are not compatible with other update locks and so this serializes the entire process. The second process to run is blocked right at the beginning by the locks that the first one has taken. Until the first one completes the entire process, the second one cannot start to run.<\/p>\n<h3>Partition escalation deadlocks<\/h3>\n<p>This type of deadlock is only possible on a partitioned table where the table&#8217;s <code>lock_escalation<\/code> option has been set to <code>AUTO<\/code>, which on a partitioned table allows lock escalation to go to the partition level rather than the table.<\/p>\n<p>The default <code>lock<\/code><code>_<\/code><code>escalation<\/code> option, <code>TABLE<\/code>, does not allow this deadlock to occur and the fact that this deadlock can occur when lock escalation is set to <code>AUTO<\/code> is the reason why it&#8217;s not the default on partitioned tables!<\/p>\n<p>Before we dig into this one, we need a very quick summary of lock escalation (see <em>Further Reading<\/em> for more). SQL Server will start locking at the lowest granularity it thinks is reasonable for the operation in hand. Typically, it uses row or page locks. However, if the number of locks on a single object exceeds a threshold (currently 5000), or the memory used for locks exceeds a percentage of SQL&#8217;s memory (currently 40%), then it triggers lock escalation. SQL Server will try to replace the row or page locks on the object with a table-level or partition-level lock, depending on the setting of the table&#8217;s <code>LOCK_ESCALATION<\/code> option, and on whether or not the index is partitioned.<\/p>\n<p>Let&#8217;s look at a deadlock graph (yes, this is a contrived example).<\/p>\n<pre class=\"listing prettyprint lang-xml\">&lt;deadlock&gt;\n  &lt;victim-list&gt;\n    &lt;victimProcess id=\"process2ed5bd498\"\"\"\"\" \/&gt;\n  &lt;\/victim-list&gt;\n  &lt;process-list&gt;\n    &lt;process id=\"process2ed5bd498\"\"\"\"\" waitresource=\"HOBT: 9:72057594040090624 \"\n                 transactionname=\"user_transaction\" lockMode=\"IS\" spid=\"52\"             \n                 sbid=\"0\" ecid=\"0\" priority=\"0\" trancount=\"1\"             \n                 lastbatchstarted=\"2013-12-19T16:18:38.003\"             \n                 lastbatchcompleted=\"2013-12-19T16:07:51.777\"             \n                 clientapp=\"Microsoft SQL Server Management Studio - Query\"             \n                 isolationlevel=\"repeatable read (3)\"&gt;\n      &lt;executionStack&gt;\n        &lt;frame procname=\"adhoc\" line=\"10\" stmtstart=\"452\" stmtend=\"616\"\n               sqlhandle=\"0x02000000d4cccd376fce07789a95b02531a0c3b366bca36                           \n                          00000000000000000000000000000000000000000\"&gt;\n          SELECT MAX(invoiceDate) FROM dbo.Invoices AS i           \n           WHERE InvoiceDate &amp;gt; '2013\/12\/01'\n        &lt;\/frame&gt;\n      &lt;\/executionStack&gt;\n      &lt;inputbuf&gt;\n        UPDATE dbo.Invoices\n        SET Status = 'Archived'\n        WHERE InvoiceDate BETWEEN '2013\/11\/02' AND '2013\/11\/20'\n\n        SELECT MAX(invoiceDate) FROM dbo.Invoices AS i                         \n                                WHERE InvoiceDate &gt; '2013\/12\/01'\n\n      &lt;\/inputbuf&gt;\n    &lt;\/process&gt;\n    &lt;process id=\"process2ed5bccf8\"\"\"\" waitresource=\"HOBT: 9:72057594040025088 \"\n                 transactionname=\"user_transaction\" lockMode=\"IS\" spid=\"53\"             \n                 lastbatchstarted=\"2013-12-19T16:18:38.893\"              \n                 lastbatchcompleted=\"2013-12-19T16:17:35.670\"             \n                 clientapp=\"Microsoft SQL Server Management Studio - Query\"             \n                 isolationlevel=\"repeatable read (3)\" &gt;\n      &lt;executionStack&gt;\n        &lt;frame procname=\"adhoc\" line=\"10\" stmtstart=\"452\" stmtend=\"616\"               \n                sqlhandle=\"0x0200000005e48d295a25040639fd7bd6a903f9f84275e574\n                           0000000000000000000000000000000000000000\"&gt;\n        &lt;\/frame&gt;\n        &lt;frame procname=\"adhoc\" line=\"10\" stmtstart=\"452\" stmtend=\"616\"\n                sqlhandle=\"0x02000000d51ecc3334b00dd0f0886da242d6ff064c838e07\n                           0000000000000000000000000000000000000000\"&gt;\n          SELECT MAX(invoiceDate) FROM dbo.Invoices AS i\n            WHERE InvoiceDate &amp;lt; '2013\/12\/01'\n        &lt;\/frame&gt;\n      &lt;\/executionStack&gt;\n      &lt;inputbuf&gt;\n        UPDATE dbo.Invoices\n        SET Status = 'Archived'\n        WHERE InvoiceDate BETWEEN '2013\/12\/02' AND '2013\/12\/20'\n\n        SELECT MAX(invoiceDate) FROM dbo.Invoices AS i\n                                  WHERE InvoiceDate &lt; '2013\/12\/01'\n      &lt;\/inputbuf&gt;\n    &lt;\/process&gt;\n  &lt;\/process-list&gt;\n  &lt;resource-list&gt;\n    &lt;hobtlock hobtid=\"72057594040090624\" subresource=\"FULL\" dbid=\"9\"\n                  objectname=\"Deadlocks.dbo.Invoices\" indexname=\"idx_Test\"              \n                  id=\"lock2de692e80\"\" mode=\"X\"\n                  associatedObjectId=\"72057594040090624\"&gt;\n      &lt;owner-list&gt;\n        &lt;owner id=\"process2ed5bccf8\"\"\"\" mode=\"X\" \/&gt;\n      &lt;\/owner-list&gt;\n      &lt;waiter-list&gt;\n        &lt;waiter id=\"process2ed5bd498\"\"\"\"\" mode=\"IS\" requestType=\"convert\" \/&gt;\n      &lt;\/waiter-list&gt;\n    &lt;\/hobtlock&gt;\n    &lt;hobtlock hobtid=\"72057594040025088\" subresource=\"FULL\" dbid=\"9\"\n                  objectname=\"Deadlocks.dbo.Invoices\" indexname=\"idx_Test\"              \n                  id=\"lock2e1645800\"\" mode=\"X\"              \n                  associatedObjectId=\"72057594040025088\"&gt;\n      &lt;owner-list&gt;\n        &lt;owner id=\"process2ed5bd498\"\"\"\"\" mode=\"X\" \/&gt;\n      &lt;\/owner-list&gt;\n      &lt;waiter-list&gt;\n        &lt;waiter id=\"process2ed5bccf8\"\"\"\" mode=\"IS\" requestType=\"convert\" \/&gt;\n      &lt;\/waiter-list&gt;\n    &lt;\/hobtlock&gt;\n  &lt;\/resource-list&gt;\n&lt;\/deadlock&gt;\n<\/pre>\n<p class=\"caption\">Listing 22: Deadlock graph for a partition escalation deadlock<\/p>\n<p>What happened here is that each of the updates affected only one partition. At the point that the escalation threshold was hit, SQL Server escalated the initial row locks to partition-level locks (because partition-level locking is enabled). At the point that the updates finished, each session had an exclusive lock on a different, single partition of the table. The <code>select<\/code> then requires a lock on the other partition of the table, which causes the deadlock.<\/p>\n<p>Essentially:<\/p>\n<ul>\n<li>Process 1 requests row-level X locks on Partition 1 of a table<\/li>\n<li>Process 2 requests row-level X locks on Partition 2 of a table<\/li>\n<li>Process 1&#8217;s locks are escalated to partition-level<\/li>\n<li>Process 2&#8217;s locks are escalated to partition-level<\/li>\n<\/ul>\n<p>At this point, each process holds an exclusive lock on a different partition of the table<\/p>\n<ul>\n<li>Process 1 requests a shared lock on a row in Partition 2 of the table. It is blocked.<\/li>\n<li>Process 2 requests a shared lock on a row in Partition 1 of the table. It is blocked<\/li>\n<\/ul>\n<p>Now we have a deadlock and SQL Server&#8217;s deadlock detector identifies it and kills one of the sessions<\/p>\n<p>If the <code>lock_escalation<\/code> option was set to the default of <code>table<\/code>, this deadlock would not occur. With the lock escalation set to <code>table<\/code>, if both updates had started taking locks and then one or both triggered a lock escalation, the escalation to table locks would fail. Locks cannot be escalated to table if there are any incompatible locks already on any of the rows or pages in the table. With the escalation having failed, the X locks that each update took would stay as row locks. The <code>select<\/code>, which then needed a lock on a row not affected by the update, could then get the lock it needs and both queries would complete without deadlocking<\/p>\n<p>With deadlocks like this, where one of the locks is at the HoBT level, the first step should be changing the index&#8217;s lock settings to escalate to table. If the deadlock is caused by partition-level lock escalation then this change will remove the chance of deadlock occurring. If the deadlocks still occur even with escalation set to table, then the deadlock should be debugged as for a reader-writer or writer-writer deadlock, as detailed earlier.<\/p>\n<h3>Handling deadlocks<\/h3>\n<p>The best way to deal with deadlocks is via error handling and retries. All application code that deals with database queries should have error handling. To be honest, all application code should have proper error handling, but I might as well also wish for peace on earth and an end to poverty.<\/p>\n<p>If application code assumes that any database query will succeed, that code is brittle and a potential problem because there are a number of issues that can cause database calls to fail.<\/p>\n<p>Any form of high availability failover drops connections. If the application assumes that a connection is open and runs a query it will get an error. If the application doesn&#8217;t handle the error properly, the application can crash or the user can get a message about something they probably don&#8217;t understand and almost certainly don&#8217;t care about. Likewise, deadlocks, or update conflicts, depending on isolation level, can cause a query to fail. If the application doesn&#8217;t handle the error properly, the consequences are the same.<\/p>\n<p>Application code should have error handling and retry logic on any data-access code. If a query fails, the error handling code should look at what error was raised and decide, based on that error, whether to retry the query, or log the failure and send the user a meaningful error (see <em>Further Reading<\/em>).<\/p>\n<h2>Summary<\/h2>\n<p>I hope that this article will serve you as a good reference piece, in recognizing and resolving the different types of deadlock that can afflict your SQL Servers, and the business operations that run on them. I hope it will also help you to identify the sort of coding mistakes and (anti)patterns that make them more likely to occur, especially on busy systems with a high level of concurrent access.<\/p>\n<p>If you can deploy on your SQL Server instances one of the row-versioning isolation levels (<code>SNAPSHOT<\/code> or <code>READ_COMMITTED_SNAPSHOT<\/code>), then by definition you rule out the possibility of any form of Reader-Writer deadlock, since readers won&#8217;t acquire Shared locks in these modes.<\/p>\n<p>Beyond that, avoiding deadlocks is largely an exercise in query and index tuning. I haven&#8217;t had room in this piece to digress too far into this huge topic, but the references I provide in <em>Further Reading<\/em>, below, should help direct your efforts.<\/p>\n<h2>Further Reading<\/h2>\n<ul class=\"reference-list\">\n<li><strong>Query Tuning<\/strong>\n<ul>\n<li><a href=\"https:\/\/www.simple-talk.com\/sql\/performance\/finding-the-causes-of-poor-performance-in-sql-server,-part-2\/\">Finding the causes of poor performance in SQL Server, Part 2<\/a>, by Gail Shaw<\/li>\n<li><a href=\"http:\/\/www.amazon.co.uk\/Server-Query-Performance-Tuning-Edition\/dp\/1430242035\/\">SQL Server 2012 Query Performance Tuning 3rd Edition<\/a> (Book), by Grant Fritchey<\/li>\n<li>Chapters 5 and 6 of <a href=\"https:\/\/www.simple-talk.com\/books\/sql-books\/troubleshooting-sql-server-a-guide-for-the-accidental-dba\/\">Troubleshooting SQL Server<\/a> (free eBook), by Jonathan Kehayias and Ted Krueger<\/li>\n<\/ul>\n<\/li>\n<li><strong>Lock types, lock modes, lock compatibility<\/strong>\n<ul>\n<li>Chapter 6 of <a href=\"https:\/\/www.simple-talk.com\/books\/sql-books\/troubleshooting-sql-server-a-guide-for-the-accidental-dba\/\">Troubleshooting SQL Server<\/a><\/li>\n<li>Chapters 2 and 3 of Kalen Delaney&#8217;s <a href=\"https:\/\/www.simple-talk.com\/books\/sql-books\/sql-server-concurrency-locking,-blocking-and-row-versioning\/\">SQL Server Concurrency<\/a> (free eBook).<\/li>\n<\/ul>\n<\/li>\n<li><strong>Transactions, isolation levels (<\/strong><strong>i<\/strong><strong>nc.<\/strong><strong> row versioning<\/strong><strong>)<\/strong>\n<ul>\n<li>Chapters 1 and 6 of <a href=\"https:\/\/www.simple-talk.com\/books\/sql-books\/sql-server-concurrency-locking,-blocking-and-row-versioning\/\">SQL Server Concurrency<\/a>.<\/li>\n<\/ul>\n<\/li>\n<li><strong>Error handling (SQL Server and client-side)<\/strong>\n<ul>\n<li>Chapter 8 of <a href=\"https:\/\/www.simple-talk.com\/books\/sql-books\/defensive-database-programming\/\">Defensive Database Programming<\/a> (free eBook) by Alexander Kuznetsov<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/div>\n\n\n<section id=\"my-first-block-block_403a6dad0ff16fd28628dcc018daadfb\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Fast, reliable and consistent SQL Server development&#8230;<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            &#8230;with SQL Toolbelt Essentials. 10 ingeniously simple tools for accelerating development, reducing risk, and standardizing workflows.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/products\/sql-toolbelt-essentials\/\" class=\"btn btn--secondary btn--lg\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: How to find and prevent deadlocks in SQL Server<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. How do you find deadlocks in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>In SQL Server 2012 and later, retrieve deadlock graphs from the system_health Extended Events session using the ring_buffer target. Query the session for events with name = \u2018xml_deadlock_report\u2019 to get the deadlock graph XML. You can also enable trace flag 1222 to write deadlock details to the error log, or use the Profiler deadlock graph event. The Extended Events approach is preferred because the system_health session runs by default &#8211; no setup required.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. What is the difference between blocking and deadlocking in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Blocking is transient &#8211; one session holds a lock that another needs, but the blocking chain has a head blocker that will eventually complete and release its locks. A deadlock is permanent &#8211; two or more sessions form a circular chain where each is waiting for the other, so none can ever complete without intervention. SQL Server automatically resolves deadlocks by killing one session; blocking resolves naturally when the head blocker finishes.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. How do you prevent deadlocks in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Reduce deadlock risk by: accessing tables in a consistent order across all transactions, keeping transactions as short as possible, avoiding user interaction during transactions, using appropriate isolation levels (READ COMMITTED SNAPSHOT can eliminate many read-write deadlocks), and adding covering indexes to reduce the number of resources each query needs to lock. For specific deadlock types, the fix varies &#8211; bookmark deadlocks require covering indexes, serializable deadlocks may need isolation level changes.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. What is a deadlock victim in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>The deadlock victim is the session SQL Server kills to resolve a deadlock. It receives error 1205 and its transaction is rolled back. The lock monitor chooses the victim based on DEADLOCK_PRIORITY (LOW\/NORMAL\/HIGH) first, then on rollback cost &#8211; the session requiring the least work to roll back gets killed. You can influence this with SET DEADLOCK_PRIORITY but the better approach is preventing deadlocks entirely.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Complete guide to SQL Server deadlocks with reproducible examples. Covers bookmark deadlocks, key-range deadlocks, cascade deadlocks, deadlock graphs, DEADLOCK_PRIORITY, and prevention strategies.&hellip;<\/p>\n","protected":false},"author":221857,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143529],"tags":[4728,5559,5966,4206,4150,4151],"coauthors":[11297],"class_list":["post-1749","post","type-post","status-publish","format-standard","hentry","category-performance-sql-server","tag-blocking","tag-deadlocks","tag-monitor","tag-performance","tag-sql","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1749","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221857"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1749"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1749\/revisions"}],"predecessor-version":[{"id":109374,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1749\/revisions\/109374"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1749"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1749"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1749"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1749"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}