Status: Reviewed (deep-dive v1)
Stack note: Write procs (usp_Alert_Ack, collector merges) and busy Blazor boards share the warehouse — wrong isolation or fat transactions → blocking, deadlocks, and UI timeouts. Pair with SQL-15 (TRY/CATCH + XACT_STATE).
Pairs with: SQL-12 (write procs), SQL-15 (error/tran cleanup), SQL-19 (MERGE upserts — next on shortlist), SPIKE-07 (Ack/Evaluate).
Goal: RC vs RCSI/snapshot, short transactions, deadlock hygiene for Ack/merge, what Blazor sees when blocked.
| Situation | Lean toward |
|---|---|
| Default OLTP warehouse | READ COMMITTED; consider RCSI (DB option) for read-heavy boards |
| Ack / small write | Short explicit tran; XACT_ABORT ON (SQL-15) |
| Board read during merges | RCSI reduces reader/writer blocking; still measure tempdb |
| Long report inside UI request | Don’t — snapshot to #temp or Agent job; short read |
| “I need a consistent estate snapshot” | One short read tran or AS OF/staging — not HOLDLOCK forever |
| Level | Readers see | Main risk for this estate |
|---|---|---|
| READ COMMITTED (default) | Committed only; can block on writers | Writers block readers (and vice versa locks) |
RCSI (DB: READ_COMMITTED_SNAPSHOT ON) |
Versioned committed; readers don’t take shared locks the same way | tempdb version store growth |
| SNAPSHOT (session) | Statement/tran consistent view | Same version store; don’t leave long snapshot trans open |
| REPEATABLE / SERIALIZABLE | Stronger | Extra blocking — rare for boards; avoid by default |
| READ UNCOMMITTED / NOLOCK | Dirty | Wrong RAG/alert counts — not a perf strategy (SQL-16) |
Team default: keep DB on RC or turn on RCSI after a tempdb size check (SQL-06). Prefer RCSI over sprinkling NOLOCK in usp_*.
-- check
SELECT name, is_read_committed_snapshot_on, snapshot_isolation_state_desc
FROM sys.databases WHERE name = DB_NAME();
-- enable RCSI (maintenance window; watch tempdb)
ALTER DATABASE Current SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -- explicit; don’t surprise
BEGIN TRY
BEGIN TRAN;
-- validate + single-row/set update + event insert
COMMIT TRAN;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 ROLLBACK TRAN;
THROW;
END CATCH
Rules:
Typical shape: Session A updates Alert then inserts AlertEvent; Session B (or Evaluate) locks in another order / overlapping ranges.
Mitigations:
SqlException 1205 (optional, documented) system_health / Extended Events deadlock graph — fix the pair, don’t only retry forever -- victim message includes deadlock XML in XE; also:
DBCC TRACEON (1222, -1); -- ops judgment; prefer XE in prod
| Symptom | Likely cause |
|---|---|
| Timeout on GetOpenBoard | Blocked on shared/exclusive locks; or slow scan |
| Timeout on Ack | Blocked behind Evaluate/merge or another Ack |
| Intermittent 1205 | Deadlock victim |
| Works in SSMS, fails in app | Longer commands, different timeout, connection pooling + leftover tran (bug) |
App habits:
Without RCSI: usp_Alert_GetOpenBoard can block behind Ack/Evaluate writers (and hold locks that slow writers).
With RCSI: readers use row versions → fewer block chains; writers still conflict with writers.
-- optional: statement snapshot for one heavy read (if SI enabled)
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
SELECT …; -- keep short
Don’t hold snapshot tran across user think-time.
| Hint | When |
|---|---|
UPDLOCK, ROWLOCK |
Claim row to update without immediate deadlock dance |
HOLDLOCK / Serial |
Almost never on boards |
NOLOCK |
Avoid for alert/RAG truth |
usp_* BEGIN TRAN puzzles — keep depth 1 in app procs SQL Dude — SQL-18 Transactions & Isolation v1