Quartznet: SqlServer AdoJobStore SqlParameter ohne Textgröße erzeugt Druck auf dem Server

Erstellt am 22. Aug. 2020  ·  15Kommentare  ·  Quelle: quartznet/quartznet

Die AdoJobStore-Implementierung wurde kürzlich für den Parameter sched_name in 3.1.0 korrigiert, belastet jedoch aufgrund der fehlenden SqlParameter-Größeneigenschaft für Textparameter immer noch den Sql Server-Plancache.

Dies generiert viele verschiedene SQL-Pläne. Hier einige generierte Pläne, dies ist die Version 3.0.0 ohne den Fix für den Parameter des Scheduler-Namens, aber das Problem bleibt immer noch bestehen:

(<strong i="7">@triggerRepeatCount</strong> int,<strong i="8">@triggerRepeatInterval</strong> bigint,<strong i="9">@triggerTimesTriggered</strong> int,<strong i="10">@triggerName</strong> nvarchar(12),<strong i="11">@triggerGroup</strong> nvarchar(17))UPDATE QRTZ_SIMPLE_TRIGGERS SET REPEAT_COUNT = <strong i="12">@triggerRepeatCount</strong>, REPEAT_INTERVAL = <strong i="13">@triggerRepeatInterval</strong>, TIMES_TRIGGERED = <strong i="14">@triggerTimesTriggered</strong> WHERE SCHED_NAME = 'scheduler' AND TRIGGER_NAME = <strong i="15">@triggerName</strong> AND TRIGGER_GROUP = @triggerGroup

(<strong i="18">@triggerRepeatCount</strong> int,<strong i="19">@triggerRepeatInterval</strong> bigint,<strong i="20">@triggerTimesTriggered</strong> int,<strong i="21">@triggerName</strong> nvarchar(10),<strong i="22">@triggerGroup</strong> nvarchar(10))UPDATE QRTZ_SIMPLE_TRIGGERS SET REPEAT_COUNT = <strong i="23">@triggerRepeatCount</strong>, REPEAT_INTERVAL = <strong i="24">@triggerRepeatInterval</strong>, TIMES_TRIGGERED = <strong i="25">@triggerTimesTriggered</strong> WHERE SCHED_NAME = 'scheduler' AND TRIGGER_NAME = <strong i="26">@triggerName</strong> AND TRIGGER_GROUP = @triggerGroup

Wie Sie ohne den Parameter size sehen können, wird die Größe aus dem Wert selbst entnommen, der ähnliche Pläne erzeugt.
So zeigen Sie den auf einem SqlServer generierten Plan-Cache an:

SELECT DISTINCT dm_exec_sql_text.text, creation_time FROM sys.dm_exec_cached_plans CROSS APPLY sys.dm_exec_query_plan(plan_handle) INNER JOIN sys.dm_exec_query_stats ON dm_exec_query_stats.plan_handle = dm_exec_cached_plans.plan_handle CROSS APPLY sys.dm_exec_sql_text(dm_exec_query_stats.plan_handle) WHERE text LIKE '%qrtz%' ORDER BY dm_exec_sql_text.text DESC;

Dieser Port führt zu einem Plan-Flooding, das kritische Auswirkungen auf den gesamten Server hat.

Um zu vermeiden, dass wir beim Start die Spaltengröße aus dem Schema erfassen und diesen Wert während der Parametrierung setzen können, denke ich zum Beispiel in SqlServerDelegate.AddCommandParameter.

Alle 15 Kommentare

Sehr interessanter Fund und vielen Dank für die detaillierte Aufschlüsselung. Wären Sie daran interessiert, eine PR einzureichen, um dies zu beheben?

Klar, lassen Sie es mich wissen, wenn Sie einige Hinweise oder Vorsichtsmaßnahmen haben. Ich fork es, um so schnell wie möglich eine PR mit dem Fix einzureichen.

Die Schnellprüfung lässt mich glauben, dass es genau so ist, wie Sie es gesagt haben, indem Sie Größeninformationen zu aufgerufenem AddCommandParameter hinzufügen. Ich denke, es ist am einfachsten, die Größen zu AdoConstants.cs hinzuzufügen, wo die Spaltennamen bereits sind. Neue wie public const int ColumnSchedulerNameSize = 120 .

AddCommandParameter(cmd, "schedulerName", schedName);

Wird:

AddCommandParameter(cmd, "schedulerName", schedName, size: AdoConstants.ColumnSchedulerNameSize);

Hoffentlich treten keine Probleme zwischen Datenbanken auf, die Größen sollten für jedes Schema und jeden Anbietercode gleich sein.

Ich stimme Ihnen zu, dies ist eine einfache Lösung und an derselben Stelle, an der der Code stärker an das Schema gebunden ist, ist dies der erste Ort, um im Falle zukünftiger Schemaänderungen zu überprüfen. Diese Änderung wird von anderen Engines unterstützt und die Größen sind bei den verschiedenen Engines gleich. Andere Optionen wie das Lesen von Schemametadaten zum Abrufen der Spaltengröße sind allein dafür zu komplex. Ich werde diesen Fix auch in einer realen Produktionsumgebung testen, in der jetzt eindeutig ein Problem besteht, und das Ergebnis überprüfen.

Eine Option könnte auch einen Standardwert wie Größe 4000 ableiten, wenn es sich um einen String handelt. Größe wäre fest und immer gleich? Obwohl größer als die tatsächliche Datenbankgröße, aber ich denke, das spielt keine Rolle?

Es gibt unterschiedliche Größen für jede Spalte, daher ist es besser, einen Standardwert wie diesen oder nvarchar(max) festzulegen.
Ich überprüfe, wie einige ORMs damit umgehen, weil ich mich erinnere, dass ich einen ähnlichen Ansatz beim Lesen des Codes hinter Entity Framework angewendet habe, aber wahrscheinlich werden Metadaten gelesen. Der einzige Zweifel besteht in der Speicherzuweisung für eine solche deklarierte Größe, aber das Testen des generierten Abfrageplans scheint sich für SQL Server überhaupt nicht zu ändern.

SET TRAN ISOLATION LEVEL READ UNCOMMITTED; IF OBJECT_ID('TEST_TABLE') IS NOT NULL DROP table [dbo].[TEST_TABLE]; CREATE TABLE [dbo].[TEST_TABLE] ( [SCHED_NAME] nvarchar(120) NOT NULL, [TRIGGER_NAME] nvarchar(150) NOT NULL, [TRIGGER_GROUP] nvarchar(150) NULL ); GO DECLARE <strong i="7">@p1</strong> NVARCHAR(4000) = 'scheduler-name'; DECLARE <strong i="8">@p2</strong> NVARCHAR(4000) = 'trigger-name'; DECLARE <strong i="9">@p3</strong> NVARCHAR(4000) = 'trigger-group'; DECLARE <strong i="10">@sql</strong> NVARCHAR(4000) = N'insert into [dbo].[TEST_TABLE] ([SCHED_NAME],[TRIGGER_NAME],[TRIGGER_GROUP]) values (<strong i="11">@p1</strong>,<strong i="12">@p2</strong>,@p3)'; DECLARE <strong i="13">@params</strong> NVARCHAR(4000) = N'<strong i="14">@p1</strong> nvarchar(4000),<strong i="15">@p2</strong> nvarchar(4000),<strong i="16">@p3</strong> nvarchar(4000)'; EXECUTE sys.sp_executesql <strong i="17">@sql</strong>, <strong i="18">@params</strong>, @p1=<strong i="19">@p1</strong>, @p2=<strong i="20">@p2</strong>, @p3=@p3; SELECT DISTINCT dm_exec_sql_text.text, creation_time FROM sys.dm_exec_cached_plans CROSS APPLY sys.dm_exec_query_plan(plan_handle) INNER JOIN sys.dm_exec_query_stats ON dm_exec_query_stats.plan_handle = dm_exec_cached_plans.plan_handle CROSS APPLY sys.dm_exec_sql_text(dm_exec_query_stats.plan_handle) WHERE text LIKE '%TEST_TABLE%' ORDER BY dm_exec_sql_text.text DESC;

Ich sehe da gibt's jetzt die 4000? Es könnte einfach sein, sich hier einzuklinken:

https://github.com/quartznet/quartznet/blob/effbc3ee162e429ea6f198cc67927cdf01747c55/src/Quartz/Impl/AdoJobStore/StdAdoDelegate.cs#L3357 -L3365

Wie SQL Server bereits tut, um Binärdateien zu erkennen:

https://github.com/quartznet/quartznet/blob/effbc3ee162e429ea6f198cc67927cdf01747c55/src/Quartz/Impl/AdoJobStore/SqlServerDelegate.cs#L62 -L82

Es würde also ausreichen, etwas wie if type == string && size is null => size = 4000; zu tun

Ich wurde daran erinnert, dass NHibernate 4000 für nvarchar und 8000 für varchar festlegt, wenn keines angegeben wurde.

Ich teste, ob für jeden Typ ein Standardwert in AdoUtil.AddCommandParameter vorhanden ist, da die Größe anscheinend nur für Typen mit variabler Größe wie varchar, vcarchar, varbinary und -1 der maximale Wert verwendet wird.

param.Größe = Größe ?? -1;

andernfalls hat param.DbType nach der param-Erstellung den realen Typ und wir können ihn nur für einen String-Typ korrigieren, bei dem die Größe nicht angegeben ist.

Wenn wir ansonsten strenger sein und es nur für den Moment im SqlServer-Teil beheben möchten, können wir den Teil in SqlServerDelegate für den Datentyp null, die Größe null erweitern und prüfen, ob er mit Integer und anderen gut funktioniert, oder den Objektwerttyp für einen String überprüfen type, um den Fix einzuschränken (da dataType niemals angegeben wird, es sei denn varbinary).

Ich bin mir nicht sicher über die Leistung beim Festlegen eines Standardwerts (in den echten Abfragen nie angegeben) mit einem maximalen Wert. Vielleicht ist dies ein Overkill für die Speicherauswertung. Ich kenne die Auswirkungen nicht, aber ich kann sie testen.

Ich denke

            if (size == null && paramValue is string)
            {
                size = 4000;
            }

wäre ganz sicher? Der Datentyp wird nur explizit für Binär gesetzt, wenn ich mich an Rechte erinnere. Dies könnte auch nur an SqlServerDelegate gehen. Ich bin mir nicht sicher, wie Abfragepläne mit anderen DBs funktionieren, leiden sie unter diesem Problem.

Dieser Fix in SqlServerDelegate ist perfekt, um ein erstes Feedback zu erhalten und das Problem in anderen DBs ein zweites Mal zu überdenken.
Ich werde überprüfen, wie es funktioniert Dapper\EF\NH und andere bewährte Verfahren sammeln, nur um ein weiteres externes Feedback zu diesem Fall zu erhalten.
Ich werde die Produktion für den Speicherverbrauch nach dem Fix im Auge behalten.

  • eine nützliche Link Referenz von Brent Ozar auf der SqlParameter Ausgabe über den Plan Cache Verschmutzung.

  • MSDN- Dokumentation zur Verwendung von SqlParameter.Size

Ich habe die Version NHibernate 5.1.2 getestet, sie hat einen festen Standardwert von 4000 für nvarchar beim Abfragen einer zugeordneten Spalte mit [StringLength(50)] und derselben Größe für die Datenbankspalte festgelegt
EXEC sp_executesql N'select this_.Id as id1_7_0_, ... from dbo.test this_ where this_.Description=<strong i="7">@p0</strong>',N'<strong i="8">@p0</strong> nvarchar(4000)',<strong i="9">@p0</strong> = N'test';
dasselbe beim Einfügen und Aktualisieren.
Ich kann Entity Framework nicht testen, aber ich erinnere mich deutlich in Version 6 an etwas Ähnliches.
Ich habe Dapper 2.0.35 getestet und einen festen Standardwert von 4000 festgelegt, wenn er nicht durch Parameter für eine Spalte mit einer Datenbankgröße von 50 Zeichen angegeben wird.
exec sp_executesql N'SELECT COUNT(*) FROM [dbo].[test] WHERE [Description] = <strong i="16">@Name</strong>',N'<strong i="17">@Name</strong> nvarchar(4000)',@Name=N'test'
Ich denke, wir können eine gute Lösung in Betracht ziehen, indem Sie die Größe 4000 als Standardwert festlegen.

Super Untersuchung, danke dafür. Aus Neugier, wie groß ist Ihr Einsatz? Trigger/Job/Knoten-Zählweise. Ich wette, es erfordert eine gewisse Last, um dies an die Oberfläche zu bringen.

Dutzende von Prozessen, 10 Scheduler, einige Hundert Jobs mit einzelnen einfachen Triggern, die geclustert auf einer einzigen dedizierten Datenbank für Quarz ausgeführt werden. Vermutlich ist es auf dem Server auch nach dem Fix auf den Plancache zu gesprächig.
Ich freue mich, wenn Sie in unserem Anwendungsfall tiefer in die Details gehen möchten, können Sie mir privat schreiben.
Morgen früh werde ich die PR mit dem Fix einreichen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen