Las estadísticas desaparecen después de la actualización incremental


21

Tenemos una gran base de datos particionada de SQL Server que utiliza estadísticas incrementales. Todos los índices están alineados con particiones. Cuando intentamos reconstruir una partición en línea por partición, todas las estadísticas desaparecen después de que se reconstruye el índice.

A continuación se muestra un script para replicar el problema en SQL Server 2014 con la base de datos AdventureWorks2014.

--Example against AdventureWorks2014 Database

CREATE PARTITION FUNCTION TransactionRangePF1 (DATETIME)
AS RANGE RIGHT FOR VALUES 
(
   '20130501', '20130601', '20130701', '20130801', 
   '20130901', '20131001', '20131101', '20131201', 
   '20140101', '20140201', '20140301'
);
GO

CREATE PARTITION SCHEME TransactionsPS1 AS PARTITION TransactionRangePF1 TO 
(
  [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], 
  [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], 
  [PRIMARY], [PRIMARY], [PRIMARY]
);
GO

CREATE TABLE dbo.TransactionHistory 
(
  TransactionID        INT      NOT NULL, -- not bothering with IDENTITY here
  ProductID            INT      NOT NULL,
  ReferenceOrderID     INT      NOT NULL,
  ReferenceOrderLineID INT      NOT NULL DEFAULT (0),
  TransactionDate      DATETIME NOT NULL DEFAULT (GETDATE()),
  TransactionType      NCHAR(1) NOT NULL,
  Quantity             INT      NOT NULL,
  ActualCost           MONEY    NOT NULL,
  ModifiedDate         DATETIME NOT NULL DEFAULT (GETDATE()),
  CONSTRAINT CK_TransactionType 
    CHECK (UPPER(TransactionType) IN (N'W', N'S', N'P'))
) 
ON TransactionsPS1 (TransactionDate);


INSERT INTO dbo.TransactionHistory
SELECT * FROM Production.TransactionHistory
--  SELECT * FROM sys.partitions
--  WHERE object_id = OBJECT_ID('dbo.TransactionHistory');

CREATE NONCLUSTERED INDEX IDX_ProductId ON dbo.TransactionHistory (ProductId) 
  WITH (DATA_COMPRESSION = ROW, STATISTICS_INCREMENTAL=ON)  
  ON TransactionsPS1 (TransactionDate)

DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);
PRINT 'Stats are avialable'  

ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = ON , DATA_COMPRESSION = ROW)

PRINT 'After online index rebuild by partition stats are now gone'
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

PRINT 'Rebuild the stats with a rebuild for all paritions (this works)' 
ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = ALL WITH (ONLINE = ON , DATA_COMPRESSION = ROW, 
  STATISTICS_INCREMENTAL = ON)

PRINT 'Stats are back'
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

PRINT 'Works correctly for an offline rebuild by partition'
ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = OFF , DATA_COMPRESSION = ROW)

    --stats still there  
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = ON , DATA_COMPRESSION = ROW)

DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);
PRINT' stats are gone!!!!!!'

Como se muestra, no podemos reconstruir índices por partición en línea sin perder todas las estadísticas del índice. Este es un problema de mantenimiento importante para nosotros. Casi parece que la opción incremental de estadísticas debe ser parte de la sintaxis de reconstrucción de índice único o la opción en línea debe manejarla correctamente como lo hace la opción fuera de línea.

Por favor, avíseme si me falta algo.

Actualizaciones:

En cuanto a nuestra necesidad de estadísticas incrementales: estamos particionando en una identificación de cliente interna y no en una fecha. Entonces, cuando ingresa un nuevo cliente (gran carga de datos), simplemente podemos actualizar las estadísticas de la partición y evitar rápidamente que se creen planes feos para este nuevo cliente. Creo que lo archivaré con Microsoft como un error y veré lo que tienen que decir e iré con la solución de simplemente volver a muestrear las estadísticas para esa partición.

Informe de error de conexión:

Las estadísticas desaparecen después de la reconstrucción del índice en línea con estadísticas incrementales

Actualización: Microsoft ha confirmado que es un error.


1
Actualización: Microsoft me envió un correo electrónico esta mañana
diciendo

¿sabe qué CU lo arreglaron o cuál es el KB que informaron en ese correo electrónico? Intentando ver cuándo se solucionó.
mbourgon

1
Estoy bastante seguro de que es el número de error VSTS 8046729 KB, número de artículo 3194959, que formaba parte de CU 9 para SQL Server 2014 SP1. Un enlace a la KB está aquí .
JasonR

Sí, eso parece, y se solucionó el 2016SP1 el mes pasado. ¡Muchas muchas gracias!
mbourgon

Corrección: solo corregido en 2016 SP1 CU2. Sucede en 2016 SP1 CU1.
mbourgon

Respuestas:


17

No estoy seguro de si es un error, per se, pero definitivamente es un hecho interesante. Las reconstrucciones de particiones en línea son nuevas en SQL Server 2014, por lo que puede haber algunas partes internas para resolver esto.

Aquí está mi mejor explicación para ti. Las estadísticas incrementales requieren que todas las particiones se muestreen a la misma velocidad, de modo que cuando el motor fusiona las páginas de estadísticas, puede estar seguro de que la distribución muestreada es comparable. REBUILDnecesariamente muestrea datos a una frecuencia de muestreo del 100%. No hay garantía de que la frecuencia de muestreo del 100% en la partición 9 siempre sea la frecuencia de muestreo exacta del resto de las particiones. Debido a esto, parece que el motor no puede fusionar las muestras y terminas con un blob de estadísticas vacío. Sin embargo, el objeto de estadísticas sigue ahí:

select 
    check_time = sysdatetime(),                         
    schema_name = sh.name,
    table_name = t.name,
    stat_name = s.name,
    index_name = i.name,
    stats_column = index_col(quotename(sh.name)+'.'+quotename(t.name),s.stats_id,1),
    s.stats_id,
    s.has_filter,                       
    s.is_incremental,
    s.auto_created,
    sp.last_updated,    
    sp.rows,
    sp.rows_sampled,                        
    sp.unfiltered_rows,
    modification_counter 
from sys.stats s 
join sys.tables t 
    on s.object_id = t.object_id
join sys.schemas sh
    on t.schema_id = sh.schema_id
left join sys.indexes i 
    on s.object_id = i.object_id
    and s.name = i.name
outer apply sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
where t.name = 'TransactionHistory' and sh.name = 'dbo'

Puede llenar el blob a través de cualquier cantidad de medios:

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE;

o

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE ON PARTITIONS (9);

o puede esperar a que AutoStats se actualice en la primera compilación de un plan de consulta con ese objeto:

-- look at my creative query
select * 
from dbo.TransactionHistory
where TransactionDate = '20140101';

Habiendo dicho todo eso, esta publicación esclarecedora de Erin Stellato destaca lo que se percibe como una deficiencia importante de las estadísticas incrementales. El optimizador no utiliza sus datos a nivel de partición en la generación del plan de consulta, lo que reduce el supuesto beneficio de las estadísticas incrementales. ¿Cuál es, entonces, el beneficio actual de las estadísticas incrementales? Diría que su utilidad principal es la capacidad de muestrear tablas grandes de manera más consistente a una tasa más alta que con las estadísticas tradicionales.

Usando su ejemplo, así es como se ven las cosas:

set statistics time on;

update statistics dbo.TransactionHistory(IDX_ProductId)
with fullscan;

--SQL Server Execution Times:
--  CPU time = 94 ms,  elapsed time = 131 ms.


update statistics dbo.TransactionHistory(IDX_ProductId)
with resample on partitions(2);

 --SQL Server Execution Times:
 --  CPU time = 0 ms,  elapsed time = 5 ms.

drop index IDX_ProductId On dbo.TransactionHistory;

CREATE NONCLUSTERED INDEX IDX_ProductId ON dbo.TransactionHistory (ProductId) 
  WITH (DATA_COMPRESSION = ROW)  
  ON [PRIMARY]

update statistics dbo.TransactionHistory(IDX_ProductId)
with fullscan;

 --SQL Server Execution Times:
 --  CPU time = 76 ms,  elapsed time = 66 ms.

Una actualización de estadísticas de exploración completa sobre los costos estadísticos incrementales de 131 ms. Una actualización de estadísticas de exploración completa en la estadística sin alineación de partición cuesta 66 ms. La estadística no alineada es más lenta probablemente debido a la sobrecarga incurrida al fusionar las páginas de estadísticas individuales nuevamente en el histograma principal. Sin embargo, usando el objeto estadístico alineado con la partición, podemos actualizar una partición y fusionarla nuevamente en el blob de histograma principal en 5 ms. Entonces, en este punto, el administrador con la estadística incremental se enfrenta a una decisión. Pueden disminuir el tiempo general de mantenimiento de las estadísticas solo actualizando las particiones que tradicionalmente tendrían que actualizarse, o pueden experimentar con frecuencias de muestreo más altas de modo que potencialmente obtengan más filas muestreadas en el mismo período de tiempo que su período de mantenimiento anterior. El primero permite respirar en la ventana de mantenimiento, el segundo puede llevar las estadísticas en una tabla muy grande a un lugar donde las consultas obtengan mejores planes basados ​​en estadísticas más precisas. Esto no es una garantía y su kilometraje puede variar.

El lector puede ver que 66 ms no es un tiempo de actualización de estadísticas doloroso en esta tabla, así que intenté configurar una prueba en el conjunto de datos de stackexchange. Hay 6.418.608 publicaciones (excluyendo las publicaciones de StackOverflow y todas las publicaciones de 2012, un error de datos por mi parte) en el volcado reciente que descargué.

He particionado los datos por [CreationDate]... demo.

Aquí hay algunos tiempos para algunos escenarios bastante estándar (100% - reconstrucción de índice, predeterminado - actualización automática de estadísticas o UPDATE STATISTICSsin una frecuencia de muestreo especificada:

  • Crear estadística no incremental con Fullscan: tiempo de CPU = 23500 ms, tiempo transcurrido = 22521 ms.
  • Crear estadística incremental con exploración completa: tiempo de CPU = 20406 ms, tiempo transcurrido = 15413 ms.
  • Actualizar estadística no incremental con frecuencia de muestreo predeterminada: tiempo de CPU = 406 ms, tiempo transcurrido = 408 ms.
  • Actualizar estadística incremental con frecuencia de muestreo predeterminada: tiempo de CPU = 453 ms, tiempo transcurrido = 507 ms.

Supongamos que somos más sofisticados que estos escenarios predeterminados y hemos decidido que una tasa de muestra del 10% es la tasa mínima que debería proporcionarnos los planes que necesitamos y al mismo tiempo mantener el tiempo de mantenimiento en un plazo razonable.

  • Actualice la estadística no incremental con un 10 por ciento de muestra: tiempo de CPU = 2344 ms, tiempo transcurrido = 2441 ms.
  • Actualice la estadística incremental con una muestra del 10 por ciento: tiempo de CPU = 2344 ms, tiempo transcurrido = 2388 ms.

Hasta ahora no hay un beneficio claro de tener una estadística incremental. Sin embargo, si aprovechamos el DMV indocumentado sys.dm_db_stats_properties_internal() (a continuación), puede obtener una idea de qué partición (es) desea actualizar. Digamos que hicimos cambios en los datos en la partición 3 y queremos asegurarnos de que las estadísticas estén actualizadas para las consultas entrantes. Aquí están nuestras opciones:

  • Actualización no incremental por defecto (también el comportamiento predeterminado de la actualización automática de estadísticas): 408 ms.
  • Actualización no incremental al 10%: 2441 ms.
  • Actualice las estadísticas incrementales, partición 3 con remuestreo (10% - nuestra frecuencia de muestreo definida): tiempo de CPU = 63 ms, tiempo transcurrido = 63 ms.

Aquí es donde debemos tomar una decisión. ¿Nos llevamos la victoria de un 63 ms. actualización de estadísticas basada en particiones, ¿o aumentamos aún más la frecuencia de muestreo? Digamos que estamos dispuestos a tomar el golpe inicial de muestreo al 50% en una estadística incremental:

  • Actualizar estadísticas incrementales al 50%: tiempo transcurrido = 16840 ms.
  • Actualizar estadísticas incrementales, partición 3 con remuestreo (50% - nuestro nuevo tiempo de actualización): tiempo transcurrido = 295 ms.

Podemos muestrear muchos más datos, tal vez configurando el optimizador para hacer mejores conjeturas sobre nuestros datos (aunque todavía no está usando estadísticas a nivel de partición) y podemos hacerlo más rápido ahora que tenemos estadísticas incrementales

Sin embargo, una última cosa divertida para resolver. ¿Qué pasa con las actualizaciones de estadísticas sincrónicas? ¿Se conserva la frecuencia de muestreo del 50% incluso cuando se inician los autostats?

Eliminé datos de la partición 3 y ejecuté una consulta en CreationDate y verifiqué y luego verifiqué las tasas con la misma consulta a continuación. La tasa de muestra del 50% se conservó.

Entonces, en resumen: las estadísticas incrementales pueden ser una herramienta útil con la cantidad correcta de pensamiento y el trabajo de configuración inicial. Sin embargo, debe conocer el problema que está tratando de resolver y luego debe resolverlo adecuadamente. Si obtiene estimaciones de cardinalidad incorrectas, es posible que pueda obtener mejores planes con una tasa de muestreo estratégica y alguna intervención invertida. Sin embargo, solo está obteniendo una pequeña porción del beneficio ya que el histograma que se usa es la página de estadísticas combinadas y no la información de nivel de partición. Si siente dolor en su ventana de mantenimiento, entonces quizás las estadísticas incrementales pueden ayudarlo, pero probablemente requerirá que configure un proceso de intervención de mantenimiento de alto contacto. Independientemente,:

  • Estadísticas creadas con índices que no están alineados con la partición de la tabla base.
  • Estadísticas creadas en bases de datos secundarias legibles AlwaysOn.
  • Estadísticas creadas en bases de datos de solo lectura.
  • Estadísticas creadas en índices filtrados.
  • Estadísticas creadas en vistas.
  • Estadísticas creadas en tablas internas.
  • Estadísticas creadas con índices espaciales o índices XML.

Espero que esto ayude

select 
    sysdatetime(),                          
    schema_name = sh.name,
    table_name = t.name,
    stat_name = s.name,
    index_name = i.name,
    leading_column = index_col(quotename(sh.name)+'.'+quotename(t.name),s.stats_id,1),
    s.stats_id,
    parition_number = isnull(sp.partition_number,1),
    s.has_filter,                       
    s.is_incremental,
    s.auto_created,
    sp.last_updated,    
    sp.rows,
    sp.rows_sampled,                        
    sp.unfiltered_rows,
    modification_counter = coalesce(sp.modification_counter, n1.modification_counter) 
from sys.stats s 
join sys.tables t 
    on s.object_id = t.object_id
join sys.schemas sh
    on t.schema_id = sh.schema_id
left join sys.indexes i 
    on s.object_id = i.object_id
        and s.name = i.name
cross apply sys.dm_db_stats_properties_internal(s.object_id, s.stats_id) sp
outer apply sys.dm_db_stats_properties_internal(s.object_id, s.stats_id) n1
where n1.node_id = 1
    and (
            (is_incremental = 0)
               or
            (is_incremental = 1 and sp.partition_number is not null)
         )
    and t.name = 'Posts'
    and s.name like 'st_posts%'
order by s.stats_id,isnull(sp.partition_number,1)
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.