Apparentemente, la mia funzione di assemblaggio CLR sta causando deadlock?


9

La nostra applicazione deve funzionare ugualmente bene con un database Oracle o un database Microsoft SQL Server. Per facilitare ciò, abbiamo creato una manciata di UDF per omogeneizzare la nostra sintassi delle query. Ad esempio, SQL Server ha GETDATE () e Oracle ha SYSDATE. Svolgono la stessa funzione ma sono parole diverse. Abbiamo scritto un wrapper UDF chiamato NOW () per entrambe le piattaforme che racchiude la sintassi specifica della piattaforma pertinente in un nome di funzione comune. Abbiamo altre funzioni del genere, alcune delle quali essenzialmente non fanno nulla ma esistono esclusivamente per motivi di omogeneizzazione. Sfortunatamente, questo ha un costo per SQL Server. Gli UDF scalari incorporati causano il caos delle prestazioni e disabilitano completamente il parallelismo. In alternativa, abbiamo scritto le funzioni di assemblaggio CLR per raggiungere gli stessi obiettivi. Quando lo abbiamo distribuito su un client, hanno iniziato a riscontrare frequenti deadlock. Questo particolare client utilizza tecniche di replica e disponibilità elevata e mi chiedo se ci sia una sorta di interazione in corso qui. Non capisco come l'introduzione di una funzione CLR possa causare problemi come questo. Per riferimento, ho incluso la definizione UDF scalare originale nonché la definizione CLR sostitutiva in C # e la relativa dichiarazione SQL. Ho anche un deadlock XML che posso fornire se questo aiuta.

UDF originale

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

Funzione di assemblaggio CLR

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

Dichiarazione di SQL Server per la funzione CLR

CREATE FUNCTION [fn].[APAD]
(
    @Value NVARCHAR(4000),
    @TableName NVARCHAR(4000),
    @ColumnName NVARCHAR(4000)
) RETURNS NVARCHAR(4000)
AS
EXTERNAL NAME ASI.fn.APAD
GO

9
Le funzioni CLR scalari deterministiche non dovrebbero contribuire ai deadlock. Naturalmente potrebbero funzionare le funzioni CLR che leggono il database. È necessario includere l'XML deadlock nella domanda.
David Browne - Microsoft,

Risposte:


7

Quali versioni di SQL Server stai usando?

Ricordo di aver visto un leggero cambiamento nel comportamento in SQL Server 2017 non molto tempo fa. Dovrò tornare indietro e vedere se riesco a trovare dove ne ho preso nota, ma penso che avesse a che fare con un blocco dello schema avviato quando si accedeva a un oggetto SQLCLR.

Mentre lo sto cercando, dirò quanto segue riguardo al tuo approccio:

  1. Utilizzare i Sql*tipi per i parametri di input, i tipi di restituzione. Dovresti usare SqlStringinvece di string. SqlStringè molto simile a una stringa nullable (la tua value?, ma ha altre funzionalità che sono specifiche di SQL Server. Tutti i Sql*tipi hanno una Valueproprietà che restituisce il tipo .NET previsto (ad es. SqlString.Valuerestituzioni string, SqlInt32restituzioni int, SqlDateTimerestituzioni DateTime, ecc.).
  2. Vorrei inizialmente sconsigliare l'intero approccio, indipendentemente dal fatto che i deadlock siano correlati. Lo dico perché:

    1. Anche con UDF deterministici SQLCLR in grado di partecipare a piani paralleli, è molto probabile che si ottengano successi prestazionali per l'emulazione di funzioni incorporate semplicistiche.
    2. L'API SQLCLR non consente VARCHAR. Stai bene convertendo implicitamente tutto in NVARCHARe poi di nuovo in VARCHARper operazioni semplici?
    3. L'API SQLCLR non consente il sovraccarico, pertanto potrebbero essere necessarie più versioni di funzioni che consentono firme diverse in T-SQL e / o PL / SQL.
    4. Simile a non consentire il sovraccarico, c'è una grande differenza tra NVARCHAR(4000)e NVARCHAR(MAX): il MAXtipo (che ne ha uno solo nella firma) fa in modo che la chiamata SQLCLR richieda il doppio del tempo che non ha alcun MAXtipo nella firma (credo che valga vero anche per VARBINARY(MAX)vs VARBINARY(4000)). Quindi, devi decidere tra:
      • usando solo NVARCHAR(MAX)per avere un'API semplificata, ma subisci il colpo di prestazione quando stai usando 8000 byte o meno di dati stringa, oppure
      • creando due varianti per tutte / la maggior parte / molte funzioni di stringa: una con i MAXtipi e una senza (per quando si ha la certezza di non superare mai 8000 byte di dati stringa dentro o fuori). Questo è l'approccio che ho scelto di adottare per la maggior parte delle funzioni nella mia libreria SQL # : esiste una Trim()funzione che probabilmente ha uno o più MAXtipi e una Trim4k()versione che non ha mai un MAXtipo in nessun punto dello schema della firma o del set di risultati. Le versioni "4k" sono assolutamente più efficienti.
    5. Non stai facendo attenzione a emulare la funzionalità data l'esempio nella domanda. LTRIMe RTRIMtaglia solo gli spazi, mentre .NET String.Trim()taglia gli spazi bianchi (almeno spazio, tabulazioni e newline). Per esempio:

        PRINT LTRIM(RTRIM(N'      a       '));
    6. Inoltre, ho appena notato che la tua funzione, sia in T-SQL che in C #, utilizza solo 1 dei 3 parametri di input. È solo una prova di concetto o un codice redatto?

1. Grazie per il suggerimento sull'uso dei tipi Sql. Farò quel cambiamento ora. 2. Ci sono forze esterne all'opera qui che ne richiedono l'uso. Non ne sono entusiasta, ma fidati di me, è meglio dell'alternativa. La mia domanda originale contiene un po 'di spiegazione sul perché esiste una funzione apparentemente asinina e viene utilizzata.
Russ Suter,

@RussSuter Capito in riferimento a: forze esterne. Stavo solo sottolineando alcune insidie ​​che potrebbero non essere note al momento della decisione. Ad ogni modo, non sono in grado di trovare le mie note o riprodurre lo scenario dai pochi dettagli che ricordo. Ricordo solo qualcosa di decisamente cambiato nel 2017 per quanto riguarda le transazioni e la chiamata del codice da un assembly, e di essermi davvero infastidito perché sembrava un cambiamento inutile in peggio, e ho dovuto aggirare il problema per quello che stavo testando che ha funzionato bene nelle versioni precedenti. Quindi, si prega di inviare un collegamento nella domanda all'XML deadlock.
Solomon Rutzky,

Grazie per le informazioni aggiuntive. Ecco un link all'XML: dropbox.com/s/n9w8nsdojqdypqm/deadlock17.xml?dl=0
Russ Suter

@RussSuter Hai provato questo inserendo il T-SQL? Guardando il deadlock XML (che non è facile in quanto è una singola riga - tutte le nuove righe sono state rimosse in qualche modo) sembra essere una serie di blocchi PAGE tra le sessioni 60 e 78. Ci sono 8 pagine bloccate tra le due sessioni: 3 per una SPID e 5 per l'altro. Ciascuno con un ID processo diverso, quindi si tratta di un problema di parallelismo. Se questo è correlato a SQLCLR, ironicamente potrebbe essere il fatto che SQLCLR non impedisca il parallelismo. Questo è il motivo per cui ti ho chiesto se hai provato a mettere in linea la semplice funzione in quanto ciò potrebbe anche mostrare il deadlock.
Solomon Rutzky,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.