SQL Server: gestione della localizzazione delle stringhe in pile di viste nidificate non deterministiche


20

Durante la creazione del profilo di un database mi sono imbattuto in una vista che fa riferimento ad alcune funzioni non deterministiche a cui si accede 1000-2500 volte al minuto per ogni connessione nel pool di questa applicazione. Un semplice SELECTdalla vista produce il seguente piano di esecuzione:

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Sembra un piano complesso per una vista che ha meno di mille righe che possono vedere una o due righe cambiare ogni pochi mesi. Ma peggiora con le seguenti altre osservanze:

  1. Le viste nidificate non sono deterministiche, quindi non possiamo indicizzarle
  2. Ogni vista fa riferimento a più UDFs per creare le stringhe
  3. Ogni UDF contiene messaggi nidificati UDFper ottenere i codici ISO per le lingue localizzate
  4. Le viste nello stack utilizzano costruttori di stringhe aggiuntivi restituiti da UDFs come JOINpredicati
  5. Ogni stack di vista viene trattato come una tabella, il che significa che ci sono INSERT/ UPDATE/ DELETEtrigger su ciascuno per scrivere nelle tabelle sottostanti
  6. Questi trigger sulle viste utilizzano CURSORSche le EXECstored procedure che fanno riferimento più di questi archi dell'edificio UDFs.

Questo mi sembra abbastanza marcio, ma ho solo pochi anni di esperienza con TSQL. Anche meglio!

Sembra che lo sviluppatore che abbia deciso che questa fosse un'ottima idea, abbia fatto tutto ciò in modo che le poche centinaia di stringhe memorizzate possano avere una traduzione basata su una stringa restituita da una UDFspecifica dello schema.

Ecco una delle viste nello stack, ma sono tutte ugualmente cattive:

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

Ecco perché UDFvengono usati come JOINpredicati. La I18NIDcolonna è formata concatenando:STRING + [ + ID + | + ID + ]

Durante il test di questi, un semplice SELECTdalla vista restituisce ~ 309 righe e richiede 900-1400ms per l'esecuzione. Se scarico le stringhe in un'altra tabella e schiaffo un indice su di essa, la stessa selezione ritorna in 20-75ms.

Quindi, per farla breve (e spero che abbiate apprezzato un po 'di questa sciocchezza) Voglio essere un buon samaritano e riprogettare e riscrivere questo per il 99% dei clienti che gestiscono questo prodotto che non usano affatto alcuna localizzazione- - gli utenti dovrebbero utilizzare le [en-US]impostazioni locali anche quando l'inglese è una seconda / terza lingua.

Dal momento che si tratta di un trucco non ufficiale, sto pensando a quanto segue:

  1. Creare una nuova tabella String popolata con un set di dati unito correttamente dalle tabelle di base originali
  2. Indicizza la tabella.
  3. Creare un set di sostituzione di viste di livello superiore nello stack che includa NVARCHARe INTcolonne per le colonne WKTypee WKIndex.
  4. Modifica una manciata di UDFs che fanno riferimento a queste viste per evitare conversioni di tipo in alcuni predicati di join (la nostra tabella di controllo più grande è di 500-2.000 M righe e ne memorizza una INTin una NVARCHAR(4000)colonna che viene utilizzata per unire contro la WKIndexcolonna ( INT).)
  5. Schemabind le viste
  6. Aggiungi alcuni indici alle viste
  7. Ricostruisci i trigger sulle viste usando set logic anziché cursori

Ora, le mie domande attuali:

  1. Esiste un metodo di best practice per gestire stringhe localizzate tramite una vista?
  2. Quali alternative esistono per usare a UDFcome stub? (Posso scrivere uno specifico VIEWper ciascun proprietario dello schema e codificare la lingua invece di fare affidamento su una varietà di UDFstub.)
  3. Queste viste possono essere semplicemente rese deterministiche qualificando completamente le UDFs nidificate e quindi schematizzando le pile di viste?

5
Prova a convertire l' UDF scalare in una UDF valutata in tabella . Pubblica anche la tua UDFdefinizione. Inoltre, fai riferimento a Funzioni definite dall'utente T-SQL: il buono, il brutto e il cattivo
Kin Shah,

Questo ti aiuta in qualche modo? stackoverflow.com/questions/316780/...
stacylaray

Risposte:


1

Guardando il codice dato, possiamo dire,

  • Innanzitutto, questa non dovrebbe essere una vista ma dovrebbe essere una procedura memorizzata, in quanto non si limita a leggere da una tabella, ma utilizza UDF.
  • In secondo luogo, l'UDF non dovrebbe essere chiamato frequentemente per la stessa colonna. Qui, viene chiamato una volta nella selezione

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

    e la seconda volta per l'adesione

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

È possibile generare valori in una tabella temporanea o utilizzare un CTE (Common Table Expression) per ottenere quei valori in primo luogo prima che avvenga il join.

Ho generato un USP di esempio che fornirà alcuni miglioramenti:

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

Per favore, prova questo


Ciao MarmiK, grazie per aver dedicato del tempo a dare un'occhiata a questo post. Questa è purtroppo una vista (in una serie di viste nidificate), quindi spostarlo in una stored procedure era fuori questione.
beeks

bene in quel caso possiamo usare CTE in vista poiché le tabelle temporanee non sono consigliate in Vista. O le righe della tabella temporanea possono essere generate da alcune stored procedure e possono essere richiamate in vista.
MarmiK,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.