Видимо, моя функция сборки CLR вызывает тупики?

9

Наше приложение должно одинаково хорошо работать с базой данных Oracle или базой данных Microsoft SQL Server. Чтобы облегчить это, мы создали несколько UDF для гомогенизации нашего синтаксиса запроса. Например, в SQL Server есть GETDATE (), а в Oracle - SYSDATE. Они выполняют одну и ту же функцию, но это разные слова. Мы написали UDF-оболочку с именем NOW () для обеих платформ, которая оборачивает специфический для конкретной платформы синтаксис в общее имя функции. У нас есть другие такие функции, некоторые из которых по сути ничего не делают, но существуют исключительно ради гомогенизации. К сожалению, это имеет цену для SQL Server. Встроенные скалярные UDF наносят ущерб производительности и полностью отключают параллелизм. В качестве альтернативы мы написали функции сборки CLR для достижения тех же целей. Когда мы развернули это на клиенте, они начали испытывать частые тупики. Этот конкретный клиент использует методы репликации и высокой доступности, и мне интересно, происходит ли какое-то взаимодействие здесь. Я просто не понимаю, как введение функции CLR может вызвать такие проблемы. Для справки я включил исходное скалярное определение UDF, а также определение CLR для замены в C # и объявление SQL для него. У меня также есть тупиковый XML, который я могу предоставить, если это поможет.

Оригинальный 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

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

Декларация SQL Server для функции 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
Детерминированные скалярные функции CLR не должны приводить к тупикам. Конечно функции CLR, которые читают базу данных, могли бы. Вы должны включить тупиковый XML в ваш вопрос.
Дэвид Браун - Microsoft

Ответы:

7

Какие версии SQL Server вы используете?

Я помню, что видел небольшое изменение в поведении в SQL Server 2017 не так давно. Мне придется вернуться и посмотреть, смогу ли я найти, где я это сделал, но я думаю, что это связано с блокировкой схемы, инициируемой при обращении к объекту SQLCLR.

Пока я ищу это, я скажу следующее относительно вашего подхода:

  1. Пожалуйста, используйте Sql*типы для входных параметров, возвращаемые типы. Вы должны использовать SqlStringвместо string. SqlStringочень похож на пустую строку (вашу value?, но в нее встроены другие функции, специфичные для SQL Server. У всех Sql*типов есть Valueсвойство, которое возвращает ожидаемый тип .NET (например, SqlString.Valuereturn string, SqlInt32return int, SqlDateTimereturn DateTimeи т. д.).
  2. Я бы порекомендовал против всего этого подхода начать с того, связаны ли взаимоблокировки. Я говорю это потому что:

    1. Даже с учетом того, что детерминированные пользовательские функции SQLCLR могут участвовать в параллельных планах, вы, скорее всего, получите снижение производительности для эмуляции упрощенных встроенных функций.
    2. API SQLCLR не позволяет VARCHAR. Вы в порядке с неявным преобразованием всего в NVARCHARи затем снова VARCHARдля простых операций?
    3. API SQLCLR не допускает перегрузки, поэтому вам может потребоваться несколько версий функций, которые допускают разные подписи в T-SQL и / или PL / SQL.
    4. Подобно тому, как не допускать перегрузки, существует большая разница между NVARCHAR(4000)и NVARCHAR(MAX): MAXтип (имеющий хотя бы один из них в сигнатуре) заставляет вызов SQLCLR занимать в два раза больше времени, чем отсутствие какого-либо MAXтипа в сигнатуре (я полагаю, что это верно верно и VARBINARY(MAX)против VARBINARY(4000)). Итак, вам нужно выбрать между:
      • использовать только NVARCHAR(MAX)для упрощенного API, но снизить производительность при использовании строковых данных 8000 байт или менее, или
      • создание двух вариантов для всех / большинства / многих строковых функций: одна с MAXтипами, а другая без (для случаев, когда вы гарантированно никогда не будете передавать или выводить более 8000 байтов строковых данных). Именно этот подход я выбрал для большинства функций в моей библиотеке SQL # : есть Trim()функция, которая, вероятно, имеет один или несколько MAXтипов, и Trim4k()версия, которая никогда не имеет MAXтипа где-либо в схеме сигнатуры или набора результатов. Версии "4k" абсолютно более эффективны.
    5. Вы не будете осторожны, чтобы эмулировать функциональность, учитывая пример из вопроса. LTRIMи RTRIMтолько обрезать пробелы, в то время как .NET String.Trim()обрезает пробелы (по крайней мере, пробел, табуляции и новые строки). Например:

        PRINT LTRIM(RTRIM(N'      a       '));
    6. Кроме того, я только что заметил, что ваша функция, как в T-SQL, так и в C #, использует только 1 из 3 входных параметров. Это просто подтверждение концепции или отредактированный код?
Соломон Руцкий
источник
1. Спасибо за совет по использованию типов Sql. Я сделаю это изменение сейчас. 2. Здесь действуют внешние силы, которые требуют их использования. Я не в восторге от этого, но поверь мне, это лучше, чем альтернатива. Мой первоначальный вопрос содержит немного объяснений, почему существует и используется, казалось бы, асининовая функция.
Русс Сутер
@RussSuter Понял re: внешние силы. Я просто указывал на некоторые подводные камни, которые, возможно, не были известны, когда это решение было принято. В любом случае, я не могу найти свои заметки или воспроизвести сценарий из немногих деталей, которые я помню об этом. Я просто помню что-то определенно изменившееся в 2017 году в отношении транзакций и вызова кода из сборки, и я был действительно раздражен этим, так как это казалось ненужным изменением к худшему, и мне пришлось обойти это тем, что я тестировал, чтобы это работало хорошо в предыдущих версиях. Поэтому, пожалуйста, разместите ссылку в вопросе в тупик XML.
Соломон Руцкий
Спасибо за эту дополнительную информацию. Вот ссылка на XML: dropbox.com/s/n9w8nsdojqdypqm/deadlock17.xml?dl=0
Расс Сутер
@RussSuter Вы пробовали это с использованием T-SQL? Если посмотреть на тупиковый XML (который не так прост, так как это одна строка - все новые строки были удалены каким-то образом), то это будет серия блокировок PAGE между сессиями 60 и 78. Между обеими сессиями заблокировано 8 страниц: 3 на одну SPID и 5 для другого. У каждого свой идентификатор процесса, так что это проблема параллелизма. Если это связано с SQLCLR, то по иронии судьбы это может быть тот факт, что SQLCLR не предотвращает параллелизм. Вот почему я спросил, пытались ли вы вставить простую функцию в строку, поскольку это также может указывать на тупик.
Соломон Руцкий,