SQL Server: manejo de la localización de cadenas en pilas de vista no deterministas anidadas


20

Al perfilar una base de datos, me encontré con una vista que hace referencia a algunas funciones no deterministas a las que se accede 1000-2500 veces por minuto para cada conexión en el grupo de esta aplicación. Un simple SELECTdesde la vista produce el siguiente plan de ejecución:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Parece un plan complejo para una vista que tiene menos de mil filas que pueden ver una o dos filas cambiar cada pocos meses. Pero empeora con las siguientes otras observancias:

  1. Las vistas anidadas no son deterministas, por lo que no podemos indexarlas
  2. Cada vista hace referencia a múltiples UDFs para construir las cadenas
  3. Cada UDF contiene UDFs anidados para obtener los códigos ISO para idiomas localizados
  4. Las vistas en la pila están utilizando constructores de cadenas adicionales devueltos por UDFs como JOINpredicados
  5. Cada pila de vista se trata como una tabla, lo que significa que hay INSERT/ UPDATE/ DELETEdisparadores en cada una para escribir en las tablas subyacentes
  6. Estos factores desencadenantes de las vistas utilizan CURSORSque los EXECprocedimientos almacenados que hacen referencia a más de estas cuerdas edificio UDFs.

Esto me parece bastante malo, pero solo tengo unos años de experiencia con TSQL. ¡También se pone mejor!

Parece que el desarrollador que decidió que esta era una gran idea, hizo todo esto para que los cientos de cadenas que se almacenan puedan tener una traducción basada en una cadena devuelta desde un UDFesquema específico.

Aquí hay una de las vistas en la pila, pero todas son igualmente malas:

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

Aquí es por qué UDFs se utilizan como JOINpredicados. La I18NIDcolumna se forma concatenando:STRING + [ + ID + | + ID + ]

Durante la prueba de estos, un simple SELECTdesde la vista devuelve ~ 309 filas y tarda 900-1400ms en ejecutarse. Si vuelco las cadenas en otra tabla y le doy un índice, la misma selección regresa en 20-75 ms.

Así que, para resumir (y espero que hayan apreciado algo de esta tontería), quiero ser un buen samaritano y rediseñar y reescribir esto para el 99% de los clientes que ejecutan este producto y que no utilizan ninguna localización en absoluto. Se espera que los usuarios finales usen la [en-US]configuración regional incluso cuando el inglés es un segundo / tercer idioma.

Dado que este es un truco no oficial, estoy pensando en lo siguiente:

  1. Cree una nueva tabla de Cadena rellenada con un conjunto de datos limpiamente unidos de las tablas base originales
  2. Indice la tabla.
  3. Cree un conjunto de reemplazo de vistas de nivel superior en la pila que incluya columnas NVARCHARy INTpara las columnas WKTypey WKIndex.
  4. Modifique un puñado de UDFcorreos electrónicos que hagan referencia a estas vistas para evitar conversiones de tipos en algunos predicados de unión (nuestra tabla de auditoría más grande es de 500-2,000M filas y almacena una INTen una NVARCHAR(4000)columna que se usa para unirse contra la WKIndexcolumna ( INT)).
  5. Schemabind las vistas
  6. Agregue algunos índices a las vistas.
  7. Reconstruya los desencadenantes en las vistas usando la lógica establecida en lugar de los cursores

Ahora, mis preguntas reales:

  1. ¿Existe un método de mejores prácticas para manejar cadenas localizadas a través de una vista?
  2. ¿Qué alternativas existen para usar a UDFcomo trozo? (Puedo escribir un específico VIEWpara cada propietario de esquema y codificar el idioma en lugar de confiar en una variedad de UDFstubs).
  3. ¿Pueden estas vistas hacerse simplemente deterministas al calificar completamente los UDFsy anidados y luego vincular esquemáticamente las pilas de vistas?

55

¿Esto te ayuda de alguna manera? stackoverflow.com/questions/316780/…
stacylaray

Respuestas:


1

Mirando el código dado, podemos decir:

  • Primero, esto no debería ser una vista, sino un procedimiento almacenado, ya que no es solo una lectura de una tabla, sino que utiliza UDF.
  • En segundo lugar, el UDF no debe llamarse con frecuencia para la misma columna. Aquí, se llama una vez en la selección

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

    y segunda vez para unirse

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

Se pueden generar valores en una tabla temporal o usar un CTE (expresión de tabla común) para obtener esos valores en primer lugar antes de que se realice la unión.

He generado una muestra de USP que proporcionará algunas mejoras:

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 intente esto


Hola MarmiK, gracias por tomarte el tiempo de leer esta publicación. Desafortunadamente, esta es una vista (en una serie de vistas anidadas), por lo que no se puede mover a un procedimiento almacenado.
suena

bueno, en ese caso podemos usar CTE a la vista ya que las tablas temporales no se recomiendan en la Vista. O bien, las filas de la tabla temporal se pueden generar mediante algún procedimiento almacenado y se pueden llamar a la vista.
MarmiK
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.