Pourquoi la deuxième INSERT
instruction est-elle 5 fois plus lente que la première?
D'après la quantité de données de journal générées, je pense que la seconde n'est pas admissible à une journalisation minimale. Cependant, la documentation du Guide de performances de chargement des données indique que les deux insertions doivent pouvoir être journalisées de manière minimale. Donc, si la journalisation minimale est la principale différence de performances, pourquoi la deuxième requête ne remplit-elle pas les conditions requises pour une journalisation minimale? Que peut-on faire pour améliorer la situation?
Requête n ° 1: insertion de lignes de 5 mm à l'aide de INSERT ... WITH (TABLOCK)
Considérez la requête suivante, qui insère des lignes de 5 mm dans un segment. Cette requête s'exécute dans 1 second
et génère 64MB
des données de journal des transactions comme indiqué par sys.dm_tran_database_transactions
.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbers
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Requête n ° 2: insertion des mêmes données, mais SQL sous-estime le nombre de lignes
Considérons maintenant cette requête très similaire, qui fonctionne exactement sur les mêmes données mais qui se trouve être tirée d'une table (ou d'une SELECT
instruction complexe avec de nombreuses jointures dans mon cas de production réel) où l'estimation de cardinalité est trop faible. Cette requête s'exécute dans 5.5 seconds
et génère 461MB
des données du journal des transactions.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that produces 5MM rows but SQL estimates just 1000 rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Script complet
Voir ce Pastebin pour un ensemble complet de scripts pour générer les données de test et exécuter l'un de ces scénarios. Notez que vous devez utiliser une base de données qui se trouve dans le SIMPLE
modèle de récupération .
Contexte d'affaires
Nous nous déplaçons semi-fréquemment sur des millions de lignes de données, et il est important que ces opérations soient aussi efficaces que possible, à la fois en termes de temps d'exécution et de charge d'E / S disque. Au départ, nous avions l'impression que la création d'une table de tas et l'utilisation INSERT...WITH (TABLOCK)
étaient un bon moyen de le faire, mais nous sommes maintenant devenus moins confiants étant donné que nous avons observé la situation démontrée ci-dessus dans un scénario de production réel (bien qu'avec des requêtes plus complexes, pas la version simplifiée ici).
SELECT
instruction complexe avec de nombreuses jointures qui génère le jeu de résultats pour leINSERT
. Ces jointures produisent de mauvaises estimations de cardinalité pour l'opérateur d'insertion de table final (que j'ai simulé dans le script de repro via le mauvaisUPDATE STATISTICS
appel), et ce n'est donc pas aussi simple que d'émettre uneUPDATE STATISTICS
commande pour résoudre le problème. Je suis tout à fait d'accord pour dire que la simplification de la requête afin qu'il soit plus facile à comprendre pour l'estimateur de cardinalité pourrait être une bonne approche, mais ce n'est pas une solution implicite à mettre en œuvre compte tenu d'une logique métier complexe.