Fix sp_BlitzLock parallel deadlock filtering regression#3887
Conversation
The qn column was changed from ROW_NUMBER() - 1 (0-based) to ROW_NUMBER() (1-based), but downstream code still expects 0-based values. This caused parallel deadlocks to silently drop the second query (d.qn < 2 only keeps 1 row instead of 2) and made the WHEN d.qn = 0 labeling branch dead code. Fixes issue identified by Copilot in PR #3885. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Restores the intended 0-based qn numbering in sp_BlitzLock so that parallel deadlock filtering and related downstream logic behave correctly after a prior regression.
Changes:
- Change
qnto be 0-based again by usingROW_NUMBER() ... - 1in both deadlock-process projections (regular + parallel).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| en = | ||
| DENSE_RANK() OVER (ORDER BY dp.event_date), | ||
| qn = | ||
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date), | ||
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, | ||
| dn = |
There was a problem hiding this comment.
qn is now 0-based (ROW_NUMBER() - 1), but downstream formatting later builds deadlock_group with CASE WHEN d.qn = 0 THEN '1' ELSE CONVERT(..., d.qn) END, which will label both qn=0 and qn=1 as "Query #1". Update the consumer to display d.qn + 1 (or otherwise adjust) so query numbering remains correct, especially for parallel deadlocks where only qn<2 rows are kept.
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, | ||
| dn = | ||
| ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), |
There was a problem hiding this comment.
The ROW_NUMBER() uses ORDER BY dp.event_date inside PARTITION BY dp.event_date, which is non-deterministic because all rows in the partition have the same ordering key. Since qn is used to filter parallel deadlocks to the first 2 rows, consider ordering by a stable discriminator (e.g., dp.id and/or dp.spid) to ensure consistent row selection.
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, | |
| dn = | |
| ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), | |
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.id, dp.spid) - 1, | |
| dn = | |
| ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.spid, dp.id), |
| DENSE_RANK() OVER (ORDER BY dp.event_date), | ||
| qn = | ||
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date), | ||
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, |
There was a problem hiding this comment.
Same as earlier: with qn now 0-based, the later deadlock_group label logic (WHEN d.qn = 0 THEN '1' ELSE CONVERT(..., d.qn)) will mislabel qn=1 as "Query #1". Adjust the label computation to use d.qn + 1 (or equivalent) so output query numbering stays correct.
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, | |
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date), |
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, | ||
| dn = | ||
| ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), |
There was a problem hiding this comment.
Same non-determinism here: ROW_NUMBER() orders by dp.event_date while partitioning by dp.event_date, so qn assignment can change between runs/plans. Because later logic filters qn < 2 for parallel deadlocks, order by a stable column (e.g., dp.id, dp.spid) to make the filter deterministic.
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, | |
| dn = | |
| ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.event_date), | |
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.id, dp.spid) - 1, | |
| dn = | |
| ROW_NUMBER() OVER (PARTITION BY dp.event_date, dp.id ORDER BY dp.spid), |
The CASE expression mapped qn=0 to "Query #1" but then used qn directly for all other values, so qn=1 also displayed as "Query #1". Replace the CASE with a simple qn + 1 conversion so labels are always correct (qn=0 → "Query #1", qn=1 → "Query #2", etc). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
ROW_NUMBER() - 1(0-based) for theqncolumn in sp_BlitzLock, which was accidentally changed to 1-basedROW_NUMBER()in PR 2026-04-06 Release #3885.- 1, downstream code breaks in two ways:d.qn < 2): only keeps 1 row instead of 2, silently dropping the second query involved in the deadlock.WHEN d.qn = 0): this branch becomes dead code sinceqnnever equals 0.Test plan
qncorrectly keeps 2 rows forqn < 2(parallel deadlock filter), while 1-based only keeps 1.WHEN d.qn = 0 THEN N'1'branch is reachable with 0-based numbering.🤖 Generated with Claude Code