Skip to content

The DMV blocking sentinel names the blocked session's database, not the lock resource's, so a cross-database lock still fingerprints twice #1893

Description

@erikdarlingdata

Found while implementing [#1876], and deliberately left out of that PR.

[#1876] made the two blocking collectors' contentious_object labels agree at the incident identity, and in the same change [#1876] corrected the blocked_process_report sentinel to name the LOCK RESOURCE's database (DB_NAME(resource_database_id)) rather than the event's, because for a cross-database lock those differ and the event's names where the blocked session was merely running.

The DMV side's synthesized sentinel does not have that information available where it is built.

Why

The normalizer runs in BlockingIncidentGrouper (C#), against a stored row. For an unresolvable lock, dmv_blocking_snapshots.contentious_object holds the raw wait resource — e.g. KEY: 6:72057594041991168 (8194443284a0) — whose leading 6 IS the resource database id, but C# cannot turn a database id into a name. The only database name on the row is database_name, which the collector writes as DB_NAME(der_b.database_id): the BLOCKED SESSION's database.

So today:

resource db 6, session db 1
blocked_process_report Unresolved: key lock, database: StackOverflow2013
dmv_blocking_snapshots (normalized) Unresolved: key lock, database: master

For the overwhelmingly common same-database lock the two agree and [#1876]'s fix holds. For a genuine cross-database lock they still fingerprint differently — a narrower version of the same defect, in the same place.

The fix, and why it is not a one-liner

DmvBlockingSnapshotCollector already shreds objparse.database_id out of the wait resource for its OBJECT: branch, so the resource database id is in hand server-side. It could emit the full sentinel there — N'Unresolved: ' + LOWER(lock_type) + N' lock, database: ' + ISNULL(DB_NAME(resource_db), N'unknown') — which would arrive already correct, and [#1876]'s normalizer would leave it alone (it is idempotent and a report-form label is a no-op).

What makes it a separate issue:

  • It only helps new rows. Every DMV row already in the store keeps the raw resource, so the C# approximation has to stay for them regardless — the two mechanisms then coexist and need a stated relationship, rather than one replacing the other.
  • It has a SQL twin. install/56_collect_dmv_blocking_snapshot.sql is the deprecated Dashboard's byte-identical copy and would have to move with it or the two stores diverge.
  • It reopens a scope question. The DMV collector deliberately resolves OBJECT: only, with the explicit comment "KEY/PAGE/RID stay raw" — it runs on a much tighter cadence than the blocked-process reader, and the report side's resolution is a per-database cursor. Naming the database is cheap; the tempting adjacent step of resolving the object itself is not, and the two should be decided together.
  • It changes the stored value, so it is another fingerprint transition for DMV-sourced incidents on top of the one [blocked_process_report and dmv_blocking_snapshots format contentious_object differently, so the merged blocking alerts fingerprint the same object twice #1876] already carries. Worth a release of its own rather than stacking a third churn into the same upgrade.

Not a regression: this is the residue of a fix, and the pre-[#1876] behavior for these rows was strictly worse (a per-lock-hash identity that never deduped at all).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions