¿Es útil el operador de spool ansioso para esta eliminación de un almacén de columnas en clúster?


28

Estoy probando la eliminación de datos de un índice de almacén de columnas agrupado.

Noté que hay un gran operador de spool ansioso en el plan de ejecución:

ingrese la descripción de la imagen aquí

Esto se completa con las siguientes características:

  • 60 millones de filas eliminadas
  • 1.9 GiB TempDB utilizado
  • 14 minutos de tiempo de ejecución
  • Plan de serie
  • 1 rebobinado en carrete
  • Costo estimado de escaneo: 364.821

Si engaño al estimador para que subestime, obtengo un plan más rápido que evita el uso de TempDB:

ingrese la descripción de la imagen aquí

Costo estimado de escaneo: 56.901

(Este es un plan estimado, pero los números en los comentarios son correctos).

Curiosamente, el carrete desaparece nuevamente si elimino las tiendas delta ejecutando lo siguiente:

ALTER INDEX IX_Clustered ON Fact.RecordedMetricsDetail REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);

Parece que el spool solo se introduce cuando hay más de un umbral de páginas en las tiendas delta.

Para verificar el tamaño de las tiendas delta, estoy ejecutando la siguiente consulta para verificar las páginas en fila para la tabla:

SELECT  
  SUM([in_row_used_page_count]) AS in_row_used_pages,
  SUM(in_row_data_page_count) AS in_row_data_pages
FROM sys.[dm_db_partition_stats] as pstats
JOIN sys.partitions AS p
ON pstats.partition_id = p.partition_id
WHERE p.[object_id] = OBJECT_ID('Fact.RecordedMetricsDetail');

¿Hay algún beneficio plausible para el iterador de spool en el primer plan? Tengo que asumir que está destinado a mejorar el rendimiento y no a la protección de Halloween porque su presencia no es consistente.

Estoy probando esto en 2016 CTP 3.1, pero veo el mismo comportamiento en 2014 SP1 CU3.

Publiqué un script que genera esquemas y datos y lo guía para demostrar el problema aquí .

La pregunta es principalmente por curiosidad sobre el comportamiento del optimizador en este momento, ya que tengo una solución para el problema que provocó la pregunta (un gran carrete TempDB lleno). Ahora estoy eliminando mediante el cambio de partición en su lugar.


2
Si lo intento, OPTION (QUERYRULEOFF EnforceHPandAccCard)el carrete desaparece. Supongo que HP podría ser "Protección de Halloween". Sin embargo, tratar de usar ese plan con una USE PLANpista falla (al igual que intentar usar el plan de la OPTIMIZE FOR solución alternativa también)
Martin Smith

Gracias @MartinSmith. ¿Alguna idea de lo AccCardque sería? ¿Columna ascendente cardinalidad cardinalidad quizás?
James L

1
@JamesLupolt No, no se me ocurrió nada particularmente convincente para mí. ¿Quizás el Acc es Acumulado o Acceso?
Martin Smith

Respuestas:


22

¿Hay algún beneficio plausible para el iterador de spool en el primer plan?

Esto depende de lo que considere "plausible", pero la respuesta según el modelo de costos es sí. Por supuesto, esto es cierto, porque el optimizador siempre elige el plan más barato que encuentra.

La verdadera pregunta es por qué el modelo de costos considera que el plan con el carrete es mucho más barato que el plan sin él. Considere los planes estimados creados para una tabla nueva (de su secuencia de comandos) antes de que se agreguen filas a la tienda delta:

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE);

El costo estimado de este plan es de 771,734 unidades enormes :

Plan original

El costo está casi todo asociado con la Eliminación de índice agrupado, porque se espera que las eliminaciones den como resultado una gran cantidad de E / S aleatorias. Esta es solo la lógica genérica que se aplica a todas las modificaciones de datos. Por ejemplo, se supone que un conjunto desordenado de modificaciones a un índice b-tree da como resultado E / S en gran medida aleatorias, con un alto costo de E / S asociado.

Los planes de cambio de datos pueden presentar una Clasificación para presentar filas en un orden que promoverá el acceso secuencial, exactamente por estos motivos de costo. El impacto se exacerba en este caso porque la tabla está particionada. Muy particionado, de hecho; su script crea 15,000 de ellos. Las actualizaciones aleatorias de una tabla muy particionada tienen un costo especialmente alto ya que el precio para cambiar particiones (conjuntos de filas) a mitad de flujo también tiene un alto costo.

El último factor importante a tener en cuenta es que la consulta de actualización simple anterior (donde 'actualizar' significa cualquier operación de cambio de datos, incluida una eliminación) califica para una optimización llamada "intercambio de conjuntos de filas", donde se utiliza el mismo conjunto de filas interno para el escaneo y actualizando la mesa. El plan de ejecución aún muestra dos operadores separados, pero no obstante, solo se utiliza un conjunto de filas.

Menciono esto porque poder aplicar esta optimización significa que el optimizador toma una ruta de código que simplemente no considera los beneficios potenciales de la clasificación explícita para reducir el costo de E / S aleatorias. Cuando la tabla es un árbol b, esto tiene sentido, porque la estructura está inherentemente ordenada, por lo que compartir el conjunto de filas proporciona automáticamente todos los beneficios potenciales.

La consecuencia importante es que la lógica de costeo para el operador de actualización no considera este beneficio de ordenación (promoción de E / S secuenciales u otras optimizaciones) donde el objeto subyacente es el almacén de columnas. Esto se debe a que las modificaciones del almacén de columnas no se realizan en el lugar; usan una tienda delta. Por lo tanto, el modelo de costos refleja una diferencia entre las actualizaciones de conjuntos de filas compartidas en b-trees versus los almacenes de columnas.

Sin embargo, en el caso especial de un almacén de columnas particionado (¡muy!), Podría haber un beneficio en el orden conservado, ya que realizar todas las actualizaciones a una partición antes de pasar a la siguiente podría ser ventajoso desde el punto de vista de E / S .

Aquí se reutiliza la lógica de costo estándar para los almacenes de columnas, por lo que un plan que conserva el orden de la partición (aunque no el orden dentro de cada partición) tiene un costo menor. Podemos ver esto en la consulta de prueba usando el indicador de rastreo no documentado 2332 para requerir una entrada ordenada al operador de actualización. Esto establece la DMLRequestSortpropiedad en verdadero en la actualización y obliga al optimizador a producir un plan que proporcione todas las filas para una partición antes de pasar a la siguiente:

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332);

El costo estimado para este plan es mucho más bajo, con 52.5174 unidades:

DMLRequestSort = plan verdadero

Esta reducción en el costo se debe al menor costo estimado de E / S en la actualización. El Spool introducido no realiza ninguna función útil, excepto que puede garantizar la salida en orden de partición, como lo requiere la actualización con DMLRequestSort = true(el escaneo en serie de un índice de almacén de columnas no puede proporcionar esta garantía). El costo del carrete en sí mismo se considera relativamente bajo, especialmente en comparación con la reducción (probablemente poco realista) del costo en la actualización.

La decisión sobre si se requiere una entrada ordenada al operador de actualización se toma muy pronto en la optimización de consultas. Las heurísticas utilizadas en esta decisión nunca se han documentado, pero se pueden determinar mediante prueba y error. Parece que el tamaño de cualquier tienda delta es una entrada a esta decisión. Una vez realizada, la elección es permanente para la compilación de consultas. Ninguna USE PLANpista tendrá éxito: el objetivo del plan ha ordenado la entrada a la actualización o no.

Hay otra forma de obtener un plan de bajo costo para esta consulta sin limitar artificialmente la estimación de cardinalidad. Una estimación suficientemente baja para evitar el Spool probablemente dará como resultado que DMLRequestSort sea falso, lo que resultará en un costo de plan estimado muy alto debido a la E / S aleatoria esperada. Una alternativa es utilizar el indicador de traza 8649 (plan paralelo) junto con 2332 (DMLRequestSort = true):

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332, QUERYTRACEON 8649);

Esto da como resultado un plan que utiliza la exploración paralela en modo por lotes por partición y un intercambio de recopilación de secuencias de preservación de orden (fusión):

Eliminar ordenado

Dependiendo de la efectividad en tiempo de ejecución del pedido de particiones en su hardware, esto puede funcionar mejor de los tres. Dicho esto, las grandes modificaciones no son una gran idea en el almacén de columnas, por lo que la idea de cambio de partición es casi seguramente mejor. Si puede hacer frente a los largos tiempos de compilación y las elecciones de planes extravagantes que a menudo se ven con objetos particionados, especialmente cuando el número de particiones es grande.

La combinación de muchas características relativamente nuevas, especialmente cerca de sus límites, es una excelente manera de obtener planes de ejecución deficientes. La profundidad del soporte del optimizador tiende a mejorar con el tiempo, pero el uso de 15,000 particiones del almacén de columnas probablemente siempre significará que vives en tiempos interesantes.

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.