Le statistiche scompaiono dopo l'aggiornamento incrementale


21

Disponiamo di un ampio database SQL Server partizionato che utilizza statistiche incrementali. Tutti gli indici sono partizionati allineati. Quando proviamo a ricostruire una partizione online per partizione, tutte le statistiche scompaiono dopo la ricostruzione dell'indice.

Di seguito è riportato uno script per replicare il problema in SQL Server 2014 con il database 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!!!!!!'

Come mostrato, non possiamo ricostruire gli indici per partizione online senza perdere tutte le statistiche per indice. Questo è un grosso problema di manutenzione per noi. Sembra quasi che l'opzione incrementale delle statistiche debba far parte della sintassi di ricostruzione dell'indice singolo o che l'opzione online debba gestirla correttamente come fa l'opzione offline.

Per favore fatemi sapere se mi manca qualcosa?

aggiornamenti:

Per quanto riguarda la nostra necessità di statistiche incrementali: stiamo partizionando su un ID cliente interno e non su una data. Quindi, quando viene introdotto un nuovo client (grande back -load di dati) possiamo semplicemente aggiornare le statistiche per la partizione ed evitare rapidamente che vengano creati brutti piani per questo nuovo cliente. Penso che lo archivierò con Microsoft come un bug e vedrò cosa hanno da dire e andremo con la soluzione di ricampionare le statistiche per quella partizione.

Connetti segnalazione bug:

Le statistiche scompaiono dopo la ricostruzione dell'indice online con statistiche incrementali

Aggiornamento: Microsoft ha confermato che si tratta di un bug.


1
Aggiornamento: Microsoft mi ha inviato un'email stamattina per correggere questo errore nel prossimo aggiornamento CU per SQL 2014.
JasonR,

sai quali CU hanno risolto il problema o quale KB è stato segnalato in quell'email? Prova di vedere quando è stato risolto.
mbourgon,

1
Abbastanza sicuro che sia il numero di bug VSTS 8046729 KB Numero articolo 3194959 che faceva parte di CU 9 per SQL Server 2014 SP1. Un link al KB è qui .
JasonR,

Sì, sembra proprio così - ed è stato appena corretto su 2016SP1 il mese scorso. Molte molte grazie!
mbourgon,

Correzione: appena risolto nel 2016 SP1 CU2. Succede nel 2016 SP1 CU1.
mbourgon,

Risposte:


17

Non so se si tratta di un bug, di per sé, ma è sicuramente un evento interessante. Le ricostruzioni delle partizioni online sono nuove in SQL Server 2014, quindi potrebbero esserci alcuni interni da ordinare con questo.

Ecco la mia migliore spiegazione per te. Le statistiche incrementali richiedono assolutamente che tutte le partizioni vengano campionate alla stessa velocità in modo che quando il motore unisce le pagine delle statistiche possa essere sicuro che la distribuzione campionata sia comparabile. REBUILDcampiona necessariamente i dati con una frequenza di campionamento del 100%. Non vi è alcuna garanzia che la frequenza di campionamento del 100% sulla partizione 9 sarà sempre la frequenza di campionamento esatta del resto delle partizioni. Per questo motivo, sembra che il motore non possa unire i campioni e si ottiene un blob di statistiche vuoto. Tuttavia, l'oggetto statistico è ancora 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'

È possibile riempire il BLOB con qualsiasi numero di mezzi:

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE;

o

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

oppure puoi attendere che AutoStats si aggiorni alla prima compilazione di un piano di query utilizzando quell'oggetto:

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

Detto questo , questo post illuminante di Erin Stellato evidenzia ciò che è diventato percepito come una grave carenza di statistiche incrementali. I loro dati a livello di partizione non vengono utilizzati dall'ottimizzatore nella generazione del piano di query, riducendo il presunto vantaggio delle statistiche incrementali. Qual è, quindi, l'attuale vantaggio delle statistiche incrementali? Direi che la loro utilità principale è in grado di campionare tabelle di grandi dimensioni in modo più coerente a un ritmo più elevato rispetto alle statistiche tradizionali.

Usando il tuo esempio, ecco come appaiono le cose:

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.

Un aggiornamento completo delle statistiche sulla statistica incrementale costa 131 ms. Un aggiornamento delle statistiche fullscan sulla statistica non allineata alla partizione costa 66 ms. La statistica non allineata è molto più lenta molto probabilmente a causa del sovraccarico dovuto alla fusione delle singole pagine statistiche nell'istogramma principale. Tuttavia, utilizzando l'oggetto statistico allineato alle partizioni, è possibile aggiornare una partizione e fonderla nuovamente nel BLOB dell'istogramma principale in 5 ms. Quindi a questo punto l'amministratore con la statistica incrementale deve affrontare una decisione. Possono ridurre i tempi di manutenzione delle statistiche complessive aggiornando solo le partizioni che tradizionalmente dovrebbero essere aggiornate, oppure possono sperimentare frequenze di campionamento più elevate in modo tale da poter campionare più righe nello stesso periodo di tempo del periodo di manutenzione precedente. Il primo consente di respirare nella finestra di manutenzione, il secondo potrebbe spingere le statistiche su un tavolo molto grande in un luogo in cui le query ottengono piani migliori basati su statistiche più accurate. Questa non è una garanzia e il tuo chilometraggio può variare.

Il lettore può vedere che 66 ms non è un doloroso tempo di aggiornamento delle statistiche su questa tabella, quindi ho provato a impostare un test sul set di dati stackexchange. Ci sono 6.418.608 post (esclusi i post StackOverflow e tutti i post del 2012 - un errore di dati da parte mia) nel recente dump che ho scaricato.

Ho partizionato i dati [CreationDate]perché ... demo.

Ecco alcuni tempi per alcuni scenari piuttosto standard (100% - ricostruzione indice, impostazione predefinita - aggiornamento automatico delle statistiche o UPDATE STATISTICSsenza una frequenza di campionamento specificata:

  • Crea statistica non incrementale con FullScan: tempo CPU = 23500 ms, tempo trascorso = 22521 ms.
  • Crea statistica incrementale con FullScan: tempo CPU = 20406 ms, tempo trascorso = 15413 ms.
  • Aggiorna statistica non incrementale con frequenza di campionamento predefinita: tempo CPU = 406 ms, tempo trascorso = 408 ms.
  • Aggiorna statistica incrementale con frequenza di campionamento predefinita: tempo CPU = 453 ms, tempo trascorso = 507 ms.

Diciamo che siamo più sofisticati di questi scenari predefiniti e abbiamo deciso che una frequenza di campionamento del 10% è la frequenza minima che dovrebbe portarci i piani di cui abbiamo bisogno mantenendo i tempi di manutenzione a un lasso di tempo ragionevole.

  • Aggiorna statistica non incrementale con il 10 percento del campione: tempo CPU = 2344 ms, tempo trascorso = 2441 ms.
  • Aggiorna statistica incrementale con il 10 percento di esempio: tempo CPU = 2344 ms, tempo trascorso = 2388 ms.

Finora non ci sono chiari vantaggi nell'avere una statistica incrementale. Tuttavia, se sfruttiamo il DMV non documentato sys.dm_db_stats_properties_internal() (di seguito), puoi ottenere alcune informazioni su quali partizioni potresti voler aggiornare. Supponiamo di aver apportato modifiche ai dati nella partizione 3 e desideriamo assicurarci che le statistiche siano aggiornate per le query in arrivo. Ecco le nostre opzioni:

  • Aggiornamento non incrementale al valore predefinito (anche il comportamento predefinito dell'aggiornamento automatico delle statistiche): 408 ms.
  • Aggiornamento non incrementale al 10%: 2441 ms.
  • Aggiorna statistiche incrementali, partizione 3 con Resample (10% - la nostra frequenza di campionamento definita): tempo CPU = 63 ms, tempo trascorso = 63 ms.

Ecco dove dobbiamo prendere una decisione. Prendiamo la vittoria di un 63 ms. aggiornamento delle statistiche basato su partizioni o aumentiamo ulteriormente la frequenza di campionamento? Diciamo che siamo disposti a prendere il colpo iniziale di campionamento al 50% su una statistica incrementale:

  • Aggiorna statistiche incrementali al 50%: tempo trascorso = 16840 ms.
  • Aggiorna statistiche incrementali, partizione 3 con Resample (50% - il nostro nuovo tempo di aggiornamento): tempo trascorso = 295 ms.

Siamo in grado di campionare molti più dati, forse impostando l'ottimizzatore per fare ipotesi migliori sui nostri dati (anche se non utilizza ancora statistiche a livello di partizione) e siamo in grado di farlo più rapidamente ora che abbiamo statistiche incrementali.

Un'ultima cosa divertente da capire, però. Che dire degli aggiornamenti delle statistiche sincrone? La frequenza di campionamento del 50% viene preservata anche quando si avvia l'autostrada?

Ho cancellato i dati dalla partizione 3 ed eseguito una query su CreationDate e verificato, quindi ho controllato le tariffe con la stessa query di seguito. La frequenza di campionamento del 50% è stata preservata.

Quindi, per farla breve: le statistiche incrementali possono essere uno strumento utile con la giusta quantità di pensiero e il lavoro di impostazione iniziale. Tuttavia, è necessario conoscere il problema che si sta tentando di risolvere e quindi è necessario risolverlo in modo appropriato. Se stai ottenendo stime errate sulla cardinalità, potresti essere in grado di ottenere piani migliori con una frequenza di campionamento strategica e alcuni interventi investiti. Tuttavia, stai ottenendo solo una piccola parte del vantaggio poiché l'istogramma utilizzato è la singola pagina delle statistiche unita e non le informazioni a livello di partizione. Se senti dolore nella finestra di manutenzione, forse le statistiche incrementali possono aiutarti, ma probabilmente ti richiederà di impostare un processo di intervento di manutenzione ad alto tocco. Senza riguardo,:

  • Statistiche create con indici non allineati alle partizioni con la tabella di base.
  • Statistiche create su database secondari leggibili AlwaysOn.
  • Statistiche create su database di sola lettura.
  • Statistiche create su indici filtrati.
  • Statistiche create su viste.
  • Statistiche create su tabelle interne.
  • Statistiche create con indici spaziali o indici XML.

Spero che sia di aiuto

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.