J'ai une requête assez simple
SELECT TOP 1 dc.DOCUMENT_ID,
dc.COPIES,
dc.REQUESTOR,
dc.D_ID,
cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
Cela me donne une performance horrible (comme jamais pris la peine d'attendre la fin). Le plan de requête ressemble à ceci:
Cependant, si je supprime le, TOP 1
je reçois un plan qui ressemble à ceci et qui s'exécute en 1-2 secondes:
PK correcte et indexation ci-dessous.
Le fait que le TOP 1
plan de requête modifié ne me surprenne pas, je suis un peu surpris que cela aggrave encore la situation.
Remarque: j'ai lu les résultats de cet article et j'ai compris le concept de Row Goal
etc. Ce que je suis curieux de savoir, c'est comment modifier la requête afin qu'elle utilise le meilleur plan. Actuellement, je vide les données dans une table temporaire, puis en extrait la première ligne. Je me demande s'il existe une meilleure méthode.
Modifier Pour ceux qui liront cela après le fait, voici quelques informations supplémentaires.
- Document_Queue - PK / CI est D_ID et contient environ 5 000 lignes.
- Correspondence_Journal - PK / CI est FILE_NUMBER, CORRESPONDENCE_ID et compte environ 1,4 mil de lignes.
Quand j'ai commencé, il n'y avait pas d'autres index. Je me suis retrouvé avec un sur Correspondence_Journal (Document_Id, File_Number)
la source
DOCUMENT_ID
relation entre les deux tables (ou chaque enregistrementCORRESPONDENCE_JOURNAL
est-il associé à un enregistrement correspondantDOCUMENT_QUEUE
)?Réponses:
Essayez de forcer un hash rejoindre *
L'optimiseur pensait probablement qu'une boucle serait meilleure avec le top 1 et que cela avait du sens, mais en réalité, cela ne fonctionnait pas ici. Juste une supposition, mais le coût estimé de ce spool était peut-être correct - il utilise TEMPDB - vous pouvez avoir une TEMPDB peu performante.
* Faites attention aux indications de jointure , car elles forcent l'ordre d'accès des tables de plan à correspondre à l' ordre écrit des tables dans la requête (comme si cela
OPTION (FORCE ORDER)
avait été spécifié). À partir du lien de la documentation:Cela peut ne produire aucun effet indésirable dans l'exemple, mais en général, cela pourrait très bien.
FORCE ORDER
(implicite ou explicite) est un indice très puissant qui va au-delà de l'exécution forcée de l'ordre; il empêche l'application d'un large éventail de techniques d'optimisation, y compris les agrégations partielles et la réorganisation.Un indice de
OPTION (HASH JOIN)
requête peut être moins intrusif dans les cas appropriés, car cela n'implique pasFORCE ORDER
. Cependant, il s'applique à toutes les jointures de la requête. D'autres solutions sont disponibles.la source
Puisque vous obtenez le bon plan avec le
ORDER BY
, vous pourriez peut-être simplement lancer votre propreTOP
opérateur?Dans mon esprit, le plan de requête pour ce qui
ROW_NUMBER()
précède devrait être le même que si vous aviez unORDER BY
. Le plan de requête doit maintenant comporter un segment, un projet de séquence et enfin un opérateur de filtrage; le reste doit ressembler à votre bon plan.la source
Edit: +1 fonctionne dans cette situation car il s'avère qu'il
FILE_NUMBER
s'agit d'une version chaîne zéro complétée d'un entier. Une meilleure solution ici pour les chaînes consiste à ajouter''
(la chaîne vide), car l’ajout d’une valeur peut affecter l’ordre, ou que les nombres ajoutent quelque chose de constant mais qui contient une fonction non déterministe, telle quesign(rand()+1)
. L'idée de "briser le genre" est toujours valable ici, c'est juste que ma méthode n'était pas idéale.+1
Non, je ne veux pas dire que je suis d'accord avec quoi que ce soit, je veux dire cela comme une solution. Si vous modifiez votre requête en
ORDER BY cj.FILE_NUMBER + 1
alors leTOP 1
se comportera différemment.Vous voyez, avec l'objectif de petite ligne en place pour une requête ordonnée, le système essaiera de consommer les données dans l'ordre, pour éviter d'avoir un opérateur de tri. Cela évitera également de construire une table de hachage, sachant qu'il n'aura probablement pas à travailler trop pour trouver la première ligne. Dans votre cas, cela est faux: vu l’épaisseur de ces flèches, il semble qu’il doive consommer beaucoup de données pour trouver une seule correspondance.
L'épaisseur de ces flèches suggère que votre
DOCUMENT_QUEUE
table (DQ) est beaucoup plus petite que votreCORRESPONDENCE_JOURNAL
table (CJ). Et que le meilleur plan serait en réalité de vérifier toutes les lignes DQ jusqu'à ce qu'une ligne CJ soit trouvée. En fait, c’est ce que l’optimiseur de requêtes (QO) ferait s’il ne contenait pas cette embêtadeORDER BY
, c’est bien soutenu par un index couvrant sur CJ.Donc, si vous les supprimiez
ORDER BY
complètement, je suppose que vous obtiendrez un plan comportant une boucle imbriquée, qui parcourt les rangées de DQ, recherchant dans CJ pour s’assurer que la rangée existe. Et avecTOP 1
, cela s'arrêterait après qu'une seule rangée ait été tirée.Mais si vous avez réellement besoin de la première ligne dans l'
FILE_NUMBER
ordre, vous pourriez alors tromper le système en ignorant cet index qui semble (à tort) être si utile, en agissantORDER BY CJ.FILE_NUMBER+1
- et nous savons qu'il gardera le même ordre qu'auparavant, mais surtout le QO ne le fait pas L’assurance qualité s’attachera à obtenir l’ensemble complet de manière à pouvoir satisfaire un opérateur de tri par ordre croissant. Cette méthode doit produire un plan contenant un opérateur Compute Scalar pour calculer la valeur pour la commande et un opérateur Top N Sort pour obtenir la première ligne. Mais à droite de ceux-ci, vous devriez voir une belle boucle imbriquée, faisant beaucoup de recherches sur CJ. Et de meilleures performances que de parcourir une grande table de lignes qui ne correspond à rien dans DQ.Le hachage n'est pas forcément horrible, mais si l'ensemble des lignes que vous retournez de DQ est bien plus petit que CJ (comme je le pensais), alors le hachage analysera beaucoup plus de CJ. que ce dont il a besoin.
Remarque: j'ai utilisé +1 au lieu de +0 car l'optimiseur de requêtes est susceptible de reconnaître que +0 ne change rien. Bien sûr, la même chose pourrait s’appliquer au +1, si ce n’est maintenant, à un moment donné dans l’avenir.
la source
L'ajout
OPTION (QUERYTRACEON 4138)
désactive l'effet des objectifs de ligne pour cette requête uniquement, sans être trop normatif sur le plan final, et constituera probablement le moyen le plus simple / le plus direct.Si l'ajout de cette astuce vous donne une erreur d'autorisations (obligatoire pour
DBCC TRACEON
), vous pouvez l'appliquer à l'aide d'un repère de plan:Utilisation
QUERYTRACEON
de guides de plan par spaghettidba... ou utilisez simplement une procédure stockée:
Quelles sont les autorisations
QUERYTRACEON
nécessaires? par Kendra Littlela source
Les nouvelles versions de SQL Server offrent des options différentes (et sans doute meilleures) pour traiter les requêtes dont les performances sont sous-optimales lorsque l'optimiseur est en mesure d'appliquer des optimisations d'objectif de ligne. SQL Server 2016 SP1 a introduit le
DISABLE_OPTIMIZER_ROWGOAL USE HINT
qui a le même effet que l'indicateur de suivi 4138. Si vous n'êtes pas sur cette version, vous pouvez également envisager d'utiliser l'OPTIMIZE FOR
indicateur de requête pour obtenir un plan de requête conçu pour renvoyer toutes les lignes au lieu de 1. La requête ci-dessous renverra les mêmes résultats que celui de la question, mais il ne sera pas créé dans le but d'obtenir seulement 1 ligne.la source
Depuis que vous faites un
TOP(1)
, je recommande de faire leORDER BY
déterministe pour un début. Au minimum, cela garantira des résultats prévisibles sur le plan fonctionnel (toujours utiles pour les tests de régression). Il semble que vous ayez besoin d'ajouterDC.D_ID
etCJ.CORRESPONDENCE_ID
pour cela.Lorsque je regarde des plans de requête, je trouve parfois instructif de simplifier la requête: il est possible de sélectionner à l’avance toutes les lignes continues pertinentes dans une table temporaire, afin d’éliminer les problèmes d’estimation de cardinalité sur
QUEUE_DATE
etPRINT_LOCATION
. Cela devrait être rapide étant donné le faible nombre de lignes. Vous pouvez ensuite ajouter des index à cette table temporaire si nécessaire sans modifier la table permanente.la source