¿Por qué el costo estimado de (lo mismo) 1000 busca en un índice único difiere en estos planes?


28

En las consultas a continuación, se estima que ambos planes de ejecución realizan 1,000 búsquedas en un índice único.

Las búsquedas son conducidas por una exploración ordenada en la misma tabla fuente, por lo que aparentemente deberían terminar buscando los mismos valores en el mismo orden.

Ambos bucles anidados tienen <NestedLoops Optimized="false" WithOrderedPrefetch="true">

¿Alguien sabe por qué esta tarea tiene un costo de 0.172434 en el primer plan pero 3.01702 en el segundo?

(La razón de la pregunta es que la primera consulta me fue sugerida como una optimización debido al costo aparentemente mucho más bajo del plan. En realidad, me parece que hace más trabajo pero solo estoy tratando de explicar la discrepancia ... .)

Preparar

CREATE TABLE dbo.Target(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL);

CREATE TABLE dbo.Staging(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL); 

INSERT INTO dbo.Target
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1,  
     master..spt_values v2;

INSERT INTO dbo.Staging
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1;

Consulta 1 enlace "Pegar el plan"

WITH T
     AS (SELECT *
         FROM   Target AS T
         WHERE  T.KeyCol IN (SELECT S.KeyCol
                             FROM   Staging AS S))
MERGE T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES(S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol;

Consulta 2 enlace "Pegar el plan"

MERGE Target T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES( S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol; 

Consulta 1

Consulta 2

Lo anterior se probó en SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64)


@Joe Obbish señala en los comentarios que una reproducción más simple sería

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

vs

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN (SELECT * FROM Target) AS T 
    ON T.KeyCol = S.KeyCol;

Para la tabla de etapas de 1,000 filas, las dos anteriores tienen la misma forma de plan con bucles anidados y el plan sin que la tabla derivada parezca más barata, pero para una tabla de etapas de 10,000 filas y la misma tabla de destino que la anterior, la diferencia de costos cambia el plan La forma (con un escaneo completo y una combinación que parece relativamente más atractiva que las búsquedas costosas) que muestra esta discrepancia de costos puede tener otras implicaciones además de dificultar la comparación de planes.

ingrese la descripción de la imagen aquí

Respuestas:


21

¿Alguien sabe por qué esta tarea tiene un costo de 0.172434 en el primer plan pero 3.01702 en el segundo?

En términos generales, una búsqueda del lado interno debajo de una unión de bucles anidados se calcula asumiendo un patrón de E / S aleatorio. Hay una reducción simple basada en reemplazo para accesos posteriores, lo que explica la posibilidad de que la página requerida ya haya sido traída a la memoria por una iteración previa. Esta evaluación básica produce el costo estándar (más alto).

Hay otra entrada de costos, Smart Seek Costing , sobre la cual se conocen pocos detalles. Mi suposición (y eso es todo lo que es en esta etapa) es que SSC intenta evaluar el costo de E / S de búsqueda del lado interno con más detalle, tal vez al considerar el orden local y / o el rango de valores a buscar. Quién sabe.

Por ejemplo, la primera operación de búsqueda trae no solo la fila solicitada, sino todas las filas de esa página (en orden de índice). Dado el patrón de acceso general, recuperar las 1000 filas en 1000 búsquedas requiere solo 2 lecturas físicas, incluso con la lectura anticipada y la captación previa deshabilitadas. Desde esa perspectiva, el costo predeterminado de E / S representa una sobreestimación significativa, y el costo ajustado por SSC está más cerca de la realidad.

Parece razonable esperar que el SSC sea más efectivo cuando el bucle impulsa una búsqueda de índice más o menos directamente, y la referencia externa de unión es la base de la operación de búsqueda. Por lo que puedo decir, el SSC siempre se intenta para operaciones físicas adecuadas, pero a menudo no produce ningún ajuste descendente cuando la búsqueda se separa de la unión por otras operaciones. Los filtros simples son una excepción a esto, tal vez porque SQL Server a menudo puede insertarlos en el operador de acceso a datos. En cualquier caso, el optimizador tiene un soporte bastante profundo para las selecciones.

Es desafortunado que el Escalar de cómputo para las proyecciones externas de la subconsulta parezca interferir aquí con el SSC. Los escalares computacionales generalmente se reubican por encima de la unión, pero estos deben permanecer donde están. Aun así, la mayoría de los escalares informáticos normales son bastante transparentes para la optimización, por lo que esto es un poco sorprendente.

En cualquier caso, cuando la operación física PhyOp_Rangese produce a partir de una simple selección en un índice SelIdxToRng, el SSC es efectivo. Cuando SelToIdxStrategyse emplea el más complejo (selección en una tabla para una estrategia de índice), el resultado PhyOp_Rangeejecuta SSC pero no produce reducción. Nuevamente, parece que las operaciones más simples y directas funcionan mejor con SSC.

Desearía poder decirte exactamente lo que hace SSC y mostrar los cálculos exactos, pero no conozco esos detalles. Si desea explorar la salida de rastreo limitada disponible para usted, puede emplear el indicador de rastreo no documentado 2398. Una salida de ejemplo es:

Coste de búsqueda inteligente (7.1) :: 1.34078e + 154, 0.001

Ese ejemplo se relaciona con el grupo de notas 7, alternativa 1, que muestra un límite superior de costo y un factor de 0.001. Para ver factores más limpios, asegúrese de reconstruir las tablas sin paralelismo para que las páginas sean lo más densas posible. Sin hacer eso, el factor es más parecido a 0.000821 para su tabla Target de ejemplo. Hay algunas relaciones bastante obvias allí, por supuesto.

El SSC también se puede deshabilitar con el indicador de seguimiento no documentado 2399. Con ese indicador activo, ambos costos son el valor más alto.


8

No estoy seguro de que esta sea una respuesta, pero es un poco largo para un comentario. La causa de la diferencia es pura especulación de mi parte y quizás pueda ser motivo de reflexión para los demás.

Consultas simplificadas con planes de ejecución.

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN (
                  SELECT *
                  FROM Target
                  ) AS T 
    ON T.KeyCol = S.KeyCol;

ingrese la descripción de la imagen aquí

La principal diferencia entre estas consultas equivalentes que realmente podrían dar como resultado planes de ejecución idénticos es el operador escalar de cómputo. No sé por qué tiene que estar allí, pero supongo que es lo más lejos que puede llegar el optimizador para optimizar la tabla derivada.

Supongo que la presencia del escalar de cómputo es lo que está arruinando el costo de IO para la segunda consulta.

Desde el interior del optimizador: planificación de costos

El costo de la CPU se calcula como 0.0001581 para la primera fila y 0.000011 para las filas posteriores.
...
El costo de E / S de 0.003125 es exactamente 1/320, lo que refleja la suposición del modelo de que el subsistema de disco puede realizar 320 operaciones de E / S aleatorias por segundo
...
el componente de costo es lo suficientemente inteligente como para reconocer que el número total de Las páginas que necesitan ser traídas del disco nunca pueden exceder el número de páginas requeridas para almacenar toda la tabla.

En mi caso, la tabla ocupa 5618 páginas y para obtener 1000 filas de 1000000 filas, el número estimado de páginas necesarias es 5.618, lo que da un costo IO de 0.015625.

El costo de CPU para ambas consultas parece ser el mismo 0.0001581 * 1000 executions = 0.1581,.

Entonces, según el artículo vinculado anteriormente, podemos calcular el costo de la primera consulta en 0.173725.

Y suponiendo que estoy en lo cierto acerca de cómo el escalar de cómputo está haciendo un desastre de IO Cost, se puede calcular a 3.2831.

No es exactamente lo que se muestra en los planes, pero está justo en el vecindario.


6

(Esto sería mejor como un comentario a la respuesta de Paul, pero todavía no tengo suficiente representante).

Quería proporcionar la lista de marcas de seguimiento (y un par de DBCCdeclaraciones) que solía llegar a una conclusión cercana, en caso de que sea útil investigar discrepancias similares en el futuro. Todo esto no debe usarse en la producción .

Primero, eché un vistazo al Memo Final para ver qué operadores físicos se estaban utilizando. Ciertamente se ven iguales de acuerdo con los planes de ejecución gráficos. Entonces, utilicé indicadores de seguimiento 3604y 8615, el primero dirige la salida al cliente y el segundo revela el Memo Final:

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN Target AS T
       ON T.KeyCol = S.KeyCol
OPTION(QUERYTRACEON 3604, -- Output client info
       QUERYTRACEON 8615, -- Shows Final Memo structure
       RECOMPILE);

Al rastrear desde Root Group, encontré estos PhyOp_Rangeoperadores casi idénticos :

  1. PhyOp_Range 1 ASC 2.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 0.175559(Distance = 2)
  2. PhyOp_Range 1 ASC 3.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 3.01702(Distance = 2)

La única diferencia obvia para mí fue el 2.0y 3.0, que se refieren a sus respectivos "memo grupo 2, original" y "memo grupo 3, original". Al revisar el memo, estos se refieren a lo mismo, por lo que aún no se han revelado diferencias.

En segundo lugar, examiné todo un desorden de indicadores de trazas que me resultaron infructuosos, pero que tienen contenido interesante. Levanté la mayoría de Benjamin Nevarez . Estaba buscando pistas sobre las reglas de optimización que se aplicaron en un caso y no en el otro.

 SELECT S.*, T.KeyCol
 FROM Staging AS S
      LEFT OUTER JOIN Target AS T
        ON T.KeyCol = S.KeyCol
 OPTION (QUERYTRACEON 3604, -- Output info to client
         QUERYTRACEON 2363, -- Show stats and cardinality info
         QUERYTRACEON 8675, -- Show optimization process info
         QUERYTRACEON 8606, -- Show logical query trees
         QUERYTRACEON 8607, -- Show physical query tree
         QUERYTRACEON 2372, -- Show memory utilization info for optimization stages 
         QUERYTRACEON 2373, -- Show memory utilization info for applying rules
         RECOMPILE );

Tercero, miré qué reglas se aplicaron para nuestros PhyOp_Rangecorreos electrónicos que se parecen tanto. Usé un par de banderas de seguimiento mencionadas por Paul en una publicación de blog .

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN (SELECT KeyCol
                      FROM Target) AS T
       ON T.KeyCol = S.KeyCol
OPTION (QUERYTRACEON 3604, -- Output info to client
        QUERYTRACEON 8619, -- Show applied optimization rules
        QUERYTRACEON 8620, -- Show rule-to-memo info
        QUERYTRACEON 8621, -- Show resulting tree
        QUERYTRACEON 2398, -- Show "smart seek costing"
        RECOMPILE );

Desde la salida, vemos que el directa- JOINaplica esta regla para conseguir nuestra PhyOp_Rangeoperador: Rule Result: group=7 2 <SelIdxToRng>PhyOp_Range 1 ASC 2 (Distance = 2). La subselección aplica esta regla en su lugar: Rule Result: group=9 2 <SelToIdxStrategy>PhyOp_Range 1 ASC 3 (Distance = 2). Aquí también es donde ve la información de "costo de búsqueda inteligente" asociada con cada regla. Para el directa- JOINesta es la salida (para mí): Smart seek costing (7.2) :: 1.34078e+154 , 0.001. Para la subselección, este es el resultado: Smart seek costing (9.2) :: 1.34078e+154 , 1.

Al final, no pude concluir mucho, pero la respuesta de Paul cierra la mayor parte de la brecha. Me gustaría ver más información sobre el costo de búsqueda inteligente.


4

Esto tampoco es realmente una respuesta: como señaló Mikael, es difícil discutir este problema en los comentarios ...

Curiosamente, si convierte la subconsulta (select KeyCol FROM Target)en un TVF en línea, verá que el plan y sus costos son los mismos que la consulta original simple:

CREATE FUNCTION dbo.cs_test()
RETURNS TABLE
WITH SCHEMABINDING
AS 
RETURN (
    SELECT KeyCol FROM dbo.Target
    );

/* "normal" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN Target AS T ON T.KeyCol = S.KeyCol;

/* "subquery" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (SELECT KeyCol FROM Target) AS T ON T.KeyCol = S.KeyCol;

/* "inline-TVF" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN dbo.cs_test() t ON s.KeyCol = t.Keycol

Los planes de consulta ( enlace pegarplan ):

ingrese la descripción de la imagen aquí

La deducción me lleva a creer que el motor de costos está confundido sobre el impacto potencial que puede tener este tipo de subconsulta .

Tomemos, por ejemplo, lo siguiente:

SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (
        SELECT KeyCol = CHECKSUM(NEWID()) 
        FROM Target
        ) AS T ON T.KeyCol = S.KeyCol;

¿Cómo le costará eso? El optimizador de consultas elige un plan muy similar a la variante "subconsulta" anterior, que contiene un escalar de cómputo ( enlace pastetheplan.com ):

ingrese la descripción de la imagen aquí

El escalar de cálculo tiene un costo bastante diferente de la variante de "subconsulta" que se muestra arriba, sin embargo, sigue siendo una suposición, ya que el optimizador de consultas no tiene forma de saber, a priori, cuál podría ser el número de filas devueltas. El plan utiliza una coincidencia hash para la unión externa izquierda, ya que las estimaciones de las filas no se pueden conocer y, por lo tanto, se establecen en el número de filas en la tabla de destino.

ingrese la descripción de la imagen aquí

No tengo una gran conclusión de esto, excepto que estoy de acuerdo con el trabajo que hizo Mikael en su respuesta, y espero que alguien más pueda encontrar una mejor respuesta.

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.