SQL Server - Manipulando a localização de seqüências de caracteres em pilhas de exibição não determinísticas aninhadas

20

Ao criar um perfil de um banco de dados, deparei-me com uma visão que faz referência a algumas funções não determinísticas que são acessadas de 1000 a 2500 vezes por minuto para cada conexão no pool desse aplicativo. Um simples SELECTda visualização gera o seguinte plano de execução:

insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui

Esse parece ser um plano complexo para uma exibição com menos de mil linhas que podem exibir uma ou duas linhas mudar a cada poucos meses. Mas piora com as seguintes outras observâncias:

  1. As visualizações aninhadas não são determinísticas, portanto, não podemos indexá-las
  2. Cada visualização faz referência a vários UDFs para construir as strings
  3. Cada UDF contém UDFs aninhados para obter os códigos ISO para idiomas localizados
  4. As visualizações na pilha estão usando construtores de sequência adicionais retornados de UDFs como JOINpredicados
  5. Cada pilha de visualizações é tratada como uma tabela, o que significa que há INSERT/ UPDATE/ DELETEgatilhos em cada um para gravar nas tabelas subjacentes
  6. Esses gatilhos nas vistas usar CURSORSque os EXECprocedimentos armazenados que fazem referência mais destes corda edifício UDFs.

Isso me parece muito ruim, mas eu tenho apenas alguns anos de experiência com o TSQL. Fica melhor também!

Parece que o desenvolvedor que decidiu que essa era uma ótima idéia fez tudo isso para que as poucas centenas de strings armazenadas possam ter uma tradução baseada em uma string retornada de uma UDFespecífica do esquema.

Aqui está uma das visualizações na pilha, mas todas são igualmente ruins:

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

Aqui está o porquê de os UDFs serem usados ​​como JOINpredicados. A I18NIDcoluna é formada concatenando:STRING + [ + ID + | + ID + ]

Durante o teste, um simples SELECTda exibição retorna ~ 309 linhas e leva 900-1400ms para ser executado. Se eu colocar as strings em outra tabela e colocar um índice nela, a mesma seleção retornará em 20-75ms.

Portanto, para encurtar a história (e espero que você tenha gostado um pouco dessa bobagem), quero ser um bom samaritano, redesenhar e reescrever isso para os 99% dos clientes que executam este produto que não usam nenhuma localização - Espera-se que os usuários finais usem o [en-US]código do idioma mesmo quando o inglês for um segundo / terceiro idioma.

Como esse é um hack não oficial, estou pensando no seguinte:

  1. Crie uma nova tabela String preenchida com um conjunto de dados unido de forma limpa a partir das tabelas base originais
  2. Indexar a tabela.
  3. Crie um conjunto de visualizações de nível superior na pilha que inclua NVARCHARe INTcolunas para as colunas WKTypee WKIndex.
  4. Modifique um punhado de UDFs que fazem referência a essas visualizações para evitar conversões de tipo em alguns predicados de junção (nossa maior tabela de auditoria tem 500-2.000M de linhas e armazena um INTem uma NVARCHAR(4000)coluna que é usada para ingressar na WKIndexcoluna ( INT).)
  5. Esquemabind as visualizações
  6. Adicione alguns índices às visualizações
  7. Reconstrua os gatilhos nas visualizações usando lógica de conjunto em vez de cursores

Agora, minhas perguntas reais:

  1. Existe um método de prática recomendada para lidar com seqüências de caracteres localizadas por meio de uma exibição?
  2. Quais alternativas existem para usar a UDFcomo um esboço? (Eu posso escrever um específico VIEWpara cada proprietário do esquema e codificar a linguagem em vez de confiar em uma variedade de UDFstubs.)
  3. Essas visualizações podem ser simplesmente tornadas determinísticas, qualificando totalmente os se aninhados UDFe depois vinculando o esquema às pilhas de visualizações?
Beeks
fonte
5
Tente converter o UDF escalar em um UDF com valor de tabela embutido . Poste sua UDFdefinição também. Além disso, referem-se a T-SQL funções definidas pelo usuário: o bom, o mau eo feio
Kin Shah
Isso ajuda você de alguma forma? stackoverflow.com/questions/316780/…
stacylaray 31/03
Ou este? stackoverflow.com/questions/258483/…
stacylaray 31/03

Respostas:

1

Olhando para o código fornecido, podemos dizer:

  • Primeiro, isso não deve ser uma visualização, mas deve ser um procedimento armazenado, pois não é apenas a leitura de uma tabela, mas usa UDFs.
  • Segundo, o UDF não deve ser chamado com frequência para a mesma coluna. Aqui, é chamado uma vez no select

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

    e Segunda vez para ingressar

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

É possível gerar valores em uma tabela temporária ou usar uma CTE (Common Table Expression) para obter esses valores em primeiro lugar antes que a junção ocorra.

Eu criei uma amostra da USP que fornecerá algumas melhorias:

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

Por favor tente isto

MarmiK
fonte
Olá MarmiK, obrigado por reservar um tempo para dar uma olhada neste post. Infelizmente, essa é uma exibição (em uma série de exibições aninhadas), portanto, movê-la para um procedimento armazenado estava fora de questão.
beeks 03/02
Nesse caso, podemos usar o CTE no modo de exibição, pois as tabelas temporárias não são recomendadas no modo de exibição. OU as linhas da tabela temporária podem ser geradas por algum procedimento armazenado e podem ser chamadas no modo de exibição.
Marmik