web-dev-qa-db-fra.com

Comment améliorer l'estimation des lignes de 1 rangée dans la jointure pour les données nouvellement insérées

Une statistique personnalisée existe pour la colonne cacheid d'une table. Après une mise à jour des statistiques de la nuit:

Statistics for INDEX 'ST_TableName_CacheId'.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Name                            Updated                         Rows                            Rows Sampled                    Steps                           Density                         Average Key Length              String Index                    
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ST_TableName_CacheId Apr 26 2014  2:04AM             121482                          121482                          6                               0                               4                               NO                                                              121482                          

All Density                     Average Length                  Columns                         
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0.1666667                       4                               CacheId                         

Histogram Steps                 
RANGE_HI_KEY                    RANGE_ROWS                      EQ_ROWS                         DISTINCT_RANGE_ROWS             AVG_RANGE_ROWS                  
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
39968                           0                               20247                           0                               1                               
40058                           0                               20247                           0                               1                               
40062                           0                               20247                           0                               1                               
40066                           0                               20247                           0                               1                               
40069                           0                               20247                           0                               1                               
41033                           0                               20247                           0                               1                               

1) Performance d'une jointure contre un ensemble de données existantes dans ce tableau dans laquelle Cacheid = 41033 fonctionne bien avec de bonnes estimations (23622 VS réelle de 20247).

2) Ensuite, un insert est effectué avec Cacheid = 41273 des rangées 20247.

3) Ensuite, une jointure contre cet ensemble de données nouvellement insérées montre une mauvaise estimation de 1 rangée entraînant un mauvais plan.

4) Une mise à jour manuelle des statistiques (qui était à l'origine avec FullScan) montre un nouvel histogramme:

Statistics for INDEX 'ST_TableName_CacheId'.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Name                            Updated                         Rows                            Rows Sampled                    Steps                           Density                         Average Key Length              String Index                    
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ST_TableName_CacheId Apr 28 2014 10:41AM             141729                          141729                          7                               0                               4                               NO                                                              141729                          

All Density                     Average Length                  Columns                         
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0.1428571                       4                               CacheId                         

Histogram Steps                 
RANGE_HI_KEY                    RANGE_ROWS                      EQ_ROWS                         DISTINCT_RANGE_ROWS             AVG_RANGE_ROWS                  
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
39968                           0                               20247                           0                               1                               
40058                           0                               20247                           0                               1                               
40062                           0                               20247                           0                               1                               
40066                           0                               20247                           0                               1                               
40069                           0                               20247                           0                               1                               
41033                           0                               20247                           0                               1                               
41274                           0                               20247                           0                               1                               

5) Exécuter la même requête de jointure pour la cacheid = 41274 montre des estimations parfaites (20247) et de bonnes performances.

Q1) Pourquoi mathématiquement est l'estimation originale si mauvaise? Je veux dire que les cacheïdes sont rares mais pas à un ratio de 20000: 1.

Q2) comme le nombre d'augmentations de cacheid vous attendez-vous aux estimations des données nouvellement insérées améliorées naturellement?

Q3) Y a-t-il des moyens (gorgés, astuces ou autres) pour améliorer l'estimation (ou le rendre moins certain de 1 rangée) sans avoir à mettre à jour les statistiques à chaque fois qu'un nouvel ensemble de données est inséré ( par exemple, ajouter un faux jeu de données à un cacheid beaucoup plus grand = 999999).

Voici le vrai nombre de lignes pour tous les cacheids dans le tableau:

CacheId Rows
39968   20247
40058   20247
40062   20247
40066   20247
40069   20247
41033   20247
41274   20247

[Je ne pense pas que le QP soit nécessaire pour répondre à cette question et de son travail pour les nettoyer. Je peux répondre aux questions spécifiques si nécessaire! ]

2
crokusek

Q1) Pourquoi mathématiquement est l'estimation originale si mauvaise? Je veux dire que les cacheïdes sont rares mais pas à un ratio de 20000: 1.

Voici la règle de déclenchement de la mise à jour automatique des statistiques fonctionnalité de maintenance statistique (autostats) dans SQL Server :

L'algorithme ci-dessus peut être résumée sous la forme d'une table:


Type de table | État vide | Seuil quand vide | seuil quand il n'est pas vide


Permanent | <500 lignes | Nombre de changements> = 500 | Nombre de changements> = 500 + (20% de la cardinalité)

Même pensait que la KB pointe de 2000, il est toujours fidèle jusqu'à 2012.

Courir dans ce scénario et voir par vous-même.

ÉTAPE 1

SET STATISTICS IO OFF;
GO
SET NOCOUNT ON;
GO
-- make sure the Include Actual Execution Plan is off!!!
IF OBJECT_ID('IDs') IS NOT NULL
DROP TABLE dbo.IDs;

CREATE TABLE IDs
(
ID tinyint NOT NULL
)

INSERT INTO IDs
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7;

IF OBJECT_ID('TestStats') IS NOT NULL
DROP TABLE dbo.TestStats;

CREATE TABLE dbo.TestStats
(
 ID tinyint NOT NULL,
 Col1 int NOT NULL,
 CONSTRAINT PK_TestStats PRIMARY KEY CLUSTERED (ID, col1)
);

DECLARE @id int = 1
DECLARE @i int = 1

WHILE @id <= 6
BEGIN
 SET @i = 1

WHILE @i <= 20247
BEGIN
    INSERT INTO dbo.TestStats VALUES(@id,@i);

    SET @i = @i + 1
END

SET @id = @id + 1
END

-- so far so good!
SELECT ID, COUNT(*) AS RowCnt FROM dbo.TestStats GROUP BY ID;

DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;

Maintenant, nous avons une table avec IDS 1 à 6 et chaque identifiant a 20247 rangées. Les statistiques ont l'air bien jusqu'à présent!

ÉTAPE 2

-- now insert another ID = 7 with 20247 rows
DECLARE @i int = 1;

WHILE @i <= 20247
BEGIN
  INSERT INTO dbo.TestStats VALUES(7,@i);

  SET @i = @i + 1
END

-- see the problem with the histogram?
SELECT ID, COUNT(*) FROM dbo.TestStats GROUP BY ID;

DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;

Regardez la table et l'histogramme! La table réelle a ID = 7 avec 20247 lignes, mais l'histogramme n'a aucune idée que vous venez d'insérer les nouvelles données car la mise à jour automatique n'a pas déclenché. Selon la formule, vous devez insérer (20247 * 6) * 0,2 + 500 = 24,796,4 lignes pour déclencher une mise à jour automatique des statistiques de cette table.

Ainsi, si vous regardez les plans de ces questions, vous voyez les mauvaises estimations:

-- CTRL + M to include the Actual Execution plan
-- now, IF we run these queries, the Optimizer has no info about ID = 7
-- and the Estimates 1 because it cannot say 0.
SELECT ts.*
FROM dbo.TestStats ts
INNER JOIN dbo.IDs ON IDs.ID = ts.ID
WHERE IDs.ID = 1;

SELECT ts.*
FROM dbo.TestStats ts
INNER JOIN dbo.IDs ON IDs.ID = ts.ID
WHERE IDs.ID = 7;

Query n ° 1:

Query #1:

Query n ° 2:

Query #2

L'optimisation ne peut pas dire 0 rangées, alors cela vous montre simplement 1.

ÉTAPE 3

-- now we manually update the stats
UPDATE STATISTICS dbo.TestStats WITH FULLSCAN;

-- check the histogram
DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;

-- rerun the queries
SELECT ts.*
FROM dbo.TestStats ts
INNER JOIN dbo.IDs ON IDs.ID = ts.ID
WHERE IDs.ID = 1;

SELECT ts.*
FROM dbo.TestStats ts
INNER JOIN dbo.IDs ON IDs.ID = ts.ID
WHERE IDs.ID = 7;

Maintenant, l'histogramme indique l'ID 7 manquant et les plans d'exécution montrent également les budgettes appropriées.

Query n ° 1:

Query #1

Query n ° 2:

Query #2

Q2) Comme le nombre d'augmentations de cacheid vous attendez-vous aux estimations des données nouvellement insérées améliorées naturellement?

Oui, dès que vous passez le seuil de 20% + 500 des rangées totales. La mise à jour automatique déclenchera. Vous pouvez exécuter ce scénario en ré-exécutant l'étape 1, mais modifier ensuite l'étape 2 en exécutant ces requêtes:

-- now insert another ID = 7 with 20247 rows
DECLARE @i int = 1;

WHILE @i <= 20247
BEGIN
   INSERT INTO dbo.TestStats VALUES(7,@i);

   SET @i = @i + 1
END

-- see the problem with the histogram?
SELECT ID, COUNT(*) FROM dbo.TestStats GROUP BY ID;

DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;
GO
-- try to insert ID = 8 to trigger the auto update for the stats
DECLARE @i int = 1;

WHILE @i <= 4548
BEGIN
  INSERT INTO dbo.TestStats VALUES(8,@i);

  SET @i = @i + 1
END

-- no update yet
SELECT ID, COUNT(*) FROM dbo.TestStats GROUP BY ID;

DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;

Aucune mise à jour, car le seuil est 24,796.4 - 20247 = 4549.4 Mais nous avons inséré seulement 4548 lignes pour ID 8. Insérez maintenant cette ligne et double vérifiez l'histogramme:

-- this will trigger the update
INSERT INTO dbo.TestStats VALUES(8,4549);

-- double check
SELECT ID, COUNT(*) FROM dbo.TestStats GROUP BY ID;

DBCC SHOW_STATISTICS('TestStats',PK_TestStats) WITH HISTOGRAM;

Q3) Y a-t-il des façons (gorgées, astuces ou autres) pour améliorer l'estimation (ou le rendre moins certain de 1 rangée) sans avoir à mettre à jour les statistiques à chaque fois qu'un nouvel ensemble de données est inséré (par exemple, ajouter une fausse définie de données à un cacheid beaucoup plus grand = 999999).

Contrôle de l'autostat (auto_update_statistics) Comportement dans SQL Server

Cependant, lorsqu'une table devient très grande, l'ancien seuil (un taux fixe - 20% des rangées modifiées) peut être trop élevé et le processus d'autostat peut ne pas être déclenché assez souvent. Cela pourrait entraîner des problèmes de performance potentiels. SQL Server 2008 R2 Service Pack 1 et version ultérieure Introduisez le drapeau de trace 2371 que vous pouvez activer de modifier ce comportement par défaut. Plus le nombre de lignes dans une table est élevé, plus le seuil sera inférieur à déclencher une mise à jour des statistiques. Par exemple, si le drapeau de trace est activé, la mise à jour des statistiques sera déclenchée sur une table avec 1 milliard de lignes lorsque 1 million de modifications se produisent. Si l'indicateur de trace n'est pas activé, la même table avec 1 milliard d'enregistrements aurait besoin de 200 millions de modifications avant que des statistiques de mise à jour ne sont déclenchées.

J'espère que cela vous a aidé à comprendre! Une très bonne question!

5
DenisT

Une réponse à Q3)

Q3 ) Existe-t-il des façons (gorgée, astuces ou autres) pour améliorer l'estimation (ou le rendre moins certain de 1 rangée) sans avoir à mettre à jour le Statistiques Chaque fois qu'un nouvel ensemble de données est inséré (par exemple, l'ajout d'une fausse définition de données à un cacheid = 999999 beaucoup plus grand.

Dans la jointure, ajoutez une certaine confusion à l'aide d'Isnull () et à la fin, ajoutez un "optimisation pour".

 select ... from ... join ...
   where CacheId = IsNull(@cacheId, 0)    
  option (recompile, optimize for (@cacheId = 41274))

Les deux semblent être nécessaires. L'ID 0 n'existe pas vraiment. La valeur d'identification utilisée dans le "optimisation de" ne semble pas importe et ne doit même pas nécessairement exister.

Note latérale: J'avais également essayé de supprimer les statistiques personnalisées, en ajoutant un nouvel indice sur la cacheid, mais ses statistiques implicites éventuellement éventuellement comportées comme les statistiques personnalisées explicites en ce qui concerne les seuils de comptage des lignes de mise à jour.

EDIT 2014-04-29:

Les estimations des "clés ascendantes" ont probablement été améliorées dans l'amélioration de l'amélioration de SQL Server 2014 estimateur de cardinalité

Il existe également une solution Traceon () pour touches ascendantes depuis 2005 SP1 de Mark Storey-Smith Commentaire.

EDIT 2015-05-07:

Certains cas estimaient toujours 1 rangée ( parfois). L'utilisation d'un inconnu semble aider puis l'ISNULL () peut également être supprimé:

  select ... from ... join ...
   where CacheId = @cacheId
  option (recompile, optimize for (@cacheId = unknown))
1
crokusek