Locking and Blocking Analysis – RockyPC / WideWorldImporters – 2026-05-31 16:53:29 UTC
Executive Summary
Top Priority 1Resolve the open transaction pattern in session 62. It is holding an X key lock on Application.StateProvinces and is directly blocking session 66, which then blocks session 63.
Top Priority 2Enable row-versioned reads for this workload, preferably READ_COMMITTED_SNAPSHOT, because the current reader in session 66 is blocked by an update lock path under normal READ COMMITTED.
Top Priority 3Standardize transaction order across the application. Current SQL shows one path updating StateProvinces first and another updating Countries first, which already produced a recent deadlock in WideWorldImporters.
Top Priority 4Add the missing nonclustered index on Application.Cities(StateProvinceID) INCLUDE (CityName) to reduce scan work in the blocked reader and shrink shared-lock exposure time.
- The live lock-blocking chain is 62 → 66 → 63.
- Session 60 is not a real lock blocker; its waits are
CXSYNC_PORT,CXPACKET, andCXCONSUMER, which indicate intra-query parallelism waits, not lock blocking. - Primary root cause category: Open transaction with no activity.
- Secondary contributing categories: Hotspot table or index and Missing index causing lock amplification.
Scope & Context
| Item | Value |
|---|---|
| Server | RockyPC |
| Database | WideWorldImporters |
| SQL Server Version | SQL Server 2022 (16.x), 16.0.1180.1, RTM-GDR |
| Edition / Feature Posture | Developer Edition, Enterprise engine capabilities available, online operations supported |
| Database Read Semantics | RCSI OFF, Snapshot Isolation OFF |
| Observation Time | Based on analysis as of 2026-05-31 16:53:29 UTC |
Key Findings
1. Blocking is active and lock-based in WideWorldImporters
- Confirmed from Active Blocking Chains: session 62 blocks session 66 on
LCK_M_Sfor 17,299 ms. - Confirmed from Active Blocking Chains: session 66 then blocks session 63 on
LCK_M_Ufor 13,008 ms. - Confirmed from Lock Inventory: session 62 holds
KEY ... StateProvinces ... Mode=X Status=GRANT, while session 66 waits forMode=Sand session 63 waits forMode=Uon the same indexPK_Application_StateProvinces.
2. Lead blocker is session 62
- Session 62 holds the granted exclusive key lock on
Application.StateProvinces. - Its transaction is open for 19 seconds: Long-Running Transactions shows
TxID=4672039, Session=62, Duration=19s. - The lock-holding SQL text from Lock Inventory is:
BEGIN TRAN; UPDATE Application.StateProvinces SET LatestRecordedPopulation = LatestRecordedPopulation + 1, ValidFrom = SYSUTCDATETIME() WHERE StateProvinceCode = N'VA'; WAITFOR DELAY '00:00:10'; - This is an open transaction intentionally holding locks after the update and before commit.
3. Most important blocked statements
- Lead blocker statement: session 62 effectively blocks with
UPDATE Application.StateProvinces ... WHERE StateProvinceCode = N'VA'; WAITFOR DELAY '00:00:10'; - First waiter statement: session 66 waits on
SELECT CityName, StateProvinceName, sp.LatestRecordedPopulation, CountryName FROM Application.Cities AS city JOIN Application.StateProvinces AS sp ON city.StateProvinceID = sp.StateProvinceID JOIN Application.Countries AS ctry ON sp.CountryID = ctry.CountryID WHERE sp.StateProvinceCode = N'VA' - Second waiter statement: session 63 waits on
UPDATE Application.StateProvinces SET LatestRecordedPopulation = LatestRecordedPopulation + 1, ValidFrom = SYSUTCDATETIME() WHERE StateProvinceCode = N'VA' - Amplifying batch context: session 63 previously updated
Application.Countriesin the same transaction, then tried to updateStateProvinces. That cross-object transaction order is important because it matches the deadlock pattern seen historically.
4. Session 60 is not a lock blocker in this case
- Active Blocking Chains lists session 60 against itself with waits
CXSYNC_PORT,CXPACKET, andCXCONSUMER. - Those are parallelism coordination waits, not evidence of one session blocking another on locks.
- Lock Inventory for session 60 shows tempdb page and database locks, but there is no corresponding lock waiter chain tied to user sessions in
WideWorldImporters. - This is a separate performance issue, not the root cause of the blocking event under review.
Blocking Tree
Figure: live lock chain in WideWorldImporters. Session 62 is the root blocker; session 66 is both a waiter and a blocker.
62 (X lock on Application.StateProvinces PK row for 'VA')
└─ blocks 66 (LCK_M_S, 17,299 ms)
Statement: SELECT ... FROM Application.Cities
JOIN Application.StateProvinces
JOIN Application.Countries
WHERE sp.StateProvinceCode = N'VA'
└─ blocks 63 (LCK_M_U, 13,008 ms)
Statement: UPDATE Application.StateProvinces
SET LatestRecordedPopulation = LatestRecordedPopulation + 1,
ValidFrom = SYSUTCDATETIME()
WHERE StateProvinceCode = N'VA'
Root Cause Classification
| Category | Assessment | Evidence |
|---|---|---|
| Open transaction with no activity | Primary root cause | Long-Running Transactions: session 62 open 19s. Lock Inventory: session 62 holds KEY ... StateProvinces ... Mode=X Status=GRANT. SQL includes BEGIN TRAN and WAITFOR DELAY '00:00:10', proving deliberate lock retention after update. |
| Long-running transaction | Confirmed contributor | Sessions 62, 66, and 63 all have open transactions of 19s, 17s, and 13s respectively. Blocking duration tracks transaction duration. |
| Hotspot table or index | Confirmed contributor | Lock Inventory: both waiters target PK_Application_StateProvinces. Index Usage: StateProvinces PK shows 9 scans and 7 updates on a tiny 53-row table, indicating repeated concentration on the same object and row set. |
| Missing index causing lock amplification | Confirmed contributor | Missing Index Candidates: impact 118.9 on object 402100473 with equality [StateProvinceID], include [CityName]. Blocker Query Plans session 66: embedded missing index on [Application].[Cities]([StateProvinceID]) INCLUDE ([CityName]) impact 93.0233. Index Usage: Cities PK scans=2 on ~37,940 rows. |
| Lock escalation | Not evidenced | No table lock wait appears. Current waits are key-level on PK_Application_StateProvinces, not escalated table locks. |
| SERIALIZABLE or REPEATABLE READ misuse | Not evidenced | All observed sessions show Isolation=ReadCommitted. |
| Implicit transactions | Not evidenced | SQL text explicitly uses BEGIN TRAN; no need to infer implicit transactions. |
| Parameter sniffing causing large scans | Low evidence | The blocked reader has a single literal predicate N'VA' and a missing-index signal. Current evidence better supports indexing and transaction-scope issues than parameter sniffing. |
| RBAR/cursor patterns | Not evidenced | No cursor or RBAR pattern appears in the provided blocking chain. |
| TempDB contention masquerading as blocking | Not root cause here | TempDB Contention: there are latch waits, but live blocking waits in WideWorldImporters are explicit LCK_M_S and LCK_M_U on StateProvinces. Session 60’s tempdb/parallelism issue is separate. |
Impact Metrics
| Metric | Value | Notes |
|---|---|---|
| Total sessions blocked by locks | 2 | Sessions 66 and 63 |
| Blocking chain depth | 3 sessions | 62 → 66 → 63 |
| Longest lock wait | 17,299 ms | Session 66 waiting on LCK_M_S |
| Cumulative current lock wait | 30,307 ms | 17,299 + 13,008 ms, excluding session 60 parallelism waits |
| Primary contended object | Application.StateProvinces |
Index PK_Application_StateProvinces |
| Business impact | Moderate now; high recurrence risk | Current blast radius is small, but it blocks both reads and writes on a shared business row and has a demonstrated deadlock history. |
StateProvinces and related joins can stall for 13–17+ seconds; concurrent update workflows on Countries/StateProvinces are exposed to deadlocks and user-visible timeouts if this pattern appears under higher concurrency.
Detailed Prioritized Recommendations
-
Shorten transaction scope and remove lock-holding idle time around
Application.StateProvinces- Fix text: Commit immediately after the
UPDATE Application.StateProvinces ... WHERE StateProvinceCode = N'VA'; do not leave the transaction open acrossWAITFOR, client think-time, or unrelated reads/updates. - Rationale: Lock Inventory shows session 62 holds
XonPK_Application_StateProvinces; Long-Running Transactions shows 19s duration; SQL text includesBEGIN TRANfollowed byWAITFOR DELAY '00:00:10'. That is direct causal evidence. - Estimated risk: Low to Medium. Risk is mainly application behavior change if current code assumes a broader transaction.
- Rollback plan: Revert the procedure or application batch to prior transaction boundaries.
- Confidence: High
- Estimated reduction in blocking: 70–90%
- Fix text: Commit immediately after the
-
Enable
READ_COMMITTED_SNAPSHOTforWideWorldImporters- Fix text: Turn on row-versioned read committed so readers do not wait on writers for this pattern.
- Rationale: Database Settings: RCSI OFF and Snapshot OFF. Active Blocking Chains: session 66 is a reader blocked with
LCK_M_Sby session 62. This is a textbook case where RCSI breaks reader-writer blocking. - Estimated risk: Medium. Changes read semantics to versioned reads and increases tempdb version-store usage.
- Rollback plan: Set
READ_COMMITTED_SNAPSHOT OFFduring a maintenance window if application validation fails. - Confidence: High
- Estimated reduction in blocking: 40–60% overall; near 100% for this specific read-vs-write wait
-
Standardize update order for
CountriesandStateProvinces- Fix text: Ensure every code path touching both tables acquires locks in the same order, preferably one consistent sequence such as
CountriesthenStateProvinces, or the reverse, but never both. - Rationale: Long-Running Transactions: session 63 batch updates
CountriesthenStateProvinces. Lock Inventory: session 61/62 sample shows another path updatingStateProvincesfirst. Deadlock History: recent deadlock inWideWorldImporterson 2026-05-31 15:37:34 UTC shows a user transaction waiting on a key lock in this database. The current code shape is consistent with conversion/order deadlocks. - Estimated risk: Medium. Requires code change across all competing workflows.
- Rollback plan: Revert procedure/application code to prior release if functional testing fails.
- Confidence: High
- Estimated reduction in blocking: 30–50%; deadlock reduction 70–95%
- Fix text: Ensure every code path touching both tables acquires locks in the same order, preferably one consistent sequence such as
-
Create the missing index on
Application.Cities- Fix text: Add a nonclustered index on
(StateProvinceID) INCLUDE (CityName). - Rationale: Missing Index Candidates: impact 118.9 with equality
[StateProvinceID]and include[CityName]. Blocker Query Plans session 66: missing index recommendation impact 93.0233 onApplication.Cities. Index Usage:Cities.PKhas scans on ~37,940 rows. Reducing scan work shortens read duration and lock retention under non-RCSI reads. - Estimated risk: Low. Additional storage and write overhead on
Cities. - Rollback plan: Drop the new index if regression or write overhead appears.
- Confidence: High
- Estimated reduction in blocking: 10–25%
- Additional note: Confirm with the Index Tuning goal for the
Application.Citiestable for a broader design review before promoting to production.
- Fix text: Add a nonclustered index on
-
Keep reads outside explicit transactions unless the read must be transactionally coupled to a later write
- Fix text: Remove
BEGIN TRANaround the session 66 read, or move the read after the write if business logic permits. - Rationale: Long-Running Transactions: session 66 batch is
BEGIN TRAN; SELECT ... ; UPDATE Application.StateProvinces .... The read becomes a blocker once it is waiting and later chains into session 63. Avoiding the transaction around the read reduces chain depth. - Estimated risk: Medium. Depends on consistency requirements.
- Rollback plan: Restore the original transaction wrapper.
- Confidence: Medium
- Estimated reduction in blocking: 15–35%
- Fix text: Remove
-
Do not treat session 60 as the lead blocking target for this incident
- Fix text: Separate parallelism tuning for
sp01108inSQLStormfrom the lock-blocking remediation inWideWorldImporters. - Rationale: Active Blocking Chains: session 60 only shows self-referential
CX*waits; no lock wait chain to other sessions. This is not the blocker causing the live lock issue. - Estimated risk: None
- Rollback plan: Not applicable
- Confidence: High
- Estimated reduction in blocking: 0% for this lock incident; avoids misdiagnosis
- Fix text: Separate parallelism tuning for
Code / Query Rewrite Guidance
Transaction scope rewrite
Problematic shape: update followed by idle time inside a transaction, for example BEGIN TRAN; UPDATE Application.StateProvinces ...; WAITFOR DELAY '00:00:10';.
Better shape: perform any waiting or non-critical work before the transaction, then keep the transaction limited to the shortest possible update/commit window.
- Before:
BEGIN TRAN → UPDATE StateProvinces → WAITFOR / client delay → COMMIT - After:
non-transactional prep → BEGIN TRAN → UPDATE StateProvinces → COMMIT
Consistent table access order
The current batches show two conflicting patterns:
UPDATE Application.StateProvinces ... ; ... UPDATE Application.Countries ...UPDATE Application.Countries ... ; ... UPDATE Application.StateProvinces ...
Choose one order and enforce it everywhere. A deterministic example is:
BEGIN TRAN → UPDATE Application.Countries ... → UPDATE Application.StateProvinces ... → COMMIT
This directly targets the deadlock pattern evidenced by the current batch shapes and recent deadlock capture.
Reader query improvement
The blocked reader is:
SELECT CityName, StateProvinceName, sp.LatestRecordedPopulation, CountryName FROM Application.Cities AS city JOIN Application.StateProvinces AS sp ON city.StateProvinceID = sp.StateProvinceID JOIN Application.Countries AS ctry ON sp.CountryID = ctry.CountryID WHERE sp.StateProvinceCode = N'VA'
With the recommended Cities(StateProvinceID) INCLUDE (CityName) index, SQL Server can satisfy the join to Cities with much less work. This reduces scan time and lowers the duration of shared-lock exposure when RCSI is not yet enabled.
Deadlock History & Long-Term Mitigations
Recent deadlock evidence
- Deadlock History:
2026-05-31 15:37:34 UTCinWideWorldImporters, victimspid=61,transactionname="user_transaction", waiting on aKEYresource in database 6 /WideWorldImporters. - This aligns with the current live pattern: short explicit user transactions, row/key-level contention, and conflicting update sequences.
Figure: simplified representation of the deadlock pattern supported by current batch text and recent deadlock history.
Long-term mitigations
- Enforce a single object access order across all procedures and application paths.
- Use RCSI to remove read-vs-write blocking where correctness permits.
- Keep transactions as short as possible and never hold locks during
WAITFORor client-side pauses. - For known deadlock-prone, low-priority retryable operations, consider application retry logic with bounded retries.
- Query Store and XE deadlock capture are already available here, which is good; continue retaining these for recurrence tracking.
Cross-Referenced Evidence
| Conclusion | Evidence Lines |
|---|---|
| Blocking is active | Active Blocking Chains: Blocker: session=62 ... Waiter: session=66, wait_type=LCK_M_S, wait_duration=17299ms; and Blocker: session=66 ... Waiter: session=63, wait_type=LCK_M_U, wait_duration=13008ms. |
| Session 62 is the lead blocker | Lock Inventory: KEY, DB=WideWorldImporters, Object=StateProvinces, Index=PK_Application_StateProvinces, Mode=X, Status=GRANT, Session=62. Long-Running Transactions: Session=62 ... Duration=19s. |
| Open transaction with no activity is primary cause | Lock Inventory SQL: BEGIN TRAN; UPDATE Application.StateProvinces ... WHERE StateProvinceCode = N'VA'; WAITFOR DELAY '00:00:10';. This proves lock holding beyond the update itself. |
| Reader is blocked by writer because RCSI is off | Database Settings: RCSI OFF, Snapshot Isolation OFF. Active Blocking Chains: session 66 LCK_M_S on a SELECT. |
StateProvinces is the hotspot |
Lock Inventory: both waits target PK_Application_StateProvinces. Index Usage: StateProvinces PK Scans=9, Updates=7, RowCount≈53. |
| Reader query is scan-prone and missing an index | Missing Index Candidates: impact 118.9, equality [StateProvinceID], include [CityName]. Blocker Query Plans session 66: missing index group impact 93.0233 on Application.Cities. Index Usage: Cities PK Scans=2, RowCount≈37,940. |
| Deadlock risk is real and recurring | Deadlock History: recent event at 2026-05-31 15:37:34 UTC in WideWorldImporters; current batches show conflicting update order across Countries and StateProvinces. |
| Session 60 is unrelated to the lock chain | Active Blocking Chains: session 60 only has CXSYNC_PORT, CXPACKET, CXCONSUMER with self-references. No LCK_M_* waiters depend on it. |
| Tempdb is not the cause of this blocking chain | TempDB Contention: latch waits exist, but live blockers/waiters in WideWorldImporters are explicit lock waits on StateProvinces; VersionStore=0.0MB and no spill-heavy query is identified. |
Scripts
Recommendation 2: Enable READ_COMMITTED_SNAPSHOT for WideWorldImporters
USE master;
GO
ALTER DATABASE [WideWorldImporters]
SET READ_COMMITTED_SNAPSHOT ON
WITH ROLLBACK IMMEDIATE;
GO
Recommendation 4: Create the missing nonclustered index on Application.Cities to reduce scan work and lock exposure
USE [WideWorldImporters];
GO
CREATE INDEX IX_Cities_StateProvinceID_CityName
ON [Application].[Cities] ([StateProvinceID])
INCLUDE ([CityName]);
GO