Lorsque j'ajoute deux colonnes à ma sélection, la requête ne répond pas. Le type de colonne est nvarchar(2000)
. C'est un peu inhabituel.
- La version de SQL Server est 2014.
- Il n'y a qu'un seul index primaire.
- L'ensemble des enregistrements ne contient que 1 000 lignes.
Voici le plan d'exécution avant ( showplan XML ):
Plan d'exécution après ( XML showplan ):
Voici la requête:
select top(100)
Batch_Tasks_Queue.id,
btq.id,
Batch_Tasks_Queue.[Parameters], -- this field
btq.[Parameters] -- and this field
from
Batch_Tasks_Queue with(nolock)
inner join Batch_Tasks_Queue btq with(nolock) on Batch_Tasks_Queue.Start_Time < btq.Start_Time
and btq.Start_Time < Batch_Tasks_Queue.Finish_Time
and Batch_Tasks_Queue.id <> btq.id
and btq.Start_Time is not null
and btq.State in (3, 4)
where
Batch_Tasks_Queue.Start_Time is not null
and Batch_Tasks_Queue.State in (3, 4)
and Batch_Tasks_Queue.Operation_Type = btq.Operation_Type
and Batch_Tasks_Queue.Operation_Type not in (23, 24, 25, 26, 27, 28, 30)
order by
Batch_Tasks_Queue.Start_Time desc
Le nombre total de résultats est de 17 lignes. Les données sales (indice nolock) ne sont pas importantes.
Voici la structure du tableau:
CREATE TABLE [dbo].[Batch_Tasks_Queue](
[Id] [int] NOT NULL,
[OBJ_VERSION] [numeric](8, 0) NOT NULL,
[Operation_Type] [numeric](2, 0) NULL,
[Request_Time] [datetime] NOT NULL,
[Description] [varchar](1000) NULL,
[State] [numeric](1, 0) NOT NULL,
[Start_Time] [datetime] NULL,
[Finish_Time] [datetime] NULL,
[Parameters] [nvarchar](2000) NULL,
[Response] [nvarchar](max) NULL,
[Billing_UserId] [int] NOT NULL,
[Planned_Start_Time] [datetime] NULL,
[Input_FileId] [uniqueidentifier] NULL,
[Output_FileId] [uniqueidentifier] NULL,
[PRIORITY] [numeric](2, 0) NULL,
[EXECUTE_SEQ] [numeric](2, 0) NULL,
[View_Access] [numeric](1, 0) NULL,
[Seeing] [numeric](1, 0) NULL,
CONSTRAINT [PKBachTskQ] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [Batch_Tasks_QueueData]
) ON [Batch_Tasks_QueueData] TEXTIMAGE_ON [Batch_Tasks_QueueData]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] WITH NOCHECK ADD CONSTRAINT [FK0_BtchTskQ_BlngUsr] FOREIGN KEY([Billing_UserId])
REFERENCES [dbo].[BILLING_USER] ([ID])
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] CHECK CONSTRAINT [FK0_BtchTskQ_BlngUsr]
GO
sql-server
query-performance
sql-server-2014
Hamid Fathi
la source
la source
Réponses:
Sommaire
Les principaux problèmes sont:
Détails
Les deux plans sont fondamentalement assez similaires, bien que les performances puissent être très différentes:
Plan avec les colonnes supplémentaires
Prendre celui avec les colonnes supplémentaires qui ne se termine pas dans un délai raisonnable:
Les fonctionnalités intéressantes sont:
Start_Time
n'est pas nul,State
est 3 ou 4 etOperation_Type
est l'une des valeurs répertoriées. Le tableau est entièrement analysé une fois, chaque ligne étant testée par rapport aux prédicats mentionnés. Seules les lignes qui réussissent tous les tests sont transmises au tri. L'optimiseur estime que 38 283 lignes seront admissibles.Start_Time DESC
. Il s'agit de l'ordre de présentation final demandé par la requête.Start_Time
n'est pas nul etState
vaut 3 ou 4. On estime que cela produira 400 875 lignes à chaque itération. Sur 94,2791 itérations, le nombre total de lignes est de près de 38 millions.Operation_Type
correspond, que leStart_Time
noeud 4 est inférieur auStart_Time
noeud 5, que leStart_Time
noeud 5 est inférieur auFinish_Time
noeud 4 et que les deuxId
valeurs ne correspondent pas.La grande inefficacité se situe évidemment aux étapes 6 et 7 ci-dessus. L'analyse complète de la table au nœud 5 pour chaque itération n'est que légèrement raisonnable si elle ne se produit que 94 fois comme le prévoit l'optimiseur. L'ensemble de comparaisons d'environ 38 millions de lignes au nœud 2 représente également un coût élevé.
Surtout, l'estimation de l'objectif de la ligne 93/94 est également très probablement erronée, car elle dépend de la distribution des valeurs. L'optimiseur suppose une distribution uniforme en l'absence d'informations plus détaillées. En termes simples, cela signifie que si 1% des lignes du tableau sont censées se qualifier, l'optimiseur explique que pour trouver 1 ligne correspondante, il doit lire 100 lignes.
Si vous exécutez cette requête jusqu'à la fin (ce qui peut prendre très longtemps), vous constaterez très probablement que plus de 93/94 lignes doivent être lues à partir du tri afin de produire finalement 100 lignes. Dans le pire des cas, la 100e ligne serait trouvée en utilisant la dernière ligne du tri. En supposant que l'estimation de l'optimiseur au nœud 4 est correcte, cela signifie exécuter le scan au nœud 5 38 284 fois, pour un total d'environ 15 milliards de lignes. Cela pourrait être plus si les estimations de scan sont également désactivées.
Ce plan d'exécution comprend également un avertissement d'index manquant:
L'optimiseur vous alerte du fait que l'ajout d'un index à la table améliorerait les performances.
Planifiez sans les colonnes supplémentaires
Il s'agit essentiellement du même plan que le précédent, avec l'ajout de la bobine d'indexation au nœud 6 et du filtre au nœud 5. Les différences importantes sont les suivantes:
Operation_Type
etStart_Time
avec,Id
comme colonne non clé.Operation_Type
,Start_Time
,Finish_Time
etId
de l'analyse au niveau du noeud 4 sont transmises à la branche du côté interne comme références externes.Operation_Type
correspond à la valeur de référence externe actuelle, et leStart_Time
est dans la plage définie par les références externesStart_Time
etFinish_Time
.Id
valeurs de la bobine d'indexation pour rechercher l'inégalité par rapport à la valeur de référence externe actuelle deId
.Les principales améliorations sont les suivantes:
Operation_Type
,Start_Time
) avecId
comme colonne incluse permet une jointure de boucles imbriquées d'index. L'index est utilisé pour rechercher des lignes correspondantes à chaque itération plutôt que d'analyser la table entière à chaque fois.Comme précédemment, l'optimiseur inclut un avertissement concernant un index manquant:
Conclusion
Le plan sans les colonnes supplémentaires est plus rapide car l'optimiseur a choisi de créer un index temporaire pour vous.
Le plan avec les colonnes supplémentaires rendrait l'index temporaire plus coûteux à construire. La
[Parameters
colonne] estnvarchar(2000)
, ce qui ajouterait jusqu'à 4000 octets à chaque ligne de l'index. Le coût supplémentaire suffit pour convaincre l'optimiseur que la construction de l'index temporaire à chaque exécution ne serait pas rentable.L'optimiseur avertit dans les deux cas qu'un index permanent serait une meilleure solution. La composition idéale de l'index dépend de votre charge de travail plus large. Pour cette requête particulière, les index suggérés sont un point de départ raisonnable, mais vous devez comprendre les avantages et les coûts impliqués.
Recommandation
Un large éventail d'index possibles serait bénéfique pour cette requête. Le point important à retenir est qu'une sorte d'index non clusterisé est nécessaire. D'après les informations fournies, un indice raisonnable serait à mon avis:
Je serais également tenté d'organiser un peu mieux la requête et de retarder la recherche des
[Parameters]
colonnes larges dans l'index cluster jusqu'à ce que les 100 premières lignes aient été trouvées (en utilisantId
comme clé):Lorsque les
[Parameters]
colonnes ne sont pas nécessaires, la requête peut être simplifiée pour:L'
FORCESEEK
astuce est là pour vous assurer que l'optimiseur choisit un plan de boucles imbriquées indexées (il y a une tentation basée sur le coût pour l'optimiseur de sélectionner un hachage ou (plusieurs-plusieurs) jointures de fusion sinon, ce qui a tendance à ne pas bien fonctionner avec ce type de dans la pratique. Les deux se retrouvent avec des résidus importants; de nombreux éléments par compartiment dans le cas du hachage et de nombreux rembobinages pour la fusion).Alternative
Si la requête (y compris ses valeurs spécifiques) était particulièrement critique pour les performances de lecture, je considérerais plutôt deux index filtrés:
Pour la requête qui n'a pas besoin de la
[Parameters]
colonne, le plan estimé à l'aide des index filtrés est:L'analyse d'index renvoie automatiquement toutes les lignes éligibles sans évaluer de prédicats supplémentaires. Pour chaque itération de la jointure de boucles imbriquées d'index, la recherche d'index effectue deux opérations de recherche:
Operation_Type
etState
= 3, puis recherche la plage deStart_Time
valeurs, prédicat résiduel sur l'Id
inégalité.Operation_Type
etState
= 4, puis recherche la plage deStart_Time
valeurs, prédicat résiduel sur l'Id
inégalité.Lorsque la
[Parameters]
colonne est nécessaire, le plan de requête ajoute simplement un maximum de 100 recherches singleton pour chaque table:En guise de note finale, vous devriez envisager d'utiliser les types d'entiers standard intégrés plutôt que le
numeric
cas échéant.la source
Veuillez créer l'index suivant:
la source