SQL Server - Gestion de la localisation des chaînes dans des piles de vues imbriquées non déterministes

20

Lors du profilage d'une base de données, je suis tombé sur une vue qui fait référence à certaines fonctions non déterministes qui sont accessibles 1000 à 2500 fois par minute pour chaque connexion dans le pool de cette application. Un simple SELECTde la vue donne le plan d'exécution suivant:

entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici

Cela semble être un plan complexe pour une vue comportant moins d'un millier de lignes qui peut voir une ou deux lignes changer tous les quelques mois. Mais cela empire avec les autres observances suivantes:

  1. Les vues imbriquées ne sont pas déterministes, nous ne pouvons donc pas les indexer
  2. Chaque vue fait référence à plusieurs UDFs pour créer les chaînes
  3. Chaque UDF contient des UDFs imbriqués pour obtenir les codes ISO des langues localisées
  4. Les vues de la pile utilisent des générateurs de chaînes supplémentaires renvoyés par UDFs comme JOINprédicats
  5. Chaque pile de vues est traitée comme une table, ce qui signifie qu'il y a des INSERT/ UPDATE/ DELETEdéclencheurs sur chacun pour écrire dans les tables sous-jacentes
  6. Ces déclencheurs sur les vues utilisent CURSORSque les EXECprocédures stockées qui référence plus de ces bâtiments chaîne UDFs.

Cela me semble assez pourri, mais je n'ai que quelques années d'expérience avec TSQL. Ça va mieux aussi!

Il semble que le développeur qui a décidé que c'était une excellente idée, ait fait tout cela pour que les quelques centaines de chaînes stockées puissent avoir une traduction basée sur une chaîne renvoyée par un UDFschéma spécifique.

Voici l'une des vues de la pile, mais elles sont toutes également mauvaises:

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

Voici pourquoi les UDFs sont utilisés comme JOINprédicats. La I18NIDcolonne est formée en concaténant:STRING + [ + ID + | + ID + ]

Pendant le test de ces derniers, un simple à SELECTpartir de la vue renvoie ~ 309 lignes et prend 900-1400 ms à exécuter. Si je vide les chaînes dans une autre table et que je tape un index dessus, la même sélection revient dans 20 à 75 ms.

Alors, longue histoire courte (et j'espère que vous avez apprécié une partie de cette sillyness) Je veux être un bon samaritain et re-conception et ré-écrire pour 99% des clients exécutant ce produit qui ne pas utilisent une localisation à tout - -les utilisateurs finaux sont censés utiliser les [en-US]paramètres régionaux même lorsque l'anglais est une deuxième / troisième langue.

Puisqu'il s'agit d'un hack non officiel, je pense à ce qui suit:

  1. Créer une nouvelle table String remplie d'un ensemble de données jointes à partir des tables de base d'origine
  2. Indexez la table.
  3. Créez un ensemble de remplacement de vues de niveau supérieur dans la pile qui incluent NVARCHARet INTcolonnes pour les colonnes WKTypeet WKIndex.
  4. Modifiez une poignée de UDFs qui font référence à ces vues pour éviter les conversions de type dans certains prédicats de jointure (notre plus grande table d'audit est de 500 à 2 000 millions de lignes et stocke un INTdans une NVARCHAR(4000)colonne qui est utilisée pour se joindre à la WKIndexcolonne ( INT).)
  5. Schemabind les vues
  6. Ajoutez quelques index aux vues
  7. Reconstruisez les déclencheurs sur les vues en utilisant la logique définie au lieu des curseurs

Maintenant, mes vraies questions:

  1. Existe-t-il une méthode recommandée pour gérer les chaînes localisées via une vue?
  2. Quelles alternatives existent pour utiliser un UDFcomme stub? (Je peux écrire un spécifique VIEWpour chaque propriétaire de schéma et coder en dur la langue au lieu de compter sur une variété de UDFstubs.)
  3. Ces vues peuvent-elles être simplement rendues déterministes en qualifiant pleinement les UDFs imbriqués et en schématisant ensuite les piles de vues?
les semaines
la source
5
Essayez de convertir l' UDF scalaire en UDF de valeur de table Inline . Publiez également votre UDFdéfinition. Reportez-vous également aux fonctions définies par l'utilisateur T-SQL: le bon, le mauvais et le laid
Kin Shah
Est-ce que cela vous aide d'une manière ou d'une autre? stackoverflow.com/questions/316780/…
stacylaray
Ou celui-ci? stackoverflow.com/questions/258483/…
stacylaray

Réponses:

1

En regardant le code donné, nous pouvons dire,

  • Tout d'abord, cela ne devrait pas être une vue mais ce devrait être une procédure stockée, car il ne s'agit pas seulement de lire une table, mais il utilise des FDU.
  • Deuxièmement, l'UDF ne doit pas être appelé fréquemment pour la même colonne. Ici, il est appelé une fois dans la sélection

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

    et deuxième fois pour rejoindre

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

On peut générer des valeurs dans une table temporaire ou utiliser un CTE (Common Table Expression) pour obtenir ces valeurs en premier lieu avant la jointure.

J'ai généré un exemple d'USP qui apportera quelques améliorations:

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

Veuillez essayer ceci

MarmiK
la source
Bonjour MarmiK, merci d'avoir pris le temps de jeter un œil à ce post. Il s'agit malheureusement d'une vue (dans une série de vues imbriquées), il était donc hors de question de la déplacer vers une procédure stockée.
2017
dans ce cas, nous pouvons utiliser CTE en vue car les tables temporaires ne sont pas recommandées dans View. OU les lignes de la table temporaire peuvent être générées par une procédure stockée et peuvent être appelées en vue.
MarmiK