Anscheinend verursacht meine CLR-Assembly-Funktion Deadlocks?

9

Unsere Anwendung muss mit einer Oracle-Datenbank oder einer Microsoft SQL Server-Datenbank gleich gut funktionieren. Um dies zu vereinfachen, haben wir eine Handvoll UDFs erstellt, um unsere Abfragesyntax zu homogenisieren. Beispielsweise hat SQL Server GETDATE () und Oracle SYSDATE. Sie erfüllen dieselbe Funktion, sind jedoch unterschiedliche Wörter. Wir haben für beide Plattformen eine Wrapper-UDF namens NOW () geschrieben, die die relevante plattformspezifische Syntax in einen gemeinsamen Funktionsnamen einschließt. Wir haben andere solche Funktionen, von denen einige im Wesentlichen nichts anderes tun, als nur zur Homogenisierung zu existieren. Leider hat dies Kosten für SQL Server. Inline-skalare UDFs beeinträchtigen die Leistung und deaktivieren die Parallelität vollständig. Als Alternative haben wir CLR-Assembly-Funktionen geschrieben, um dieselben Ziele zu erreichen. Als wir dies für einen Client bereitstellten, traten häufig Deadlocks auf. Dieser spezielle Client verwendet Replikations- und Hochverfügbarkeitstechniken und ich frage mich, ob hier irgendeine Art von Interaktion stattfindet. Ich verstehe nur nicht, wie die Einführung einer CLR-Funktion solche Probleme verursachen würde. Als Referenz habe ich die ursprüngliche skalare UDF-Definition sowie die Ersatz-CLR-Definition in C # und die SQL-Deklaration dafür aufgenommen. Ich habe auch Deadlock-XML, das ich bereitstellen kann, wenn das hilft.

Original UDF

CREATE FUNCTION [fn].[APAD]
(
    @Value VARCHAR(4000)
    , @tablename VARCHAR(4000) = NULL
    , @columnname VARCHAR(4000) = NULL
)

RETURNS VARCHAR(4000)
WITH SCHEMABINDING
AS

BEGIN
    RETURN LTRIM(RTRIM(@Value))
END
GO

CLR-Montagefunktion

[SqlFunction(IsDeterministic = true)]
public static string APAD(string value, string tableName, string columnName)
{
    return value?.Trim();
}

SQL Server-Deklaration für die CLR-Funktion

CREATE FUNCTION [fn].[APAD]
(
    @Value NVARCHAR(4000),
    @TableName NVARCHAR(4000),
    @ColumnName NVARCHAR(4000)
) RETURNS NVARCHAR(4000)
AS
EXTERNAL NAME ASI.fn.APAD
GO
Russ Suter
quelle
9
Deterministische skalare CLR-Funktionen sollten nicht zu Deadlocks beitragen. Natürlich können CLR-Funktionen, die die Datenbank lesen. Sie sollten das Deadlock-XML in Ihre Frage aufnehmen.
David Browne - Microsoft

Antworten:

7

Welche Version (en) von SQL Server verwenden Sie?

Ich erinnere mich an eine leichte Änderung des Verhaltens in SQL Server 2017 vor nicht allzu langer Zeit. Ich muss zurückgehen und nachsehen, ob ich herausfinden kann, wo ich es notiert habe, aber ich denke, es hat damit zu tun, dass beim Zugriff auf ein SQLCLR-Objekt eine Schemasperre initiiert wurde.

Während ich danach suche, werde ich Folgendes zu Ihrem Ansatz sagen:

  1. Bitte verwenden Sie die Sql*Typen für Eingabeparameter, Rückgabetypen. Sie sollten SqlStringanstelle von verwenden string. SqlStringist einer nullbaren Zeichenfolge sehr ähnlich (Ihre value?, aber es sind andere Funktionen integriert, die SQL Server-spezifisch sind. Alle Sql*Typen haben eine ValueEigenschaft, die den erwarteten .NET-Typ SqlString.Valuezurückgibt (z. B. Rückgaben string, SqlInt32Rückgaben int, SqlDateTimeRückgaben DateTimeusw.).
  2. Ich würde zunächst gegen diesen gesamten Ansatz empfehlen, ob die Deadlocks zusammenhängen oder nicht. Ich sage das, weil:

    1. Selbst wenn deterministische SQLCLR-UDFs an parallelen Plänen teilnehmen können, werden Sie höchstwahrscheinlich Leistungseinbußen bei der Emulation vereinfachter integrierter Funktionen erhalten.
    2. Die SQLCLR-API lässt dies nicht zu VARCHAR. Können Sie implizit alles für einfache Operationen in NVARCHARund dann wieder zurück konvertieren VARCHAR?
    3. Die SQLCLR-API lässt keine Überladung zu. Daher benötigen Sie möglicherweise mehrere Versionen von Funktionen, die unterschiedliche Signaturen in T-SQL und / oder PL / SQL zulassen.
    4. Ähnlich wie beim Überladen nicht zuzulassen, gibt es einen großen Unterschied zwischen NVARCHAR(4000)und NVARCHAR(MAX): Der MAXTyp (der nur einen einzigen in der Signatur enthält) lässt den SQLCLR-Aufruf doppelt so lange dauern wie keinen MAXTyp in der Signatur (ich glaube, dies gilt gilt auch für VARBINARY(MAX)vs VARBINARY(4000)). Sie müssen sich also entscheiden zwischen:
      • Verwenden Sie nur NVARCHAR(MAX), um eine vereinfachte API zu haben, aber nehmen Sie den Leistungseinbruch, wenn Sie 8000 Byte oder weniger Zeichenfolgendaten verwenden, oder
      • Erstellen von zwei Variationen für alle / die meisten / viele Zeichenfolgenfunktionen: eine mit MAXTyp und eine ohne (wenn Sie garantiert nie mehr als 8000 Byte Zeichenfolgendaten ein- oder ausgeben). Dies ist der Ansatz, den ich für die meisten Funktionen in meiner SQL # -Bibliothek gewählt habe: Es gibt eine Trim()Funktion, die wahrscheinlich einen oder mehrere MAXTypen hat, und eine Trim4k()Version, die niemals einen MAXTyp im Signatur- oder Ergebnismengenschema hat. Die "4k" -Versionen sind absolut effizienter.
    5. Sie achten nicht darauf, die Funktionalität anhand des Beispiels in der Frage zu emulieren. LTRIMund RTRIMnur Leerzeichen String.Trim()zuschneiden , während .NET Leerzeichen trimmt (mindestens Leerzeichen, Tabulatoren und Zeilenumbrüche). Zum Beispiel:

        PRINT LTRIM(RTRIM(N'      a       '));
    6. Außerdem ist mir gerade aufgefallen, dass Ihre Funktion sowohl in T-SQL als auch in C # nur einen der drei Eingabeparameter verwendet. Ist dies nur ein Proof of Concept oder ein redigierter Code?
Solomon Rutzky
quelle
1. Vielen Dank für den Tipp zur Verwendung der SQL-Typen. Ich werde das jetzt ändern. 2. Hier wirken äußere Kräfte, die deren Einsatz erforderlich machen. Ich bin nicht begeistert, aber vertrau mir, es ist besser als die Alternative. Meine ursprüngliche Frage enthält ein wenig Erklärung, warum eine scheinbar blöde Funktion existiert und verwendet wird.
Russ Suter
@RussSuter Verstanden bezüglich: externen Kräften. Ich habe nur auf einige Fallstricke hingewiesen, die bei dieser Entscheidung möglicherweise nicht bekannt waren. In beiden Fällen kann ich meine Notizen nicht finden oder das Szenario anhand der wenigen Details, an die ich mich erinnere, reproduzieren. Ich erinnere mich nur an etwas, das sich 2017 in Bezug auf Transaktionen und das Aufrufen von Code aus einer Assembly definitiv geändert hat, und ich war wirklich verärgert darüber, da es wie eine unnötige Änderung zum Schlechten schien, und ich musste es für das, was ich testete, umgehen, das funktionierte gut in früheren Versionen. Bitte posten Sie in der Frage einen Link zum Deadlock-XML.
Solomon Rutzky
Danke für die zusätzlichen Infos. Hier ist ein Link zum XML: dropbox.com/s/n9w8nsdojqdypqm/deadlock17.xml?dl=0
Russ Suter
@RussSuter Haben Sie dies mit Inlining des T-SQL versucht? Betrachtet man das Deadlock-XML (was nicht einfach ist, da es sich um eine einzelne Zeile handelt - alle Zeilenumbrüche wurden irgendwie entfernt), scheint es sich um eine Reihe von PAGE-Sperren zwischen den Sitzungen 60 und 78 zu handeln. Zwischen beiden Sitzungen sind 8 Seiten gesperrt: 3 für eine SPID und 5 für den anderen. Jeder mit einer anderen Prozess-ID, daher handelt es sich um ein Problem der Parallelität. Wenn dies mit SQLCLR zusammenhängt, könnte es ironischerweise die Tatsache sein, dass SQLCLR Parallelität nicht verhindert. Aus diesem Grund habe ich gefragt, ob Sie versucht haben, die einfache Funktion inline zu setzen, da dies möglicherweise auch den Deadlock anzeigt.
Solomon Rutzky