Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier les répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de changer de répertoire.
Cet article fournit des conseils de résolution des problèmes dans lesquels une requête Microsoft SQL Server prend un temps excessif pour se terminer (heures ou jours).
Symptômes
Cet article se concentre sur les requêtes qui semblent s’exécuter ou compiler sans fin. Autrement dit, leur utilisation du processeur continue d’augmenter. Cet article ne s’applique pas aux requêtes qui sont bloquées ou en attente sur une ressource qui n’est jamais publiée. Dans ce cas, l’utilisation du processeur reste constante ou change légèrement.
Important
Si une requête est laissée pour continuer à s’exécuter, elle peut finir par se terminer. Ce processus peut prendre quelques secondes ou plusieurs jours. Dans certaines situations, la requête peut vraiment être infinie, par exemple lorsqu’une boucle WHILE ne se termine pas. Le terme « jamais terminé » est utilisé ici pour décrire la perception d’une requête qui ne se termine pas.
La cause
Les causes courantes des requêtes longues (jamais terminées) sont les suivantes :
-
Jointures de boucle imbriquée (NL) sur des tables très volumineuses : En raison de la nature des jointures NL, une requête qui joint des tables qui ont beaucoup de lignes peut s’exécuter pendant longtemps. Pour plus d’informations, consultez Jointures.
- Un exemple de jointure NL est l’utilisation de
TOP,FASTouEXISTS. Même si une jointure de hachage ou de fusion peut être plus rapide, l’optimiseur ne peut pas utiliser l’un des opérateurs en raison de l’objectif de ligne. - Un autre exemple de jointure NL est l’utilisation d’un prédicat de jointure d’inégalité dans une requête. Par exemple :
SELECT .. FROM tab1 AS a JOIN tab 2 AS b ON a.id > b.id. L’optimiseur ne peut pas utiliser de jointures de fusion ou de hachage ici.
- Un exemple de jointure NL est l’utilisation de
- Statistiques obsolètes : Les requêtes qui sélectionnent un plan en fonction des statistiques obsolètes peuvent être non optimales et prendre beaucoup de temps pour s’exécuter.
- Boucles infinies : Les requêtes T-SQL qui utilisent des boucles WHILE peuvent être incorrectement écrites. Le code résultant ne quitte jamais la boucle et s’exécute sans fin. Ces requêtes ne se terminent vraiment jamais. Ils s’exécutent jusqu’à ce qu’ils soient tués manuellement.
- Requêtes complexes qui ont de nombreuses jointures et tables volumineuses : Les requêtes qui impliquent de nombreuses tables jointes ont généralement des plans de requête complexes qui peuvent prendre beaucoup de temps. Ce scénario est courant dans les requêtes analytiques qui ne filtrent pas les lignes et qui impliquent un grand nombre de tables.
- Index manquants : Les requêtes peuvent s’exécuter considérablement plus rapidement si des index appropriés sont utilisés sur des tables. Les index permettent de sélectionner un sous-ensemble des données pour fournir un accès plus rapide.
Solution
Étape 1 : Découvrir des requêtes sans fin
Recherchez une requête sans fin qui s’exécute sur le système. Vous devez déterminer si une requête a un temps d’exécution long, un temps d’attente long (bloqué sur un goulot d’étranglement) ou une longue durée de compilation.
1.1 Exécuter un diagnostic
Exécutez la requête de diagnostic suivante sur votre instance SQL Server où la requête sans fin est active :
DECLARE @cntr INT = 0
WHILE (@cntr < 3)
BEGIN
SELECT TOP 10 s.session_id,
r.status,
CAST(r.cpu_time / (1000 * 60.0) AS DECIMAL(10,2)) AS cpu_time_minutes,
CAST(r.total_elapsed_time / (1000 * 60.0) AS DECIMAL(10,2)) AS elapsed_minutes,
r.logical_reads,
r.wait_time,
r.wait_type,
r.wait_resource,
r.reads,
r.writes,
SUBSTRING(st.TEXT, (r.statement_start_offset / 2) + 1,
((CASE r.statement_end_offset
WHEN -1 THEN DATALENGTH(st.TEXT)
ELSE r.statement_end_offset
END - r.statement_start_offset) / 2) + 1) AS statement_text,
COALESCE(QUOTENAME(DB_NAME(st.dbid)) + N'.' + QUOTENAME(OBJECT_SCHEMA_NAME(st.objectid, st.dbid))
+ N'.' + QUOTENAME(OBJECT_NAME(st.objectid, st.dbid)), '') AS command_text,
r.command,
s.login_name,
s.host_name,
s.program_name,
s.last_request_end_time,
s.login_time,
r.open_transaction_count,
atrn.name as transaction_name,
atrn.transaction_id,
atrn.transaction_state
FROM sys.dm_exec_sessions AS s
JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id
CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st
LEFT JOIN (sys.dm_tran_session_transactions AS stran
JOIN sys.dm_tran_active_transactions AS atrn
ON stran.transaction_id = atrn.transaction_id)
ON stran.session_id =s.session_id
WHERE r.session_id != @@SPID
ORDER BY r.cpu_time DESC
SET @cntr = @cntr + 1
WAITFOR DELAY '00:00:05'
END
1.2 Examiner la sortie
Plusieurs scénarios peuvent entraîner l’exécution d’une requête pendant un certain temps : une longue exécution, une longue attente et une compilation longue. Pour plus d’informations sur la raison pour laquelle une requête peut s’exécuter lentement, consultez Exécution et Attente : pourquoi les requêtes sont-elles lentes ?
Temps d’exécution long
Les étapes de résolution des problèmes décrites dans cet article s’appliquent lorsque vous recevez une sortie similaire à la suivante, où le temps processeur augmente proportionnellement au temps écoulé sans temps d’attente significatif.
| session_id | statut | cpu_time_minutes | elapsed_time_minutes | logical_reads | wait_time_minutes | wait_type |
|---|---|---|---|---|---|---|
| 56 | exécution en cours | 64.40 | 23.50 | 0 | 0.00 | NULL |
La requête est en cours d’exécution si elle a :
- Temps processeur croissant
- État ou
runningrunnable - Temps d’attente minimal ou zéro
- Aucune wait_type
Dans ce cas, la requête lit des lignes, jointures, traitement des résultats, calcul ou mise en forme. Ces activités sont toutes les actions liées au processeur.
Note
Les modifications ne logical_reads sont pas pertinentes dans ce cas, car certaines requêtes T-SQL liées au processeur, telles que l’exécution de calculs ou d’une WHILE boucle, peuvent ne pas effectuer de lectures logiques du tout.
Si la requête lente répond à ces critères, concentrez-vous sur la réduction de son exécution. En règle générale, la réduction du runtime implique de réduire le nombre de lignes que la requête doit traiter tout au long de sa vie en appliquant des index, en réécritant la requête ou en mettant à jour les statistiques. Pour plus d’informations, consultez la section Résolution .
Temps d’attente long
Cet article n’est pas applicable aux scénarios d’attente longs. Dans un scénario d’attente, vous pouvez recevoir une sortie semblable à l’exemple suivant dans lequel l’utilisation du processeur ne change pas ou change légèrement, car la session attend une ressource :
| session_id | statut | cpu_time_minutes | elapsed_time_minutes | logical_reads | wait_time_minutes | wait_type |
|---|---|---|---|---|---|---|
| 56 | interrompu | 0.03 | 4.20 | 50 | 4.10 | LCK_M_U |
Le type d’attente indique que la session attend une ressource. Un temps d’attente long et un temps d’attente long indiquent que la session attend la plupart de sa vie pour cette ressource. Le temps processeur court indique que peu de temps a été consacré au traitement de la requête.
Pour résoudre les problèmes de requêtes qui sont longues en raison d’attentes, consultez Résoudre les problèmes liés aux requêtes en cours d’exécution lente dans SQL Server.
Temps de compilation long
Dans de rares cas, vous pouvez observer que l’utilisation du processeur augmente en continu au fil du temps, mais n’est pas pilotée par l’exécution de la requête. Au lieu de cela, une compilation excessivement longue (l’analyse et la compilation d’une requête) peut être la cause. Dans ces cas, vérifiez la transaction_name colonne de sortie pour obtenir la valeur sqlsource_transform. Ce nom de transaction indique une compilation.
Étape 2 : Collecter manuellement les journaux de diagnostic
Après avoir déterminé qu’une requête sans fin existe sur le système, vous pouvez collecter les données de plan de la requête pour résoudre les problèmes supplémentaires. Pour collecter les données, utilisez l’une des méthodes suivantes, en fonction de votre version de SQL Server.
- SQL Server 2008 - SQL Server 2014 (antérieur à SP2)
- SQL Server 2014 (version ultérieure à SP2) et SQL Server 2016 (antérieure à SP1)
- SQL Server 2016 (version ultérieure à SP1) et SQL Server 2017
- SQL Server 2019 et versions ultérieures
Pour collecter des données de diagnostic à l’aide de SQL Server Management Studio (SSMS), procédez comme suit :
Capturez le code XML du plan d’exécution de requête estimé.
Passez en revue le plan de requête pour savoir si les données affichent des indications évidentes de ce qui provoque la lenteur. Voici quelques exemples d’indications classiques :
- Analyses de table ou d’index (examinez les lignes estimées)
- Boucles imbriquées pilotées par un jeu de données de table externe énorme
- Boucles imbriquées qui ont une grande branche dans le côté interne de la boucle
- Pools de tables
- Fonctions dans la
SELECTliste qui prennent beaucoup de temps pour traiter chaque ligne
Si la requête s’exécute plus rapidement à tout moment, vous pouvez capturer les exécutions « rapides » (plan d’exécution XML réel) pour comparer les résultats.
Utiliser SQL LogScout pour capturer des requêtes sans fin
Vous pouvez utiliser SQL LogScout pour capturer les journaux pendant l’exécution d’une requête sans fin. Utilisez le scénario de requête sans fin avec la commande suivante :
.\SQL_LogScout.ps1 -Scenario "NeverEndingQuery" -ServerName "SQLInstance"
Note
Ce processus de capture de journal nécessite que la requête longue consomme au moins 60 secondes de temps processeur.
SQL LogScout capture au moins trois plans de requête pour chaque requête consommatrice de processeur élevée. Vous pouvez trouver des noms de fichiers qui ressemblent servername_datetime_NeverEnding_statistics_QueryPlansXml_Startup_sessionId_#.sqlplanà . Vous pouvez utiliser ces fichiers à l’étape suivante lorsque vous passez en revue les plans pour identifier la raison de l’exécution longue des requêtes.
Étape 3 : Passer en revue les plans collectés
Cette section explique comment passer en revue les données collectées. Il utilise plusieurs plans de requête XML (à l’aide de l’extension .sqlplan) collectés dans Microsoft SQL Server 2016 SP1 et versions ultérieures.
Comparez les plans d’exécution en procédant comme suit :
Ouvrez un fichier de plan d’exécution de requête précédemment enregistré (
.sqlplan).Cliquez avec le bouton droit dans une zone vide du plan d’exécution, puis sélectionnez Comparer le plan d’exécution.
Choisissez le deuxième fichier de plan de requête que vous souhaitez comparer.
Recherchez des flèches épaisses qui indiquent un grand nombre de lignes qui circulent entre les opérateurs. Sélectionnez ensuite l’opérateur avant ou après la flèche, puis comparez le nombre de lignes réelles sur les deux plans.
Comparez les deuxième et troisième plans pour savoir si le plus grand flux de lignes se produit dans les mêmes opérateurs.
Par exemple:
Étape 4 : Résolution
Vérifiez que les statistiques sont mises à jour pour les tables utilisées dans la requête.
Recherchez les recommandations d’index manquantes dans le plan de requête et appliquez les recommandations d’index manquantes.
Simplifiez la requête :
- Utilisez des prédicats plus sélectifs
WHEREpour réduire les données traitées à l’avant. - Dissoyez-le.
- Sélectionnez certaines parties dans des tables temporaires et joignez-les ultérieurement.
- Supprimez
TOP,EXISTSetFAST(T-SQL) dans les requêtes qui s’exécutent pendant longtemps en raison d’un objectif de ligne d’optimiseur.- Vous pouvez également utiliser
DISABLE_OPTIMIZER_ROWGOAL. Pour plus d’informations, consultez Les objectifs de ligne disparus non autorisés.
- Vous pouvez également utiliser
- Évitez d’utiliser des expressions de table courantes (CTEs) dans de tels cas, car elles combinent des instructions dans une seule requête volumineuse.
- Utilisez des prédicats plus sélectifs
Essayez d’utiliser des indicateurs de requête pour produire un meilleur plan :
-
HASH JOINouMERGE JOINindicateur - Indicateur
FORCE ORDER - Indicateur
FORCESEEK RECOMPILE- USE
PLAN N'<xml_plan>'(si vous avez un plan de requête rapide que vous pouvez forcer)
-
Utilisez Magasin des requêtes (QDS) pour forcer un plan connu s’il existe un tel plan et si votre version de SQL Server prend en charge Magasin des requêtes.