Demander de l'aide pour améliorer les performances de cette requête.
SQL Server 2008 R2 Enterprise , Max RAM 16 Go, CPU 40, Max Degree of Parallelism 4.
SELECT DsJobStat.JobName AS JobName
, AJF.ApplGroup AS GroupName
, DsJobStat.JobStatus AS JobStatus
, AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) AS ElapsedSecAVG
, AVG(CAST(DsJobStat.CpuMSec AS FLOAT)) AS CpuMSecAVG
FROM DsJobStat, AJF
WHERE DsJobStat.NumericOrderNo=AJF.OrderNo
AND DsJobStat.Odate=AJF.Odate
AND DsJobStat.JobName NOT IN( SELECT [DsAvg].JobName FROM [DsAvg] )
GROUP BY DsJobStat.JobName
, AJF.ApplGroup
, DsJobStat.JobStatus
HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;
Message d'exécution,
(0 row(s) affected)
Table 'AJF'. Scan count 11, logical reads 45, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsAvg'. Scan count 2, logical reads 1926, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsJobStat'. Scan count 1, logical reads 3831235, physical reads 85, read-ahead reads 3724396, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
SQL Server Execution Times:
CPU time = 67268 ms, elapsed time = 90206 ms.
Structure des tableaux:
-- 212271023 rows
CREATE TABLE [dbo].[DsJobStat](
[OrderID] [nvarchar](8) NOT NULL,
[JobNo] [int] NOT NULL,
[Odate] [datetime] NOT NULL,
[TaskType] [nvarchar](255) NULL,
[JobName] [nvarchar](255) NOT NULL,
[StartTime] [datetime] NULL,
[EndTime] [datetime] NULL,
[NodeID] [nvarchar](255) NULL,
[GroupName] [nvarchar](255) NULL,
[CompStat] [int] NULL,
[RerunCounter] [int] NOT NULL,
[JobStatus] [nvarchar](255) NULL,
[CpuMSec] [int] NULL,
[ElapsedSec] [int] NULL,
[StatusReason] [nvarchar](255) NULL,
[NumericOrderNo] [int] NULL,
CONSTRAINT [PK_DsJobStat] PRIMARY KEY CLUSTERED
( [OrderID] ASC,
[JobNo] ASC,
[Odate] ASC,
[JobName] ASC,
[RerunCounter] ASC
));
-- 48992126 rows
CREATE TABLE [dbo].[AJF](
[JobName] [nvarchar](255) NOT NULL,
[JobNo] [int] NOT NULL,
[OrderNo] [int] NOT NULL,
[Odate] [datetime] NOT NULL,
[SchedTab] [nvarchar](255) NULL,
[Application] [nvarchar](255) NULL,
[ApplGroup] [nvarchar](255) NULL,
[GroupName] [nvarchar](255) NULL,
[NodeID] [nvarchar](255) NULL,
[Memlib] [nvarchar](255) NULL,
[Memname] [nvarchar](255) NULL,
[CreationTime] [datetime] NULL,
CONSTRAINT [AJF$PrimaryKey] PRIMARY KEY CLUSTERED
( [JobName] ASC,
[JobNo] ASC,
[OrderNo] ASC,
[Odate] ASC
));
-- 413176 rows
CREATE TABLE [dbo].[DsAvg](
[JobName] [nvarchar](255) NULL,
[GroupName] [nvarchar](255) NULL,
[JobStatus] [nvarchar](255) NULL,
[ElapsedSecAVG] [float] NULL,
[CpuMSecAVG] [float] NULL
);
CREATE NONCLUSTERED INDEX [DJS_Dashboard_2] ON [dbo].[DsJobStat]
( [JobName] ASC,
[Odate] ASC,
[StartTime] ASC,
[EndTime] ASC
)
INCLUDE ( [OrderID],
[JobNo],
[NodeID],
[GroupName],
[JobStatus],
[CpuMSec],
[ElapsedSec],
[NumericOrderNo]) ;
CREATE NONCLUSTERED INDEX [Idx_Dashboard_AJF] ON [dbo].[AJF]
( [OrderNo] ASC,
[Odate] ASC
)
INCLUDE ( [SchedTab],
[Application],
[ApplGroup]) ;
CREATE NONCLUSTERED INDEX [DsAvg$JobName] ON [dbo].[DsAvg]
( [JobName] ASC
)
Plan d'exécution:
https://www.brentozar.com/pastetheplan/?id=rkUVhMlXM
Mettre à jour après avoir obtenu une réponse
Merci beaucoup @Joe Obbish
Vous avez raison sur le problème de cette requête qui concerne environ DsJobStat et DsAvg. Il ne s'agit pas de savoir comment rejoindre et ne pas utiliser NOT IN.
Il y a bien une table comme vous l'avez deviné.
CREATE TABLE [dbo].[DSJobNames](
[JobName] [nvarchar](255) NOT NULL,
CONSTRAINT [DSJobNames$PrimaryKey] PRIMARY KEY CLUSTERED
( [JobName] ASC
) );
J'ai essayé ta suggestion,
SELECT DsJobStat.JobName AS JobName
, AJF.ApplGroup AS GroupName
, DsJobStat.JobStatus AS JobStatus
, AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) AS ElapsedSecAVG
, Avg(CAST(DsJobStat.CpuMSec AS FLOAT)) AS CpuMSecAVG
FROM DsJobStat
INNER JOIN DSJobNames jn
ON jn.[JobName]= DsJobStat.[JobName]
INNER JOIN AJF
ON DsJobStat.Odate=AJF.Odate
AND DsJobStat.NumericOrderNo=AJF.OrderNo
WHERE NOT EXISTS ( SELECT 1 FROM [DsAvg] WHERE jn.JobName = [DsAvg].JobName )
GROUP BY DsJobStat.JobName, AJF.ApplGroup, DsJobStat.JobStatus
HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;
Message d'exécution:
(0 row(s) affected)
Table 'DSJobNames'. Scan count 5, logical reads 1244, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsAvg'. Scan count 5, logical reads 2129, physical reads 0, read-ahead reads 24, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsJobStat'. Scan count 8, logical reads 84, physical reads 0, read-ahead reads 83, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AJF'. Scan count 5, logical reads 757999, physical reads 944, read-ahead reads 757311, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
SQL Server Execution Times:
CPU time = 21776 ms, elapsed time = 33984 ms.
Plan d'exécution: https://www.brentozar.com/pastetheplan/?id=rJVkLSZ7f
Réponses:
Commençons par considérer l'ordre de jointure. Vous avez trois références de table dans la requête. Quel ordre de jointure pourrait vous donner les meilleures performances? L'optimiseur de requêtes pense que la jointure de
DsJobStat
àDsAvg
éliminera presque toutes les lignes (les estimations de cardinalité tombent de 212195000 à 1 ligne). Le plan actuel nous montre que l'estimation est assez proche de la réalité (11 rangées survivent à la jointure). Cependant, la jointure est implémentée comme une jointure anti semi-fusion droite, de sorte que les 212 millions de lignes de laDsJobStat
table sont analysées juste pour produire 11 lignes. Cela pourrait certainement contribuer au long temps d'exécution des requêtes, mais je ne peux pas penser à un meilleur opérateur physique ou logique pour cette jointure qui aurait été mieux. Je suis sûr que leDJS_Dashboard_2
L'index est utilisé pour d'autres requêtes, mais toutes les clés supplémentaires et les colonnes incluses nécessiteront simplement plus d'E / S pour cette requête et vous ralentiront. Vous avez donc potentiellement un problème d'accès à la table avec l'analyse d'index sur laDsJobStat
table.Je vais supposer que la jointure
AJF
n'est pas très sélective. Il n'est actuellement pas pertinent pour les problèmes de performances que vous voyez dans la requête, donc je vais l'ignorer pour le reste de cette réponse. Cela pourrait changer si les données du tableau changent.L'autre problème qui ressort du plan est l'opérateur de spoule de comptage de lignes. Il s'agit d'un opérateur très léger, mais il s'exécute plus de 200 millions de fois. L'opérateur est là car la requête est écrite avec
NOT IN
. S'il y a une seule ligne NULL,DsAvg
toutes les lignes doivent être supprimées. Le spool est l'implémentation de cette vérification. Ce n'est probablement pas la logique que vous souhaitez, alors vous feriez mieux d'écrire cette partie à utiliserNOT EXISTS
. L'avantage réel de cette réécriture dépendra de votre système et de vos données.J'ai simulé certaines données basées sur le plan de requête pour tester quelques réécritures de requête. Mes définitions de table sont considérablement différentes des vôtres, car il aurait fallu trop d'efforts pour simuler des données pour chaque colonne. Même avec les structures de données abrégées, j'ai pu reproduire le problème de performances que vous rencontrez.
Sur la base du plan de requête, nous pouvons voir qu'il y a environ 200 000
JobName
valeurs uniques dans leDsAvg
tableau. Sur la base du nombre réel de lignes après la jointure à cette table, nous pouvons voir que presque toutes lesJobName
valeurs deDsJobStat
sont également dans laDsAvg
table. Ainsi, laDsJobStat
table a 200001 valeurs uniques pour laJobName
colonne et 1000 lignes par valeur.Je crois que cette requête représente le problème de performances:
Toutes les autres choses dans votre plan de requête (
GROUP BY
,HAVING
, style ancien rejoindre, etc.) se produit après que le jeu de résultats a été réduit à 11 lignes. Actuellement, cela n'a pas d'importance du point de vue des performances des requêtes, mais il pourrait y avoir d'autres problèmes qui pourraient être révélés par des données modifiées dans vos tables.Je teste dans SQL Server 2017, mais j'ai la même forme de plan de base que vous:
Sur ma machine, cette requête prend 62219 ms de temps CPU et 65576 ms de temps écoulé pour s'exécuter. Si je réécris la requête à utiliser
NOT EXISTS
:Le spool n'est plus exécuté 212 millions de fois et il a probablement le comportement souhaité du fournisseur. Maintenant, la requête s'exécute en 34516 ms de temps CPU et 41132 ms de temps écoulé. La majorité du temps est consacrée à l'analyse de 212 millions de lignes à partir de l'index.
Cette analyse d'index est très regrettable pour cette requête. En moyenne, nous avons 1000 lignes par valeur unique de
JobName
, mais nous savons après lecture de la première ligne si nous aurons besoin des 1000 lignes précédentes. Nous n'avons presque jamais besoin de ces lignes, mais nous devons quand même les analyser. Si nous savons que les lignes ne sont pas très denses dans la table et que presque toutes seront éliminées par la jointure, nous pouvons imaginer un modèle d'E / S éventuellement plus efficace sur l'index. Que se passe-t-il si SQL Server lit la première ligne par valeur unique deJobName
, vérifie si cette valeur est dedansDsAvg
et passe simplement à la valeur suivante deJobName
si c'est le cas? Au lieu d'analyser 212 millions de lignes, un plan de recherche nécessitant environ 200 000 exécutions pourrait être effectué à la place.Cela peut principalement être accompli en utilisant la récursivité avec une technique que Paul White a inventée et qui est décrite ici . Nous pouvons utiliser la récursivité pour faire le modèle d'E / S que j'ai décrit ci-dessus:
Cette requête est beaucoup à examiner, je recommande donc d'examiner attentivement le plan réel . D'abord, nous faisons 200002 index cherche contre l'index
DsJobStat
pour obtenir toutes lesJobName
valeurs uniques . Ensuite, nous nous joignons àDsAvg
et éliminons toutes les lignes sauf une. Pour la ligne restante, rejoignezDsJobStat
et obtenez toutes les colonnes requises.Le modèle d'E / S change totalement. Avant d'avoir ceci:
Avec la requête récursive, nous obtenons ceci:
Sur ma machine, la nouvelle requête s'exécute en seulement 6891 ms de temps CPU et 7107 ms de temps écoulé. Notez que la nécessité d'utiliser la récursion de cette manière suggère que quelque chose manque dans le modèle de données (ou peut-être qu'il n'était simplement pas indiqué dans la question publiée). S'il existe une table relativement petite qui contient tout
JobNames
ce qui est possible, il vaudra beaucoup mieux utiliser cette table que la récursivité sur la grande table. Cela revient à dire que si vous avez un jeu de résultats contenant tout ceJobNames
dont vous avez besoin, vous pouvez utiliser l'index pour obtenir le reste des colonnes manquantes. Cependant, vous ne pouvez pas le faire avec un ensemble de résultatsJobNames
dont vous n'avez PAS besoin.la source
NOT EXISTS
. Ils ont déjà répondu par "J'ai déjà essayé les deux, adhérer et n'existe pas, avant de poster une question. Pas beaucoup de différence."Voyez ce qui se passe si vous réécrivez la condition,
À
Pensez également à réécrire votre jointure SQL89 car ce style est horrible.
Au lieu de
Essayer
Je soupçonne également que cette condition peut être mieux écrite, mais nous devons en savoir plus sur ce qui se passe
Faut-il vraiment savoir que la moyenne n'est pas nulle, ou simplement qu'un élément du groupe n'est pas nul?
la source