Deadlocks: read system_health files without a temp table
The 2012+ query used SELECT ... INTO #system_health, a write to tempdb. One SELECT with ROW_NUMBER() now returns the deadlock rows and the newest row (the read position) in the same pass. SQL Scripts and the read-only test no longer allow any exception. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
631edc2adc
commit
66aa47ecb3
5 changed files with 43 additions and 56 deletions
|
|
@ -61,10 +61,7 @@ Bemærk:
|
|||
|
||||
- **Read-only.** Alle scripts læser kun: SELECT mod katalog-views og DMV'er, `DBCC TRACESTATUS`, `DBCC SQLPERF(WAITSTATS)`
|
||||
(kun SQL Server 2000), `xp_readerrorlog`, `xp_msver`, `sys.fn_trace_gettable`, lokale variabler og `USE`.
|
||||
**Én undtagelse:** `SQLI-DLK-001` / `_collectors/DeadlockCollector.sql` (SQL Server 2012+) opretter en midlertidig
|
||||
tabel `#system_health` i tempdb og sletter den igen i samme batch. Den er privat for sessionen, og ingen brugerdata,
|
||||
indstillinger eller permanente objekter ændres, men det er strengt taget en skrivning i tempdb. Det står også i
|
||||
filernes header.
|
||||
Ingen undtagelser: heller ikke midlertidige tabeller i tempdb.
|
||||
- **Per-database-forespørgsler** (indexes, statistikker, Query Store, database-scoped community-regler) kører agenten i
|
||||
hver online brugerdatabase. Her er de vist for `msdb`, som findes på alle instanser, så scriptet kan køres uændret.
|
||||
Ret `[msdb]` / `N'msdb'` til den database, du vil se.
|
||||
|
|
@ -87,7 +84,7 @@ Bemærk:
|
|||
- `Every_collector_query_is_registered_and_in_a_collector_script`: hver SQL-konstant og hver `Build…Sql`-metode i
|
||||
collectorerne er registreret, og hver registreret forespørgsel står i et `_collectors/`-script.
|
||||
- `Community_rule_scripts_contain_the_rule_query_verbatim`: community-reglens `query` fra JSON står ordret i filen.
|
||||
- `Scripts_are_read_only_apart_from_the_documented_temp_table`: ingen skrivende sætninger ud over undtagelsen ovenfor.
|
||||
- `Scripts_are_read_only`: ingen skrivende sætninger, heller ikke `SELECT … INTO #temp`.
|
||||
|
||||
En ny regel skal derfor have sin `SQL Scripts/<id>.sql` (se `CONTRIBUTING.md`). Ændres en collectors SQL, peger
|
||||
testen på de filer, der skal opdateres.
|
||||
|
|
|
|||
|
|
@ -31,11 +31,8 @@
|
|||
-- Delt script: SQL Scripts/_collectors/DeadlockCollector.sql
|
||||
-- Regelkode: src/SQLInsight.Rules/Concurrency/ConcurrencyRules.cs : DeadlockRule.Evaluate
|
||||
--
|
||||
-- Read-only: IKKE STRENGT READ-ONLY. DeadlockCollector.BuildFileSql (SQL Server 2012+) opretter en midlertidig
|
||||
-- tabel i tempdb (SELECT ... INTO #system_health) og sletter den igen (DROP TABLE #system_health) i
|
||||
-- samme batch. Den er privat for sessionen; ingen brugerdata, indstillinger eller permanente objekter
|
||||
-- ændres, men det er en skrivning i tempdb. Ring buffer-varianten (2008/2008 R2) er read-only. Øvrigt:
|
||||
-- kun SELECT samt SET-sessionsindstillinger.
|
||||
-- Read-only: Ja: kun SELECT (sys.fn_xe_file_target_read_file og DMV'er) samt SET-sessionsindstillinger. Ingen
|
||||
-- temp-tabeller; læsepositionen findes med ROW_NUMBER() i samme forespørgsel.
|
||||
-- Kørsel: Kør i SSMS som agentens login (eller et login med samme rettigheder). Hver variant er sin egen batch
|
||||
-- (GO); en variant for en anden SQL Server-version end din kan fejle - det er forventet.
|
||||
-- Per-database-forespørgsler er vist for databasen msdb, som findes overalt: ret navnet til den
|
||||
|
|
@ -59,14 +56,14 @@ GO
|
|||
-- Variant: SQL Server 2012 og nyere (system_health-filer; første læsning)
|
||||
-- Kilde: DeadlockCollector.BuildFileSql
|
||||
-- Kode: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs : DeadlockCollector.BuildFileSql(lastFile: null, lastOffset: null)
|
||||
SELECT object_name, file_name, file_offset,
|
||||
CASE WHEN object_name = N'xml_deadlock_report' THEN CAST(event_data AS nvarchar(max)) END AS event_data
|
||||
INTO #system_health
|
||||
FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL);
|
||||
|
||||
SELECT event_data FROM #system_health WHERE event_data IS NOT NULL;
|
||||
SELECT TOP (1) file_name, file_offset FROM #system_health ORDER BY file_name DESC, file_offset DESC;
|
||||
DROP TABLE #system_health;
|
||||
SELECT e.event_data, e.file_name, e.file_offset, e.rn
|
||||
FROM (
|
||||
SELECT CASE WHEN object_name = N'xml_deadlock_report' THEN CAST(event_data AS nvarchar(max)) END AS event_data,
|
||||
file_name, file_offset,
|
||||
ROW_NUMBER() OVER (ORDER BY file_name DESC, file_offset DESC) AS rn
|
||||
FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL)
|
||||
) AS e
|
||||
WHERE e.event_data IS NOT NULL OR e.rn = 1;
|
||||
GO
|
||||
|
||||
-- Variant: SQL Server 2008 – 2008 R2, og når event-filerne ikke kan læses
|
||||
|
|
|
|||
|
|
@ -12,11 +12,8 @@
|
|||
-- GRANT VIEW SERVER SECURITY STATE TO [agent]; -- SQL Server 2022+
|
||||
--
|
||||
-- Bruges af: SQLI-DLK-001
|
||||
-- Read-only: IKKE STRENGT READ-ONLY. DeadlockCollector.BuildFileSql (SQL Server 2012+) opretter en midlertidig
|
||||
-- tabel i tempdb (SELECT ... INTO #system_health) og sletter den igen (DROP TABLE #system_health) i
|
||||
-- samme batch. Den er privat for sessionen; ingen brugerdata, indstillinger eller permanente objekter
|
||||
-- ændres, men det er en skrivning i tempdb. Ring buffer-varianten (2008/2008 R2) er read-only. Øvrigt:
|
||||
-- kun SELECT samt SET-sessionsindstillinger.
|
||||
-- Read-only: Ja: kun SELECT (sys.fn_xe_file_target_read_file og DMV'er) samt SET-sessionsindstillinger. Ingen
|
||||
-- temp-tabeller; læsepositionen findes med ROW_NUMBER() i samme forespørgsel.
|
||||
-- Kørsel: Kør i SSMS som agentens login. Hver variant er sin egen batch (GO); en variant for en anden SQL Server-version
|
||||
-- end din kan fejle - det er forventet. Agenten kører altid med sessionsindstillingerne i _collectors/InstanceProbe.sql.
|
||||
-- ===================================================================================================================
|
||||
|
|
@ -24,14 +21,14 @@
|
|||
-- Variant: SQL Server 2012 og nyere (system_health-filer; første læsning)
|
||||
-- Kilde: DeadlockCollector.BuildFileSql
|
||||
-- Kode: src/SQLInsight.Collectors/Concurrency/DeadlockCollector.cs : DeadlockCollector.BuildFileSql(lastFile: null, lastOffset: null)
|
||||
SELECT object_name, file_name, file_offset,
|
||||
CASE WHEN object_name = N'xml_deadlock_report' THEN CAST(event_data AS nvarchar(max)) END AS event_data
|
||||
INTO #system_health
|
||||
FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL);
|
||||
|
||||
SELECT event_data FROM #system_health WHERE event_data IS NOT NULL;
|
||||
SELECT TOP (1) file_name, file_offset FROM #system_health ORDER BY file_name DESC, file_offset DESC;
|
||||
DROP TABLE #system_health;
|
||||
SELECT e.event_data, e.file_name, e.file_offset, e.rn
|
||||
FROM (
|
||||
SELECT CASE WHEN object_name = N'xml_deadlock_report' THEN CAST(event_data AS nvarchar(max)) END AS event_data,
|
||||
file_name, file_offset,
|
||||
ROW_NUMBER() OVER (ORDER BY file_name DESC, file_offset DESC) AS rn
|
||||
FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL)
|
||||
) AS e
|
||||
WHERE e.event_data IS NOT NULL OR e.rn = 1;
|
||||
GO
|
||||
|
||||
-- Variant: SQL Server 2008 – 2008 R2, og når event-filerne ikke kan læses
|
||||
|
|
|
|||
|
|
@ -51,16 +51,17 @@ public sealed class DeadlockCollector : Collector<DeadlockSnapshot>
|
|||
? $"{Db.Literal(lastFile)}, {lastOffset.Value.ToString(CultureInfo.InvariantCulture)}"
|
||||
: "NULL, NULL";
|
||||
|
||||
// Only the deadlock rows carry their XML; the rest are kept just to know where reading stopped.
|
||||
// One read of the files and no temp table: the deadlock rows carry their XML, and the newest row
|
||||
// (rn = 1) is returned as well, only to know where reading stopped.
|
||||
return $"""
|
||||
SELECT object_name, file_name, file_offset,
|
||||
CASE WHEN object_name = N'xml_deadlock_report' THEN CAST(event_data AS nvarchar(max)) END AS event_data
|
||||
INTO #system_health
|
||||
FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, {start});
|
||||
|
||||
SELECT event_data FROM #system_health WHERE event_data IS NOT NULL;
|
||||
SELECT TOP (1) file_name, file_offset FROM #system_health ORDER BY file_name DESC, file_offset DESC;
|
||||
DROP TABLE #system_health;
|
||||
SELECT e.event_data, e.file_name, e.file_offset, e.rn
|
||||
FROM (
|
||||
SELECT CASE WHEN object_name = N'xml_deadlock_report' THEN CAST(event_data AS nvarchar(max)) END AS event_data,
|
||||
file_name, file_offset,
|
||||
ROW_NUMBER() OVER (ORDER BY file_name DESC, file_offset DESC) AS rn
|
||||
FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, {start})
|
||||
) AS e
|
||||
WHERE e.event_data IS NOT NULL OR e.rn = 1;
|
||||
""";
|
||||
}
|
||||
|
||||
|
|
@ -84,14 +85,17 @@ public sealed class DeadlockCollector : Collector<DeadlockSnapshot>
|
|||
await using (var reader = await command.ExecuteReaderAsync(ct).ConfigureAwait(false))
|
||||
{
|
||||
while (await reader.ReadAsync(ct).ConfigureAwait(false))
|
||||
{
|
||||
if (!reader.IsDBNull(0))
|
||||
{
|
||||
events.Add(reader.GetString(0));
|
||||
}
|
||||
|
||||
if (await reader.NextResultAsync(ct).ConfigureAwait(false) && await reader.ReadAsync(ct).ConfigureAwait(false))
|
||||
if (Convert.ToInt64(reader.GetValue(3), CultureInfo.InvariantCulture) == 1)
|
||||
{
|
||||
file = reader.GetString(0);
|
||||
offset = Convert.ToInt64(reader.GetValue(1), CultureInfo.InvariantCulture);
|
||||
file = reader.GetString(1);
|
||||
offset = Convert.ToInt64(reader.GetValue(2), CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ public sealed partial class SqlScriptTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void Scripts_are_read_only_apart_from_the_documented_temp_table()
|
||||
public void Scripts_are_read_only()
|
||||
{
|
||||
var problems = new List<string>();
|
||||
foreach (var file in AllScripts())
|
||||
|
|
@ -142,12 +142,6 @@ public sealed partial class SqlScriptTests
|
|||
var text = Read(file);
|
||||
var code = CommentsAndStrings().Replace(text, " ");
|
||||
code = AllowedStatements().Replace(code, " ");
|
||||
if (text.Contains("-- Kilde: DeadlockCollector.BuildFileSql", StringComparison.Ordinal))
|
||||
{
|
||||
// Documented exception: a session-private temp table in tempdb (see the script's Read-only line).
|
||||
code = DeadlockTempTable().Replace(code, " ");
|
||||
}
|
||||
|
||||
problems.AddRange(Writes().Matches(code).Select(m => $"{Path.GetRelativePath(Folder, file)}: {m.Value.ToUpperInvariant()}").Distinct());
|
||||
}
|
||||
|
||||
|
|
@ -156,6 +150,7 @@ public sealed partial class SqlScriptTests
|
|||
|
||||
[Theory]
|
||||
[InlineData("SELECT name INTO dbo.x FROM sys.objects", true)]
|
||||
[InlineData("SELECT name INTO #x FROM sys.objects", true)]
|
||||
[InlineData("UPDATE dbo.t SET a = 1", true)]
|
||||
[InlineData("EXEC sys.sp_configure N'x', 1", true)]
|
||||
[InlineData("SELECT is_auto_update_stats_on, create_date FROM sys.databases -- DROP TABLE x", false)]
|
||||
|
|
@ -213,9 +208,6 @@ public sealed partial class SqlScriptTests
|
|||
[GeneratedRegex(@"\bEXEC\s+master\.dbo\.xp_(readerrorlog|msver)\b|\bDBCC\s+TRACESTATUS\b|\bDBCC\s+SQLPERF\s*\(\s*WAITSTATS\s*\)", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex AllowedStatements();
|
||||
|
||||
[GeneratedRegex(@"\bINTO\s+#system_health\b|\bDROP\s+TABLE\s+#system_health\b", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex DeadlockTempTable();
|
||||
|
||||
[GeneratedRegex(@"\b(INSERT|UPDATE|DELETE|MERGE|DROP|ALTER|CREATE|TRUNCATE|GRANT|REVOKE|DENY|BACKUP|RESTORE|RECONFIGURE|KILL|SHUTDOWN|BULK|OPENROWSET|OPENQUERY|OPENDATASOURCE|WAITFOR|INTO|EXEC|EXECUTE|DBCC|SETUSER)\b", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex Writes();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue