SQL Server - обработка локализации строк во вложенных недетерминированных стеках представлений

20

При профилировании базы данных я столкнулся с представлением, которое ссылается на некоторые недетерминированные функции, к которым обращаются 1000-2500 раз в минуту для каждого соединения в пуле этого приложения. Простое SELECTпредставление дает следующий план выполнения:

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь

Это выглядит как сложный план для представления, в котором менее тысячи строк, в которых один или два ряда могут меняться каждые несколько месяцев. Но это ухудшается со следующими другими соблюдениями:

  1. Вложенные представления являются недетерминированными, поэтому мы не можем их индексировать
  2. Каждое представление ссылается на несколько UDFs для построения строк
  3. Каждый UDF содержит вложенные UDFs для получения ISO-кодов для локализованных языков.
  4. Представления в стеке используют дополнительные строители строк, возвращаемые из UDFs в качестве JOINпредикатов
  5. Каждый стек представлений обрабатывается как таблица, что означает наличие INSERT/ UPDATE/ DELETEтриггеров на каждом для записи в базовые таблицы
  6. Эти триггеры взглядов использовать , CURSORSчто EXECхранимые процедуры , которые ссылаются на более из этих строк здания UDFs.

Мне это кажется довольно гнилым, но у меня всего несколько лет опыта работы с TSQL. Это тоже становится лучше!

Похоже, разработчик, который решил, что это отличная идея, сделал все это так, чтобы несколько сотен хранимых строк могли иметь перевод, основанный на строке, возвращенной из UDFспецифической для схемы схемы.

Вот одно из представлений в стеке, но все они одинаково плохи:

CREATE VIEW [UserWKStringI18N]
AS
SELECT b.WKType, b.WKIndex
    , CASE
       WHEN ISNULL(il.I18NID, N'') = N''
       THEN id.I18NString
       ELSE il.I18nString
       END AS WKString
    ,CASE
       WHEN ISNULL(il.I18NID, N'') = N''
       THEN id.IETFLangCode
       ELSE il.IETFLangCode
       END AS IETFLangCode
    ,dbo.User3StringI18N_KeyValue(b.WKType, b.WKIndex, N'WKS') AS I18NID
    ,dbo.UserI18N_Session_Locale_Key()  AS IETFSessionLangCode
    ,dbo.UserI18N_Database_Locale_Key() AS IETFDatabaseLangCode
FROM   UserWKStringBASE b
LEFT OUTER JOIN User3StringI18N il
ON    (
il.I18NID       = dbo.User3StringI18N_KeyValue(b.WKType, b.WKIndex, N'WKS')
AND il.IETFLangCode = dbo.UserI18N_Session_Locale_Key()
)
LEFT OUTER JOIN User3StringI18N id
ON    (
id.I18NID       = dbo.User3StringI18N_KeyValue(b.WKType, b.WKIndex,N'WKS')
AND id.IETFLangCode = dbo.UserI18N_Database_Locale_Key()
)
GO

Вот почему UDFs используются в качестве JOINпредикатов. I18NIDКолонна формируется путем конкатенации:STRING + [ + ID + | + ID + ]

Во время их тестирования простой SELECTиз представления возвращает ~ 309 строк и требует 900-1400 мс для выполнения. Если я помещаю строки в другую таблицу и добавляю к ней индекс, тот же выбор возвращается через 20-75 мс.

Итак, короткая история (и я надеюсь, что вы оценили эту глупость), я хочу быть добрым самаритянином, перепроектировать и переписать это для 99% клиентов, использующих этот продукт, которые вообще не используют какую-либо локализацию. Ожидается, что конечные пользователи будут использовать [en-US]локаль, даже если английский является вторым / третьим языком.

Поскольку это неофициальный взлом, я думаю о следующем:

  1. Создайте новую таблицу String, заполненную четко объединенным набором данных из исходных базовых таблиц.
  2. Индексируйте таблицу.
  3. Создание замены набора представлений верхнего уровня в стеке , которые включают в себя NVARCHARи INTстолбцах для WKTypeи WKIndexстолбцов.
  4. Измените несколько UDFs, которые ссылаются на эти представления, чтобы избежать преобразований типов в некоторых предикатах соединения (наша самая большая таблица аудита - 500-2000M строк и хранит INTв NVARCHAR(4000)столбце, который используется для объединения со WKIndexстолбцом ( INT).)
  5. Схема привязки взглядов
  6. Добавьте несколько индексов к представлениям
  7. Перестройте триггеры в представлениях, используя заданную логику вместо курсоров.

Теперь мои актуальные вопросы:

  1. Есть ли лучший метод для обработки локализованных строк через представление?
  2. Какие существуют альтернативы для использования UDFв качестве заглушки? (Я могу написать конкретный VIEWдля каждого владельца схемы и жестко закодировать язык, не полагаясь на различные UDFзаглушки.)
  3. Можно ли просто сделать эти представления детерминированными, полностью квалифицируя вложенные UDFs и затем связывая схемы стеками представления?
beeks
источник
5
Попробуйте преобразовать Scalar UDF во встроенную табличную UDF . Опубликуйте также и ваше UDFопределение. Также обратитесь к пользовательским функциям T-SQL: хорошее, плохое и безобразное
Кин Шах
Это тебе как-нибудь помогает? stackoverflow.com/questions/316780/…
stacylaray
Или этот? stackoverflow.com/questions/258483/…
stacylaray

Ответы:

1

Глядя на данный код, мы можем сказать,

  • Во-первых, это не должно быть представление, это должна быть хранимая процедура, поскольку она не просто читает из таблицы, но использует UDF.
  • Во-вторых, UDF не следует часто вызывать для одного и того же столбца. Здесь он вызывается один раз в выбранном

    ,dbo.User3StringI18N_KeyValue(b.WKType, b.WKIndex, N'WKS') AS I18NID 

    и второй раз за вступление

    .IETFLangCode = dbo.User3StringI18N_KeyValue(b.WKType, b.WKIndex, N'WKS')

Можно генерировать значения во временной таблице или использовать CTE (Common Table Expression), чтобы получить эти значения в первую очередь до того, как произойдет соединение.

Я создал образец USP, который обеспечит некоторые улучшения:

CREATE PROCEDURE usp_UserWKStringI18N
AS
BEGIN
    -- Do operation using UDF 
    SELECT b.WKType
        ,b.WKIndex
        ,dbo.User3StringI18N_KeyValue(b.WKType, b.WKIndex, N'WKS') AS I18NID
        ,dbo.UserI18N_Session_Locale_Key() AS IETFSessionLangCode
        ,dbo.UserI18N_Database_Locale_Key() AS IETFDatabaseLangCode
    INTO #tempTable
    FROM UserWKStringBASE b;

    -- Now final Select
    SELECT b.WKType
        ,b.WKIndex
        ,CASE 
            WHEN ISNULL(il.I18NID, N'') = N''
                THEN id.I18NString
            ELSE il.I18nString
            END AS WKString
        ,CASE 
            WHEN ISNULL(il.I18NID, N'') = N''
                THEN id.IETFLangCode
            ELSE il.IETFLangCode
            END AS IETFLangCode
        ,b.I18NID
        ,b.IETFSessionLangCode
        ,b.IETFDatabaseLangCode
    FROM #tempTable b
    LEFT OUTER JOIN User3StringI18N il
        ON il.I18NID = b.I18NID
            AND il.IETFLangCode = b.IETFSessionLangCode
    LEFT OUTER JOIN User3StringI18N id
        ON id.I18NID = b.I18NID
            AND id.IETFLangCode = b.IETFDatabaseLangCode
END

Пожалуйста, попробуйте это

MarmiK
источник
Привет MarmiK, спасибо, что нашли время, чтобы взглянуть на этот пост. К сожалению, это представление (в серии вложенных представлений), поэтому о переносе его в хранимую процедуру не могло быть и речи.
beeks
в таком случае мы можем использовать CTE в представлении, так как временные таблицы не рекомендуются в представлении. ИЛИ строки временной таблицы могут быть сгенерированы некоторой хранимой процедурой и могут быть вызваны в представлении.
MarmiK