Locking and Blocking Analysis – RockyPC / WideWorldImporters – 2026-05-31 16:53:29 UTC

Tuning Goal: Locking and Blocking Analysis
Server: RockyPC
Version: Microsoft SQL Server 2022 (RTM-GDR) 16.0.1180.1, Developer Edition (64-bit), Engine Edition: Enterprise
Primary Database in Blocking Case: WideWorldImporters

Executive Summary

Blocking Active
Yes
Lead Blocker
Session 62
Blocked Sessions
2
Longest Lock Wait
17,299 ms

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, and CXCONSUMER, 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_S for 17,299 ms.
  • Confirmed from Active Blocking Chains: session 66 then blocks session 63 on LCK_M_U for 13,008 ms.
  • Confirmed from Lock Inventory: session 62 holds KEY ... StateProvinces ... Mode=X Status=GRANT, while session 66 waits for Mode=S and session 63 waits for Mode=U on the same index PK_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.Countries in the same transaction, then tried to update StateProvinces. 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, and CXCONSUMER.
  • 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.
Estimated business impact: interactive requests touching 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

  1. 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 across WAITFOR, client think-time, or unrelated reads/updates.
    • Rationale: Lock Inventory shows session 62 holds X on PK_Application_StateProvinces; Long-Running Transactions shows 19s duration; SQL text includes BEGIN TRAN followed by WAITFOR 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%
  2. Enable READ_COMMITTED_SNAPSHOT for WideWorldImporters

    • 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_S by 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 OFF during 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
  3. Standardize update order for Countries and StateProvinces

    • Fix text: Ensure every code path touching both tables acquires locks in the same order, preferably one consistent sequence such as Countries then StateProvinces, or the reverse, but never both.
    • Rationale: Long-Running Transactions: session 63 batch updates Countries then StateProvinces. Lock Inventory: session 61/62 sample shows another path updating StateProvinces first. Deadlock History: recent deadlock in WideWorldImporters on 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%
  4. 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 on Application.Cities. Index Usage: Cities.PK has 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.Cities table for a broader design review before promoting to production.
  5. Keep reads outside explicit transactions unless the read must be transactionally coupled to a later write

    • Fix text: Remove BEGIN TRAN around 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%
  6. Do not treat session 60 as the lead blocking target for this incident

    • Fix text: Separate parallelism tuning for sp01108 in SQLStorm from the lock-blocking remediation in WideWorldImporters.
    • 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

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 UTC in WideWorldImporters, victim spid=61, transactionname="user_transaction", waiting on a KEY resource 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 WAITFOR or 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