SQL Server: si la lógica en el procedimiento almacenado y la caché del plan

15

SQL Server 2012 y 2016 Standard:

Si pongo if-elselógica en un procedimiento almacenado para ejecutar una de dos ramas de código, dependiendo del valor de un parámetro, ¿el motor almacena en caché la última versión?

Y si en la siguiente ejecución, el valor del parámetro cambia, ¿volverá a compilar y volver a almacenar en caché el procedimiento almacenado , ya que se debe ejecutar una rama diferente del código? (Esta consulta es bastante costosa de compilar).

Nicole G.
fuente

Respuestas:

27

SQL Server 2012 y 2016 Standard: si pongo lógica if-else en un procedimiento almacenado para ejecutar una de dos ramas de código, dependiendo del valor de un parámetro, ¿el motor almacena en caché la última versión?

No, almacena en caché todas las versiones. O más bien, almacena en caché una versión con todas las rutas exploradas, compiladas con variables pasadas.

Aquí hay una demostración rápida, utilizando la base de datos Stack Overflow.

Crea un índice:

CREATE INDEX ix_yourmom ON dbo.Users (Reputation) INCLUDE (Id, DisplayName);
GO 

Cree un procedimiento almacenado con una pista de índice que apunte a un índice que no existe, en código ramificado.

CREATE OR ALTER PROCEDURE dbo.YourMom (@Reputation INT)
AS 
BEGIN

    IF @Reputation = 1
    BEGIN
        SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u WITH (INDEX = PK_Users_Id)
        WHERE u.Reputation = @Reputation;
    END;

    IF @Reputation > 1
    BEGIN
        SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u WITH (INDEX = ix_yourdad)
        WHERE u.Reputation = @Reputation;

    END;

END;

Si ejecuto ese proceso almacenado buscando Reputación = 1, obtengo un error.

EXEC dbo.YourMom @Reputation = 1;

Msg 308, Nivel 16, Estado 1, Procedimiento YourMom, Línea 14 [Batch Start Line 32] Indice 'ix_yourdad' en la tabla 'dbo.Users' (especificado en la cláusula FROM) no existe.

Si arreglamos el nombre del índice y volvemos a ejecutar la consulta, el plan en caché se ve así:

Nueces

En el interior, el XML tendrá dos referencias a la @Reputationvariable.

<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" />

Una prueba un poco más simple sería obtener un plan estimado para el proceso almacenado. Puede ver el optimizador explorando ambas rutas:

Nueces

Y si en la siguiente ejecución, el valor del parámetro cambia, ¿volverá a compilar y volver a almacenar en caché el procedimiento almacenado, porque se debe ejecutar una rama diferente del código? (Esta consulta es bastante costosa de compilar). Gracias.

No, retendrá el valor de tiempo de ejecución de la primera compilación.

Si volvemos a ejecutar con un diferente @Reputation:

EXEC dbo.YourMom @Reputation = 2;

Del plan actual :

<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" ParameterRuntimeValue="(2)" />

Todavía tenemos un valor compilado de 1, pero ahora un valor de tiempo de ejecución de 2.

En el caché del plan, que puede consultar con una herramienta gratuita como la que desarrolla mi empresa, sp_BlitzCache :

Nueces

El procedimiento almacenado se ha llamado dos veces, y cada instrucción en él se ha llamado una vez.

¿Entonces que tenemos? Un plan en caché para ambas consultas en el procedimiento almacenado.

Si desea este tipo de lógica ramificada, debería llamar a procedimientos subalmacenados:

CREATE OR ALTER PROCEDURE dbo.YourMom (@Reputation INT)
AS 
BEGIN

    IF @Reputation = 1
    BEGIN

        EXEC dbo.Reputation1Query;

    END;

    IF @Reputation > 1
    BEGIN

        EXEC dbo.ReputationGreaterThan1Query;

    END;

END;

O SQL dinámico:

DECLARE @sql NVARCHAR(MAX) = N''

SET @sql +=
N'
SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u '
IF @Reputation = 1
BEGIN
    SET @sql += N' (INDEX = PK_Users_Id)
        WHERE u.Reputation = @Reputation;'
END;


IF @Reputation > 1 
BEGIN

SET @sql += ' WITH (INDEX = ix_yourmom)
        WHERE u.Reputation = @Reputation;'

END;


EXEC sys.sp_executesql @sql;

¡Espero que esto ayude!

Erik Darling
fuente