¿De dónde provienen esta exploración constante y la unión externa izquierda en un plan de consulta trivial SELECT?


21

Tengo esta tabla:

CREATE TABLE [dbo].[Accounts] (
    [AccountId] UNIQUEIDENTIFIER UNIQUE NOT NULL DEFAULT NEWID(),
    -- WHATEVER other columns
);
GO
CREATE UNIQUE CLUSTERED INDEX [AccountsIndex]
    ON [dbo].[Accounts]([AccountId] ASC);
GO

Esta consulta:

DECLARE @result UNIQUEIDENTIFIER
SELECT @result = AccountId FROM Accounts WHERE AccountId='guid-here'

se ejecuta con un plan de consulta que consta de una sola búsqueda de índice, como se esperaba:

SELECT <---- Clustered Index Seek

Esta consulta hace lo mismo:

DECLARE @result UNIQUEIDENTIFIER
SET @result = (SELECT AccountId FROM Accounts WHERE AccountId='guid-here')

pero se ejecuta con un plan en el que el resultado de Index Seek se une exteriormente con el resultado de un escaneo constante y luego se introduce en Compute Scalar:

SELECT <--- Compute Scalar <--- Left Outer Join <--- Constant Scan
                                      ^
                                      |------Clustered Index Seek

¿Qué es esa magia extra? ¿Qué hace esa exploración constante seguida de la unión externa izquierda?

Respuestas:


29

La semántica de las dos declaraciones es diferente:

  • El primero no establece el valor de la variable si no se encuentra ninguna fila.
  • El segundo siempre establece la variable, incluso en nulo si no se encuentra ninguna fila.

La exploración constante produce una fila vacía (¡sin columnas!) Que dará como resultado que la variable se actualice en caso de que nada coincida con la tabla base. La unión izquierda asegura que la fila vacía sobreviva a la unión. Se puede pensar que la asignación variable ocurre en el nodo raíz del plan de ejecución.

Utilizando SELECT @result

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result does not change
SELECT @result = AccountId 
FROM Accounts 
WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'};

SELECT @result;

Resultado 1

Utilizando SET @result

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result set to null
SET @result = 
(
    SELECT AccountId 
    FROM Accounts 
    WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'}
);

SELECT @result;

Resultado 2

Planes de ejecucion

SELECCIONAR tareaNo llega ninguna fila al nodo raíz, por lo que no se produce ninguna asignación.

SET asignaciónUna fila siempre llega al nodo raíz, por lo que se produce una asignación variable.


La exploración constante adicional y la unión externa izquierda de los bucles anidados no son motivo de preocupación. La unión en particular es barata, ya que se garantiza que encontrará una fila en su entrada externa y, como máximo, una fila (en su ejemplo) en la entrada interna.

Hay otras formas de garantizar que se genere una fila a partir de la subconsulta para garantizar que se produzca una asignación variable. Una es usar un agregado escalar redundante (sin grupo por cláusula):

-- Set initial value
DECLARE @result uniqueidentifier = {guid 'FE2CA909-1162-4C6C-A7AC-33B257E28539'};

-- @result set to null
SET @result = 
    (
        SELECT MAX(AccountId)
        FROM Accounts 
        WHERE AccountId={guid '7AD4D33C-1ED7-4183-B7F3-48C33D666525'} 
    );
SELECT @result;

Resultado 3

Plan de ejecución agregado escalar

Observe que el agregado escalar produce una fila a pesar de que no recibe ninguna entrada.

Documentación:

Si la instrucción SELECT no devuelve filas, la variable conserva su valor actual. Si expresión es una subconsulta escalar que no devuelve ningún valor, la variable se establece en NULL.

Para asignar variables, le recomendamos que use SET @local_variable en lugar de SELECT @local_variable.

Otras lecturas:

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.