Introduction
Azure SQL Database deadlocks occur when two or more transactions hold locks on resources and each waits for a lock held by the other. SQL Server's deadlock detector kills one transaction (victim) to resolve the circular dependency.
Symptoms
Transaction deadlock:
Error: 1205, Severity: 13, State: 51
Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.Deadlock in application:
{
"error": {
"code": "SqlException",
"message": "Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim"
}
}High deadlock rate:
# Shows elevated deadlock count ```
Common Causes
- 1.Opposite lock order - Transactions acquire locks in different order
- 2.Long-running transactions - Extended lock hold time increases conflict
- 3.Explicit locks - SELECT WITH HOLDLOCK or explicit locking hints
- 4.Foreign key checks - Parent-child table lock contention
- 5.Book-look searches - Bookmark lookup deadlock pattern
- 6.Parallel queries - Intra-query parallelism deadlock
- 7.High concurrency - Many simultaneous transactions on same resources
Step-by-Step Fix
Step 1: Enable Deadlock Monitoring
```bash # Enable extended events for deadlock capture # Via Azure Portal or T-SQL:
# In SQL Server Management Studio or via sqlcmd: sqlcmd -S my-server.database.windows.net -d my-database -U admin -P password
# Create extended events session CREATE EVENT SESSION [deadlock_capture] ON SERVER ADD EVENT sqlserver.database_xml_deadlock_report ADD TARGET package0.event_file(SET filename = 'deadlock.xel') WITH (STARTUP_STATE = ON); GO
ALTER EVENT SESSION [deadlock_capture] ON SERVER STATE = START; GO ```
Step 2: Query Deadlock Graph
```bash # Query sys.dm_xe_session_targets for deadlock info sqlcmd -S my-server.database.windows.net -d my-database -U admin -P password -Q " SELECT xed.value('@timestamp', 'datetime') as CreationTime, xed.query('.') as DeadlockGraph FROM sys.dm_xe_session_targets st JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address CROSS APPLY st.target_data.nodes('Event[@name=\"database_xml_deadlock_report\"]') AS XEventData(xed) WHERE s.name = 'deadlock_capture' ORDER BY CreationTime DESC "
# Deadlock graph shows: # - Victim process # - Lock owners # - Locked resources # - Wait-for graph ```
Step 3: Analyze Deadlock Graph
<!-- Sample deadlock graph structure -->
<deadlock>
<victim-list>
<victimProcess id="process54" />
</victim-list>
<process-list>
<process id="process54">
<executionStack>
<frame>UPDATE Orders SET Status = 'Processing' WHERE OrderId = 1</frame>
</executionStack>
<inputbuf>UPDATE Orders SET Status = 'Processing' WHERE OrderId = 1</inputbuf>
</process>
<process id="process55">
<executionStack>
<frame>UPDATE Inventory SET Quantity = Quantity - 1 WHERE ProductId = 1</frame>
</executionStack>
<inputbuf>UPDATE Inventory SET Quantity = Quantity - 1 WHERE ProductId = 1</inputbuf>
</process>
</process-list>
<resource-list>
<keylock objectname="Orders" mode="X">
<owner-list><owner id="process54" /></owner-list>
<waiter-list><waiter id="process55" /></waiter-list>
</keylock>
<keylock objectname="Inventory" mode="X">
<owner-list><owner id="process55" /></owner-list>
<waiter-list><waiter id="process54" /></waiter-list>
</keylock>
</resource-list>
</deadlock>Step 4: Standardize Lock Order
```sql -- Problem: Transactions lock tables in different order -- Transaction A: Orders -> Inventory -- Transaction B: Inventory -> Orders
-- Solution: Always lock in consistent order -- All transactions: Orders -> Inventory
BEGIN TRANSACTION; -- Always lock Orders first UPDATE Orders SET Status = 'Processing' WHERE OrderId = 1; -- Then lock Inventory UPDATE Inventory SET Quantity = Quantity - 1 WHERE ProductId = 1; COMMIT TRANSACTION;
-- Alternative: Use sp_getapplock for explicit ordering BEGIN TRANSACTION; EXEC sp_getapplock @Resource = 'Order_1', @LockMode = 'Exclusive'; -- Process order... EXEC sp_releaseapplock @Resource = 'Order_1'; COMMIT TRANSACTION; ```
Step 5: Reduce Transaction Duration
```sql -- Problem: Long transaction holding locks BEGIN TRANSACTION; -- Many operations... SELECT * FROM LargeTable; -- Slow query UPDATE TargetTable SET Value = ...; -- More operations... COMMIT TRANSACTION;
-- Solution: Minimize transaction scope -- Move read operations outside transaction SELECT * FROM LargeTable; -- Outside transaction
BEGIN TRANSACTION; -- Only necessary updates inside transaction UPDATE TargetTable SET Value = ...; COMMIT TRANSACTION;
-- Or use snapshot isolation SET TRANSACTION ISOLATION LEVEL SNAPSHOT; BEGIN TRANSACTION; -- Reads don't block writes UPDATE TargetTable SET Value = ...; COMMIT TRANSACTION; ```
Step 6: Implement Retry Logic
```sql -- Application retry pattern for deadlock victims DECLARE @retry_count INT = 0; DECLARE @max_retries INT = 3; DECLARE @success BIT = 0;
WHILE @retry_count < @max_retries AND @success = 0 BEGIN BEGIN TRY BEGIN TRANSACTION; -- Transaction operations UPDATE Orders SET Status = 'Processing' WHERE OrderId = 1; UPDATE Inventory SET Quantity = Quantity - 1 WHERE ProductId = 1; COMMIT TRANSACTION; SET @success = 1; END TRY BEGIN CATCH IF ERROR_NUMBER() = 1205 -- Deadlock BEGIN SET @retry_count = @retry_count + 1; WAITFOR DELAY '00:00:01'; -- Wait 1 second before retry END ELSE BEGIN -- Other errors - don't retry THROW; END END CATCH END
IF @success = 0 BEGIN RAISERROR('Transaction failed after %d retries', 16, 1, @max_retries); END ```
Step 7: Use Read Committed Snapshot
```bash # Enable RCSI at database level (via Azure Portal or T-SQL) sqlcmd -S my-server.database.windows.net -d my-database -U admin -P password -Q " ALTER DATABASE my-database SET READ_COMMITTED_SNAPSHOT ON; "
# RCSI benefits: # - Readers don't block writers # - Writers don't block readers # - Reduces many deadlock scenarios
# Verify RCSI enabled sqlcmd -S my-server.database.windows.net -d master -U admin -P password -Q " SELECT name, is_read_committed_snapshot_on FROM sys.databases WHERE name = 'my-database'; " ```
Step 8: Optimize Foreign Key Constraints
```sql -- Foreign key checks can cause deadlocks -- Parent table locked when updating child
-- Option 1: Batch updates to reduce FK checks INSERT INTO OrderItems (OrderId, ProductId, Quantity) SELECT OrderId, ProductId, Quantity FROM TempOrderItems; -- Batch insert
-- Option 2: Use indexed FK columns for faster checks -- Ensure FK columns are indexed CREATE INDEX IX_OrderItems_OrderId ON OrderItems(OrderId);
-- Option 3: Consider NOT FOR REPLICATION for FK (careful) -- ALTER TABLE OrderItems WITH NOCHECK ADD CONSTRAINT FK_OrderItems_Orders -- FOREIGN KEY (OrderId) REFERENCES Orders(OrderId) NOT FOR REPLICATION; ```
Step 9: Monitor Deadlock Rate
# Create alert for high deadlock rate az monitor metrics alert create \ --name sql-deadlock-alert \ --resource-group my-rg \ --scopes /subscriptions/SUB/resourceGroups/my-rg/providers/Microsoft.Sql/servers/my-server \ --condition "avg deadlocks > 10" \ --window-size 1h ```
Step 10: Use Query Hints Carefully
```sql -- UPDLOCK hint prevents read-deadlock patterns SELECT * FROM Orders WITH (UPDLOCK, HOLDLOCK) WHERE OrderId = 1; -- Now safe to update without another transaction interfering
-- NOLOCK hint avoids read locks (but may read dirty data) SELECT * FROM Inventory WITH (NOLOCK); -- Use only for read-only reporting queries
-- ROWLOCK hint for finer-grain locking UPDATE Orders WITH (ROWLOCK) SET Status = 'Processing' WHERE OrderId = 1; ```
Deadlock Resolution Strategies
| Strategy | Impact | Use Case |
|---|---|---|
| Consistent lock order | Prevents | All multi-table transactions |
| Short transactions | Reduces | Long-running batch jobs |
| RCSI | Eliminates read locks | High read/write mix |
| Retry logic | Resolves | Transient deadlocks |
| Snapshot isolation | Prevents | Read-heavy workloads |
Verification
```sql -- After implementing fixes -- Monitor deadlock count over time sqlcmd -S my-server.database.windows.net -d my-database -U admin -P password -Q " SELECT COUNT(*) as DeadlockCount FROM sys.dm_xe_session_targets st JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address WHERE s.name = 'deadlock_capture' "
-- Should show reduced deadlock count
-- Query performance impact SELECT * FROM sys.dm_exec_query_stats WHERE execution_count > 100 ORDER BY total_elapsed_time DESC; ```
Prevention
To prevent Azure SQL database deadlock issues from recurring, implement these proactive measures:
1. Monitor Deadlock Events
groups:
- name: azure-sql-deadlock
rules:
- alert: AzureSQLDeadlockRateHigh
expr: |
rate(azure_sql_deadlocks_total[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "High rate of Azure SQL deadlocks"2. Use Consistent Lock Ordering
```sql -- Always access tables in same order across transactions -- Transaction A: orders then items BEGIN TRANSACTION; UPDATE orders SET status = 'processing' WHERE id = 1; UPDATE order_items SET processed = 1 WHERE order_id = 1; COMMIT;
-- Transaction B: same order (not items then orders) BEGIN TRANSACTION; UPDATE orders SET status = 'shipped' WHERE id = 2; UPDATE order_items SET shipped = 1 WHERE order_id = 2; COMMIT; ```
3. Enable Deadlock Monitoring
```sql -- Create extended event session for deadlock capture CREATE EVENT SESSION deadlock_capture ON SERVER ADD EVENT sqlserver.database_deadlock ADD TARGET package0.event_file (SET filename = 'deadlock_capture.xel') WITH (MAX_DISPATCH_LATENCY = 5 SECONDS);
ALTER EVENT SESSION deadlock_capture ON SERVER STATE = START; ```
Best Practices Checklist
- [ ] Monitor deadlock events
- [ ] Use consistent lock ordering
- [ ] Enable deadlock monitoring
- [ ] Keep transactions short
- [ ] Add appropriate indexes
- [ ] Use appropriate isolation level
Related Issues
- [Fix Azure SQL Database DTU Limit](/articles/fix-azure-sql-database-dtu-limit)
- [Fix Azure SQL Transaction Log Full](/articles/fix-azure-sql-transaction-log-full)
- [Fix Azure SQL Query Performance Slow](/articles/fix-azure-sql-database-dtu-limit)
Related Articles
- [Technical troubleshooting: Fix Azure Aks Pod Crashloopbackoff Issue in Azure](azure-aks-pod-crashloopbackoff)
- [Technical troubleshooting: Fix Azure Api Management Policy Expression Runtime](azure-api-management-policy-expression-runtime-error)
- [Technical troubleshooting: Fix Azure App Configuration Feature Flag Not Refre](azure-app-configuration-feature-flag-not-refreshing)
- [Technical troubleshooting: Fix Azure App Service 503 Always On Disabled Issue](azure-app-service-503-always-on-disabled)
- [Technical troubleshooting: Fix Azure Application Gateway Err SSL Unrecognized](azure-application-gateway-err-ssl-unrecognized-name-alert)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "Fix Azure SQL Database Deadlock", "description": "Troubleshoot Azure SQL deadlocks. Analyze deadlock graphs, optimize transaction order, and implement retry logic.", "url": "https://www.fixwikihub.com/fix-azure-sql-database-deadlock", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-04-03T18:04:00.334Z", "dateModified": "2026-04-03T18:04:00.334Z" } </script>