Les statistiques disparaissent après une mise à jour incrémentielle


21

Nous avons une grande base de données SQL Server partitionnée utilisant des statistiques incrémentielles. Tous les index sont partitionnés alignés. Lorsque nous essayons de reconstruire une partition en ligne par partition, toutes les statistiques disparaissent après la reconstruction de l'index.

Vous trouverez ci-dessous un script pour répliquer le problème dans SQL Server 2014 avec la base de données 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!!!!!!'

Comme indiqué, nous ne pouvons pas reconstruire les index par partition en ligne sans perdre toutes les statistiques de l'index. Il s'agit d'un problème de maintenance majeur pour nous. Il semble presque que l'option incrémentielle de statistiques doit faire partie de la syntaxe de reconstruction d'index unique ou que l'option en ligne doit la gérer correctement comme le fait l'option hors ligne.

Veuillez me faire savoir si je manque quelque chose?

Mises à jour:

En ce qui concerne notre besoin de statistiques incrémentielles: nous partitionnons sur un identifiant client interne et non sur une date. Donc, lorsqu'un nouveau client est amené (grande charge de données), nous pouvons simplement mettre à jour les statistiques de la partition et éviter rapidement la création de plans laids pour ce nouveau client. Je pense que je vais le déposer avec Microsoft en tant que bogue et voir ce qu'ils ont à dire et aller avec la solution de simplement rééchantillonner les statistiques de cette partition.

Rapport de bogue de connexion:

Les statistiques disparaissent après la reconstruction de l'index en ligne avec des statistiques incrémentielles

Mise à jour: Microsoft a confirmé qu'il s'agit d'un bogue.


1
Mise à jour: Microsoft m'a envoyé un e-mail ce matin indiquant que ce bogue sera corrigé dans la prochaine mise à jour de CU pour SQL 2014.
JasonR

savez-vous quelles UC ont résolu le problème, ou qu'est-ce que la base de connaissances qu'elles ont signalé dans cet e-mail? Essayer de voir quand il a été corrigé.
mbourgon

1
Je suis sûr que c'est le numéro de bogue VSTS 8046729 KB Numéro d'article 3194959 qui faisait partie de CU 9 pour SQL Server 2014 SP1. Un lien vers la base de connaissances est ici .
JasonR

Ouais, ça y ressemble - et vient d'être corrigé sur 2016SP1 le mois dernier. Merci beaucoup!
mbourgon

Correction: juste corrigé dans 2016 SP1 CU2. Cela se produit sur 2016 SP1 CU1.
mbourgon

Réponses:


17

Je ne sais pas si c'est un bug, en soi mais c'est certainement un événement intéressant. Les reconstructions de partition en ligne sont nouvelles dans SQL Server 2014, il peut donc y avoir des éléments internes à trier avec cela.

Voici ma meilleure explication pour vous. Les statistiques incrémentielles nécessitent absolument que toutes les partitions soient échantillonnées au même rythme afin que lorsque le moteur fusionne les pages de statistiques, il puisse être sûr que la distribution échantillonnée est comparable. REBUILDéchantillonne nécessairement les données à un taux d'échantillonnage de 100%. Il n'y a aucune garantie que le taux d'échantillonnage de 100% sur la partition 9 sera toujours le taux d'échantillonnage exact des autres partitions. Pour cette raison, il semble que le moteur ne puisse pas fusionner les échantillons et vous vous retrouvez avec un blob de statistiques vide. Cependant, l'objet de statistiques est toujours 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'

Vous pouvez remplir le blob par n'importe quel nombre de moyens:

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE;

ou

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

ou vous pouvez attendre la mise à jour d'AutoStats lors de la première compilation d'un plan de requête à l'aide de cet objet:

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

Cela dit, ce message édifiant d'Erin Stellato met en évidence ce qui est désormais perçu comme une lacune majeure des statistiques incrémentielles. Leurs données au niveau de la partition ne sont pas utilisées par l'optimiseur dans la génération du plan de requête, ce qui réduit l'avantage présumé des statistiques incrémentielles. Quel est donc l’avantage actuel des statistiques incrémentielles? Je dirais que leur utilité principale est de pouvoir échantillonner de grands tableaux de manière plus cohérente à un taux plus élevé qu'avec les statistiques traditionnelles.

En utilisant votre exemple, voici à quoi les choses ressemblent:

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.

Une mise à jour complète des statistiques sur la statistique incrémentielle coûte 131 ms. Une mise à jour complète des statistiques sur la statistique non alignée sur les partitions coûte 66 ms. La statistique non alignée est plus lente, probablement en raison des frais généraux occasionnés par la fusion des pages de statistiques individuelles dans l'histogramme principal.. Cependant, en utilisant l'objet statistique aligné sur la partition, nous pouvons mettre à jour une partition et la fusionner à nouveau dans le blob d'histogramme principal en 5 ms. Donc, à ce stade, l'administrateur avec la statistique incrémentale est confronté à une décision. Ils peuvent réduire leur temps de maintenance des statistiques globales en ne mettant à jour que les mises à jour des partitions, ou ils peuvent expérimenter avec des taux d'échantillonnage plus élevés de sorte qu'ils obtiennent potentiellement plus de lignes échantillonnées dans la même période de temps que leur calendrier de maintenance précédent. Le premier permet de respirer dans la fenêtre de maintenance, le second peut pousser les statistiques sur une très grande table vers un endroit où les requêtes obtiennent de meilleurs plans basés sur des statistiques plus précises. Ce n'est pas une garantie et votre kilométrage peut varier.

Le lecteur peut voir que 66 ms n'est pas un temps de mise à jour des statistiques pénible sur ce tableau, j'ai donc essayé de configurer un test sur l'ensemble de données stackexchange. Il y a 6 418 608 messages (à l'exclusion des messages StackOverflow et tous les messages de 2012 - une erreur de données de ma part) dans le récent vidage que j'ai téléchargé.

J'ai partitionné les données par [CreationDate]parce que ... démo.

Voici quelques timings pour certains scénarios assez standard (100% - reconstruction d'index, par défaut - mise à jour automatique des statistiques ou UPDATE STATISTICSsans taux d'échantillonnage spécifié:

  • Créer une statistique non incrémentielle avec Fullscan: temps CPU = 23500 ms, temps écoulé = 22521 ms.
  • Créer une statistique incrémentielle avec Fullscan: temps CPU = 20406 ms, temps écoulé = 15413 ms.
  • Mettre à jour la statistique non incrémentielle avec la fréquence d'échantillonnage par défaut: temps CPU = 406 ms, temps écoulé = 408 ms.
  • Mettre à jour la statistique incrémentielle avec la fréquence d'échantillonnage par défaut: temps CPU = 453 ms, temps écoulé = 507 ms.

Disons que nous sommes plus sophistiqués que ces scénarios par défaut et avons décidé qu'un taux d'échantillonnage de 10% est le taux minimum qui devrait nous fournir les plans dont nous avons besoin tout en maintenant le temps de maintenance à un délai raisonnable.

  • Mettre à jour la statistique non incrémentielle avec un échantillon de 10%: temps CPU = 2344 ms, temps écoulé = 2441 ms.
  • Mettre à jour la statistique incrémentielle avec un échantillon de 10%: temps CPU = 2344 ms, temps écoulé = 2388 ms.

Jusqu'à présent, il n'y a aucun avantage clair à avoir une statistique incrémentale. Cependant, si nous exploitons le DMV non documenté sys.dm_db_stats_properties_internal() (ci-dessous), vous pouvez obtenir un aperçu de la ou des partitions que vous souhaitez mettre à jour. Disons que nous avons apporté des modifications aux données de la partition 3 et que nous voulons nous assurer que les statistiques sont actualisées pour les requêtes entrantes. Voici nos options:

  • Mise à jour non incrémentielle par défaut (également le comportement par défaut de la mise à jour automatique des statistiques): 408 ms.
  • Mise à jour non incrémentielle à 10%: 2441 ms.
  • Mettre à jour les statistiques incrémentielles, partition 3 avec rééchantillonnage (10% - notre taux d'échantillonnage défini): temps CPU = 63 ms, temps écoulé = 63 ms.

Voici où nous devons prendre une décision. Prenons-nous la victoire d'un 63 ms. mise à jour des statistiques basée sur la partition, ou augmentons-nous encore le taux d'échantillonnage? Disons que nous sommes prêts à prendre le coup initial d'échantillonnage à 50% sur une statistique incrémentale:

  • Mise à jour des statistiques incrémentielles à 50%: temps écoulé = 16840 ms.
  • Mettre à jour les statistiques incrémentielles, partition 3 avec rééchantillonnage (50% - notre nouveau temps de mise à jour): temps écoulé = 295 ms.

Nous pouvons échantillonner beaucoup plus de données, peut-être configurer l'optimiseur pour mieux deviner nos données (même s'il n'utilise pas encore de statistiques au niveau de la partition) et nous pouvons le faire plus rapidement maintenant que nous avons statistiques incrémentielles.

Une dernière chose amusante à comprendre, cependant. Qu'en est-il des mises à jour synchrones des statistiques? Le taux d'échantillonnage de 50% est-il conservé même lorsque les statistiques automatiques interviennent?

J'ai supprimé des données de la partition 3 et exécuté une requête sur CreationDate et vérifié puis vérifié les taux avec la même requête ci-dessous. Le taux d'échantillonnage de 50% a été conservé.

Donc, histoire courte: les statistiques incrémentales peuvent être un outil utile avec la bonne quantité de réflexion et de travail de configuration initiale. Cependant, vous devez connaître le problème que vous essayez de résoudre, puis vous devez le résoudre de manière appropriée. Si vous obtenez de mauvaises estimations de cardinalité, vous pourriez être en mesure d'obtenir de meilleurs plans avec un taux d'échantillonnage stratégique et une intervention investie. Cependant, vous n'obtenez qu'une petite partie des avantages puisque l'histogramme utilisé est la seule page de statistiques fusionnée et non les informations au niveau de la partition. Si vous ressentez de la douleur dans votre fenêtre de maintenance, des statistiques incrémentielles peuvent peut-être vous aider, mais elles nécessiteront probablement la mise en place d'un processus d'intervention de maintenance hautement tactile. Indépendamment,:

  • Statistiques créées avec des index qui ne sont pas alignés sur la partition avec la table de base.
  • Statistiques créées sur des bases de données secondaires lisibles AlwaysOn.
  • Statistiques créées sur des bases de données en lecture seule.
  • Statistiques créées sur des index filtrés.
  • Statistiques créées sur les vues.
  • Statistiques créées sur des tables internes.
  • Statistiques créées avec des index spatiaux ou des index XML.

J'espère que cela t'aides

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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.