Najwyraźniej moja funkcja montażu CLR powoduje zakleszczenia?

9

Nasza aplikacja musi równie dobrze współpracować z bazą danych Oracle lub Microsoft SQL Server. Aby to ułatwić, stworzyliśmy garść UDF w celu ujednolicenia naszej składni zapytań. Na przykład SQL Server ma GETDATE (), a Oracle ma SYSDATE. Pełnią tę samą funkcję, ale są różnymi słowami. Napisaliśmy opakowanie UDF o nazwie NOW () dla obu platform, które otacza odpowiednią składnię specyficzną dla platformy wspólną nazwą funkcji. Mamy inne takie funkcje, z których niektóre w zasadzie nic nie istnieją, ale istnieją wyłącznie w celu homogenizacji. Niestety ma to koszt dla SQL Server. Inline skalarne UDF sieją spustoszenie w wydajności i całkowicie wyłączają równoległość. Alternatywnie napisaliśmy funkcje asemblera CLR, aby osiągnąć te same cele. Kiedy wdrożyliśmy to na kliencie, zaczęli doświadczać częstych impasów. Ten konkretny klient korzysta z technik replikacji i wysokiej dostępności i zastanawiam się, czy nie zachodzi tutaj jakaś interakcja. Po prostu nie rozumiem, w jaki sposób wprowadzenie funkcji CLR spowodowałoby takie problemy. Dla odniesienia załączyłem oryginalną skalarną definicję UDF, a także zastępczą definicję CLR w C # i deklarację SQL dla niej. Mam także zakleszczenie XML, które mogę podać, jeśli to pomoże.

Oryginalny 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

Funkcja montażu CLR

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

Deklaracja programu SQL Server dla funkcji CLR

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
źródło
9
Deterministyczne skalarne funkcje CLR nie powinny przyczyniać się do impasu. Oczywiście funkcje CLR odczytujące bazę danych. W pytaniu należy podać kod XML zakleszczenia.
David Browne - Microsoft

Odpowiedzi:

7

Z jakich wersji programu SQL Server korzystasz?

Pamiętam niewielką zmianę zachowania w SQL Server 2017 nie tak dawno temu. Będę musiał wrócić i sprawdzić, czy mogę znaleźć miejsce, w którym to zanotowałem, ale myślę, że miało to związek z zainicjowaniem blokady schematu podczas uzyskiwania dostępu do obiektu SQLCLR.

Podczas gdy tego szukam, powiem o twoim podejściu:

  1. Proszę użyć Sql*typów dla parametrów wejściowych, typów zwrotów. Powinieneś używać SqlStringzamiast string. SqlStringjest bardzo podobny do łańcucha zerowego (twój value?, ale ma wbudowaną inną funkcjonalność, która jest specyficzna dla SQL Server. Wszystkie Sql*typy mają Valuewłaściwość, która zwraca oczekiwany typ .NET (np. SqlString.Valuezwraca string, SqlInt32zwraca int, SqlDateTimezwraca DateTimeitp.).
  2. Odradzałbym na początku całe to podejście, niezależnie od tego, czy impasy są powiązane. Mówię to, ponieważ:

    1. Nawet przy deterministycznych SQLCLR UDF mogących uczestniczyć w planach równoległych, najprawdopodobniej dostaniesz uderzenia wydajności dla emulacji uproszczonych wbudowanych funkcji.
    2. Interfejs API SQLCLR nie zezwala na VARCHAR. Czy zgadzasz się z niejawną konwersją wszystkiego na, NVARCHARa następnie z powrotem VARCHARna proste operacje?
    3. Interfejs API SQLCLR nie pozwala na przeładowanie, więc może być konieczne wiele wersji funkcji, które pozwalają na różne podpisy w T-SQL i / lub PL / SQL.
    4. Podobny do nie dopuszczając do przeładowania, istnieje duża różnica między NVARCHAR(4000)i NVARCHAR(MAX): the MAXtype (mając nawet jednego z nich w podpisie) dokonanie podjąć rozmowę SQLCLR dwa razy tak długo, jak nie mając żadnych MAXtyp w podpisie (wierzę to trzyma prawdziwe również dla VARBINARY(MAX)vs VARBINARY(4000)). Musisz więc zdecydować między:
      • używając tylko NVARCHAR(MAX)uproszczonego interfejsu API, ale skorzystaj z wydajności, jeśli używasz 8000 bajtów lub mniej danych ciągowych, lub
      • tworząc dwie odmiany dla wszystkich / większości / wielu funkcji łańcuchowych: jedną z MAXtypami, a drugą bez (na wypadek, gdy nigdy nie przekroczysz 8000 bajtów danych wejściowych lub wyjściowych). Takie podejście wybrałem dla większości funkcji w mojej bibliotece SQL # : istnieje Trim()funkcja, która prawdopodobnie ma jeden lub więcej MAXtypów oraz Trim4k()wersję, która nigdy nie ma żadnego MAXtypu w schemacie podpisu lub zestawu wyników. Wersje „4k” są absolutnie bardziej wydajne.
    5. Biorąc pod uwagę przykład w pytaniu, nie jesteś ostrożny, aby emulować funkcjonalność. LTRIMi RTRIMtylko przycinaj spacje, a .NET String.Trim()przycina białe spacje (przynajmniej spację, tabulatory i znaki nowej linii). Na przykład:

        PRINT LTRIM(RTRIM(N'      a       '));
    6. Zauważyłem też, że twoja funkcja, zarówno w języku T-SQL, jak i C #, używa tylko 1 z 3 parametrów wejściowych. Czy to tylko dowód koncepcji, czy zredagowanego kodu?
Solomon Rutzky
źródło
1. Dziękujemy za wskazówkę dotyczącą korzystania z typów Sql. Dokonam teraz tej zmiany. 2. Działają tu siły zewnętrzne, które wymagają ich użycia. Nie jestem tym zachwycony, ale zaufaj mi, to lepsze niż alternatywa. Moje oryginalne pytanie zawiera trochę wyjaśnienia, dlaczego pozornie asyninowa funkcja istnieje i jest używana.
Russ Suter,
@RussSuter Zrozumiał re: siły zewnętrzne. Właśnie wskazywałam pewne pułapki, które mogły nie być znane, kiedy zapadła taka decyzja. Tak czy inaczej, nie jestem w stanie znaleźć notatek ani odtworzyć scenariusza z kilku szczegółów, które o nim pamiętam. Po prostu pamiętam coś, co zdecydowanie zmieniło się w 2017 roku w odniesieniu do transakcji i wywoływania kodu z zestawu, i byłem naprawdę zirytowany tym, ponieważ wydawało się to niepotrzebną zmianą na gorsze, i musiałem obejść to, co testowałem, co działało w poprzednich wersjach. Tak, proszę zamieścić link w pytaniu do impasu XML.
Solomon Rutzky
Dzięki za te dodatkowe informacje. Oto link do XML: dropbox.com/s/n9w8nsdojqdypqm/deadlock17.xml?dl=0
Russ Suter
@RussSuter Czy próbowałeś tego z wprowadzaniem T-SQL? Patrząc na impas XML (co nie jest łatwe, ponieważ jest to pojedyncza linia - wszystkie nowe wiersze zostały jakoś usunięte) wydaje się, że jest to seria blokad PAGE między sesjami 60 i 78. Pomiędzy sesjami jest zablokowanych 8 stron: 3 za jedną SPID i 5 dla pozostałych. Każdy z innym identyfikatorem procesu, więc jest to kwestia równoległości. Jeśli ma to związek z SQLCLR, może to być ironicznie fakt, że SQLCLR nie zapobiega równoległości. Właśnie dlatego zapytałem, czy próbowałeś umieścić prostą funkcję w linii, ponieważ może to również pokazać impas.
Solomon Rutzky