As estatísticas desaparecem após a atualização incremental

21

Temos um grande banco de dados particionado do SQL Server utilizando estatísticas incrementais. Todos os índices são particionados alinhados. Quando tentamos reconstruir uma partição online por partição, todas as estatísticas desaparecem após a reconstrução do índice.

Abaixo está um script para replicar o problema no SQL Server 2014 com o banco de dados 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 mostrado, não podemos reconstruir índices partindo online sem perder todas as estatísticas do índice. Este é um grande problema de manutenção para nós. Parece quase que a opção incremental de estatísticas precisa fazer parte da sintaxe de reconstrução de índice único ou a opção online precisa lidar adequadamente com ela, como faz a opção offline.

Informe-me se estiver faltando alguma coisa?

Atualizações:

Quanto à nossa necessidade de estatísticas incrementais: estamos particionando em um ID de cliente interno e não em uma data. Portanto, quando um novo cliente é trazido (grande carregamento de dados), podemos simplesmente atualizar as estatísticas da partição e evitar rapidamente qualquer plano feio que esteja sendo criado para esse novo cliente. Acho que vou arquivá-lo na Microsoft como um bug e ver o que eles têm a dizer e seguir a solução de apenas re-amostrar as estatísticas dessa partição.

Conecte o relatório de erro:

As estatísticas desaparecem após a reconstrução do índice online com estatísticas incrementais

Atualização: A Microsoft confirmou que é um bug.

JasonR
fonte
1
Atualização: A Microsoft me enviou um e-mail esta manhã
informando
você sabe quais UCs ​​o corrigiram ou qual é a KB que eles relataram nesse email? Tentando ver quando foi consertado.
precisa saber é o seguinte
1
Certamente, é o número de bug do VSTS 8046729 KB Número do artigo 3194959 que fazia parte da CU 9 do SQL Server 2014 SP1. Um link para o KB está aqui .
precisa saber é o seguinte
Sim, parece - e foi corrigido no 2016SP1 no mês passado. Muitíssimo obrigado!
mbourgon
Correção: corrigida apenas em 2016 SP1 CU2. Isso acontece no 2016 SP1 CU1.
precisa saber é o seguinte

Respostas:

17

Não tenho certeza se é um bug, por si só, mas é definitivamente uma ocorrência interessante. As recriações de partições online são novas no SQL Server 2014, portanto, pode haver alguns elementos internos a serem resolvidos.

Aqui está a minha melhor explicação para você. As estatísticas incrementais exigem absolutamente que todas as partições sejam amostradas na mesma taxa, para que, quando o mecanismo mesclar as páginas de estatísticas, ele possa ter certeza de que a distribuição amostrada é comparável. REBUILDnecessariamente coleta dados a uma taxa de amostragem de 100%. Não há garantia de que a taxa de amostragem de 100% na partição 9 sempre seja a taxa de amostragem exata do restante das partições. Por esse motivo, parece que o mecanismo não pode mesclar as amostras e você acaba com um blob de estatísticas vazio. No entanto, o objeto de estatísticas ainda está lá:

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'

Você pode preencher o blob através de qualquer número de meios:

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE;

ou

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

ou você pode aguardar a atualização do AutoStats na primeira compilação de um plano de consulta usando esse objeto:

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

Dito tudo isso, este post esclarecedor de Erin Stellato destaca o que passou a ser percebido como uma grande deficiência de estatísticas incrementais. Seus dados no nível da partição não são utilizados pelo otimizador na geração do plano de consulta, reduzindo o benefício presumido de estatísticas incrementais. Qual é o benefício atual das estatísticas incrementais? Eu diria que o utilitário principal deles é capaz de amostrar tabelas grandes de maneira mais consistente a uma taxa mais alta do que com as estatísticas tradicionais.

Usando seu exemplo, veja como as coisas ficam:

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.

Uma atualização de estatísticas fullscan na estatística incremental custa 131 ms. Uma atualização de estatística fullscan na estatística não alinhada à partição custa 66 ms. A estatística não alinhada é mais lenta, provavelmente devido às despesas gerais resultantes da mesclagem das páginas de estatísticas individuais de volta ao histograma principal. No entanto, usando o objeto estatístico alinhado à partição, podemos atualizar uma partição e mesclá-la novamente no blob principal do histograma em 5 ms. Portanto, neste ponto, o administrador com a estatística incremental é confrontado com uma decisão. Eles podem diminuir o tempo geral de manutenção de estatísticas, apenas atualizando as partições que tradicionalmente precisariam ser atualizadas ou podem experimentar taxas de amostragem mais altas, de modo que possam obter mais linhas de amostra no mesmo período de tempo que o período de manutenção anterior. O primeiro permite espaço para respirar na janela de manutenção; o último pode enviar estatísticas em uma tabela muito grande para um local onde as consultas obtêm melhores planos com base em estatísticas mais precisas. Isso não é uma garantia e sua milhagem pode variar.

O leitor pode ver que 66 ms não é um momento doloroso de atualização estatística nesta tabela, então tentei configurar um teste no conjunto de dados stackexchange. Existem 6.418.608 postagens (excluindo as postagens do StackOverflow e todas as postagens de 2012 - um erro de dados da minha parte) no dump recente que baixei.

Particionei os dados [CreationDate]porque ... demo.

Aqui estão alguns horários para alguns cenários bastante padrão (100% - reconstrução do índice, padrão - atualização automática de estatísticas ou UPDATE STATISTICSsem uma taxa de amostragem especificada:

  • Crie estatística não incremental com o Fullscan: tempo da CPU = 23500 ms, tempo decorrido = 22521 ms.
  • Crie estatística incremental com a varredura completa: tempo da CPU = 20406 ms, tempo decorrido = 15413 ms.
  • Atualizar estatística não incremental com taxa de amostra padrão: tempo de CPU = 406 ms, tempo decorrido = 408 ms.
  • Atualizar estatística incremental com taxa de amostra padrão: tempo de CPU = 453 ms, tempo decorrido = 507 ms.

Digamos que somos mais sofisticados do que esses cenários padrão e decidimos que uma taxa de amostragem de 10% é a taxa mínima que deve fornecer os planos de que precisamos, mantendo o tempo de manutenção em um prazo razoável.

  • Atualize a estatística não incremental com amostra 10%: tempo da CPU = 2344 ms, tempo decorrido = 2441 ms.
  • Atualize a estatística incremental com amostra 10%: tempo da CPU = 2344 ms, tempo decorrido = 2388 ms.

Até o momento, não há benefício claro em ter uma estatística incremental. No entanto, se alavancarmos a DMV não documentada sys.dm_db_stats_properties_internal() (abaixo), você poderá obter algumas dicas sobre quais partições você deseja atualizar. Digamos que fizemos alterações nos dados na partição 3 e queremos garantir que as estatísticas sejam novas para as consultas recebidas. Aqui estão as nossas opções:

  • Atualizar não incremental no padrão (também o comportamento padrão da atualização de estatísticas automáticas): 408 ms.
  • Atualizar não incremental a 10%: 2441 ms.
  • Atualizar estatísticas incrementais, partição 3 com reamostragem (10% - nossa taxa de amostragem definida): tempo da CPU = 63 ms, tempo decorrido = 63 ms.

Aqui é onde precisamos tomar uma decisão. Tomamos a vitória de 63 ms. atualização de estatísticas baseada em partição ou aumentamos ainda mais a taxa de amostragem? Digamos que estamos dispostos a considerar o resultado inicial da amostragem em 50% em uma estatística incremental:

  • Atualizar estatísticas incrementais em 50%: tempo decorrido = 16840 ms.
  • Atualizar estatísticas incrementais, partição 3 com reamostragem (50% - nosso novo tempo de atualização): tempo decorrido = 295 ms.

Podemos coletar muito mais dados, talvez configurando o otimizador para adivinhar melhor nossos dados (mesmo que ainda não esteja usando estatísticas no nível da partição) e podemos fazer isso mais rapidamente agora que temos estatísticas incrementais.

Uma última coisa divertida para descobrir, no entanto. E as atualizações de estatísticas síncronas? A taxa de amostragem de 50% é preservada mesmo quando os autostats entram em ação?

Excluí os dados da partição 3 e executei uma consulta no CreationDate e verifiquei e verifiquei as taxas com a mesma consulta abaixo. A taxa de amostragem de 50% foi preservada.

Então, para encurtar a história: Estatísticas Incrementais podem ser uma ferramenta útil com a quantidade certa de pensamento e trabalho de configuração inicial. No entanto, você deve conhecer o problema que está tentando resolver e, em seguida, precisa resolvê-lo adequadamente. Se você está recebendo estimativas de cardinalidade ruins, você pode ser capaz de obter melhores planos com uma taxa de amostragem estratégica e alguma intervenção investido. No entanto, você está obtendo apenas uma pequena parte do benefício, pois o histograma usado é a única página de estatísticas mesclada e não as informações no nível da partição. Se você sentir dor na janela de manutenção, talvez as estatísticas incrementais possam ajudá-lo, mas provavelmente exigirá que você configure um processo de intervenção de manutenção de alto nível. Independentemente disso, lembre-se de:

  • Estatísticas criadas com índices que não estão alinhados pela partição com a tabela base.
  • Estatísticas criadas em bancos de dados secundários legíveis AlwaysOn.
  • Estatísticas criadas em bancos de dados somente leitura.
  • Estatísticas criadas em índices filtrados.
  • Estatísticas criadas em visualizações.
  • Estatísticas criadas em tabelas internas.
  • Estatísticas criadas com índices espaciais ou índices XML.

Espero que isto ajude

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)
swasheck
fonte