Requête s'exécutant différemment sur SQL 2005 vs SQL 2008R2

9

À mon bureau, nous avons une requête qui est assez moche, mais qui fonctionne plutôt bien en production et dans l'environnement de développement (20sec et 4sec respectivement). Cependant, dans notre environnement de test, cela prend plus de 4 heures. SQL2005 (+ derniers correctifs) est en cours de production et de développement. SQL2008R2 est en cours d'exécution dans les tests.

J'ai jeté un coup d'œil au plan de requête, et il montre que SQL2008R2 utilise TempDB, au moyen d'un spouleur de table (spool paresseux) pour stocker les lignes renvoyées du serveur lié. L'étape suivante montre que les boucles imbriquées (anti-semi-jointure gauche) absorbent 96,3% de la requête. La ligne entre les deux opérateurs est à 5 398 Mo!

Le plan de requête pour SQL 2005 ne montre aucune utilisation de tempdb et aucune utilisation d'une anti-semi-jointure gauche.

Ci-dessous, le code aseptisé et les plans d'exécution, le plan 2005 en haut, le 2008R2 en bas.

Qu'est-ce qui cause le ralentissement et le changement drastiques? Je m'attendais à voir un plan d'exécution différent, donc cela ne me dérange pas. Le ralentissement spectaculaire du temps de requête est ce qui me trouble.

Dois-je regarder le matériel sous-jacent, puisque la version 2008R2 utilise tempdb, dois-je voir comment optimiser l'utilisation de cela?

Existe-t-il une meilleure façon d'écrire la requête?

Merci pour l'aide.

    INSERT INTO Table1_GroupLock (iGroupID, dLockedDate)
SELECT 
 Table1.iGroupID,
 GETDATE()
FROM Table1
WHERE 
 NOT EXISTS (
  SELECT 1
  FROM LinkedServer.Database.Table2 Alias2
  WHERE 
   (
    Alias2.FirstName + Alias2.LastName = dbo.fnRemoveNonLetter(Table1.FullName)
    AND NOT dbo.fnRemoveNonLetter(Table1.FullName) IS NULL
    AND NOT Alias2.FirstName IS NULL 
    AND NOT Alias2.LastName  IS NULL
   ) OR (
    Alias2.FamilyName = dbo.fnRemoveNonLetter(Table1.FamilyName)
    AND Alias2.Child1Name = dbo.fnRemoveNonLetter(Table1.Child1Name)
    AND NOT dbo.fnRemoveNonLetter(Table1.FamilyName) IS NULL
    AND NOT dbo.fnRemoveNonLetter(Table1.Child1Name) IS NULL
    AND NOT Alias2.Familyname IS NULL
    AND NOT Alias2.Child1Name IS NULL
   ) OR (
    Alias2.StepFamilyName = dbo.fnRemoveNonLetter(Table1.StepFamilyName)
    AND Alias2.StepFamilyNameChild1 = dbo.fnRemoveNonLetter(Table1.StepFamilyNameChild2)
    AND NOT Alias2.StepFamilyName IS NULL
    AND NOT Alias2.StepFamilyNameChild1 IS NULL
    AND NOT dbo.fnRemoveNonLetter(Table1.StepFamilyName) IS NULL
    AND NOT dbo.fnRemoveNonLetter(Table1.StepFamilyNameChild2) IS NULL
   )  
 ) AND NOT EXISTS (
  SELECT 1
  FROM Table3
  INNER JOIN Table4
   ON Table4.FirstNameType = Table3.FirstNameType 
  INNER JOIN table5
   ON table5.LastNameType = Table3.LastNameType 
  WHERE 
   Table3.iGroupID = Table1.iGroupID
   AND Table3.bIsClosed = 0
   AND Table4.sNameTypeConstant = 'new_lastname'
   AND table5.sFirstNameConstant = 'new_firstname'
 )

SQL-2005


SQL2008R2

:: EDIT :: Exécuté la requête à partir d'une instance SQL2005 différente, à peu près le même plan d'exécution que le "bon". Vous ne savez toujours pas comment les deux versions 2005 fonctionnent mieux sur le serveur lié 2008R2, que les instances 2008R2 vers les instances 2008R2.

Bien que je ne nie pas que le code puisse utiliser certains travaux, si c'était le code qui posait problème, ne verrais-je pas les mêmes plans d'exécution dans tous mes essais? Quelle que soit la version de SQL?

:: EDIT :: J'ai appliqué SP1 et CU3 aux deux instances 2008R2, toujours pas de dés. J'ai spécifiquement défini la collocation sur le serveur lié, pas de dés. J'ai spécifiquement défini les autorisations de mon compte utilisateur pour qu'il soit administrateur système sur les deux instances, pas de dés. Je me suis également souvenu de mes internes et du dépannage de sql server 2008, nous verrons si je peux suivre cela de quelque façon.

Merci à tous pour l'aide et les conseils.

:: EDIT :: J'ai apporté diverses modifications aux autorisations sur le serveur lié. J'ai utilisé des connexions SQL, des connexions de domaine, j'ai emprunté des utilisateurs, j'ai utilisé l'option "être créé en utilisant ce contexte de sécurité". J'ai créé des utilisateurs des deux côtés du serveur lié qui ont des droits d'administrateur système sur le serveur. Je n'ai plus d'idées.

Je voudrais toujours savoir pourquoi SQL2005 exécute la requête si radicalement différente de SQL2008R2. Si c'était la requête qui était mauvaise, je verrais le temps d'exécution de 4 + heures sur SQL2005 et SQL2008R2.

RateControl
la source

Réponses:

5

J'aimerais que vous retravailliez la requête.

Vous avez des problèmes de sargabilité et utilisez même des appels de fonctions scalaires, ce qui nuira également à la requête. Vous souhaiterez peut-être créer une colonne calculée FullName sur Table2 et y placer un index, en vous assurant que votre index INCLUT FirstName et LastName. Vous devez également ajouter des index qui aident l'autre

En outre, créez une fonction table en ligne pour effectuer votre fonctionnalité "RemoveNonLetter" et retravaillez votre requête pour l'utiliser, probablement en utilisant APPLY comme je l'ai fait ici.

Et vérifiez bien ce bug auquel fait référence la réponse de Paul .

INSERT INTO Table1_GroupLock (iGroupID, dLockedDate)
SELECT 
 Table1.iGroupID,
 GETDATE()
FROM Table1
OUTER APPLY (SELECT NonLettersRemoved FROM dbo.ifnRemoveNonLetter(Table1.FullName)) AS fn (FullName)
OUTER APPLY (SELECT NonLettersRemoved FROM dbo.ifnRemoveNonLetter(Table1.FamilyName)) AS famn (FamilyName)
OUTER APPLY (SELECT NonLettersRemoved FROM dbo.ifnRemoveNonLetter(Table1.Child1Name)) AS c1n (Child1Name)
OUTER APPLY (SELECT NonLettersRemoved FROM dbo.ifnRemoveNonLetter(Table1.StepFamilyName)) AS sfn (StepFamilyName)
OUTER APPLY (SELECT NonLettersRemoved FROM dbo.ifnRemoveNonLetter(Table1.StepFamilyNameChild2)) AS sfnc2 (StepFamilyNameChild2)
WHERE 
 NOT EXISTS (
  SELECT 1
  FROM LinkedServer.Database.Table2 Alias2
  WHERE Alias2.FullName = fn.FullName
  UNION ALL
  SELECT 1
  FROM LinkedServer.Database.Table2 Alias2
  WHERE Alias2.FamilyName = famn.FamilyName AND Alias2.Child1Name = c1n.Child1Name
  UNION ALL
  SELECT 1
  FROM LinkedServer.Database.Table2 Alias2
  WHERE Alias2.StepFamilyName = sfn.StepFamilyName AND Alias2.StepFamilyNameChild1 = sfnc2.StepFamilyNameChild2
 ) 
 AND NOT EXISTS (
  SELECT 1
  FROM Table3
  INNER JOIN Table4
   ON Table4.FirstNameType = Table3.FirstNameType 
  INNER JOIN table5
   ON table5.LastNameType = Table3.LastNameType 
  WHERE 
   Table3.iGroupID = Table1.iGroupID
   AND Table3.bIsClosed = 0
   AND Table4.sNameTypeConstant = 'new_lastname'
   AND table5.sFirstNameConstant = 'new_firstname'
 )
;
Rob Farley
la source
6

En plus des réponses précédentes, la raison de la régression du plan peut être due à un bogue d'estimation de cardinalité connu lorsque le plan comprend une Anti Semi Join. Voir KB 2222998

En supposant que le plan 2005 produise des performances acceptables, vous trouverez peut-être que le fait de mettre le serveur à une version qui inclut ce correctif (et d'activer TF4199 pour l'activer) vous ramènera au «bon» plan.

Cela dit, il existe de nombreuses autres opportunités pour améliorer cette requête, donc cela pourrait être le bon moment pour vous concentrer sur cela.

Paul White 9
la source
5

Je dirais que les données distantes sont mises en file d'attente localement parce que l'un des

  1. Les paramètres du serveur lié (tels que le classement) ne sont pas les mêmes
  2. Le classement local n'est pas le même que distant, malgré les paramètres du serveur lié
  3. La requête ne peut pas s'exécuter correctement à distance en raison des autorisations

Pour le point 1, voir sp_serveroption
Et pour le point 2, mais aussi vérifiez les classements serveur / db.

Pour le point 3, voir ceux de Linchi Shea:

Vous demandez à SQL Server de traiter toutes les données localement, selon ma réponse ici: Implications des performances de l'utilisation de OPENQUERY dans une vue

Éditer

Au second regard, je vois 2 appels à distance sur le "bon" plan plutôt qu'un. Cela confirme ce que je dis ici

gbn
la source
Désolé pour le long retard. 1: J'ai vérifié les paramètres du serveur lié, ce sont les mêmes 2: J'ai également vérifié et défini implicitement le classement des propriétés du serveur lié sur le serveur distant. 3: Le contexte de sécurité sur lequel je travaille toujours sur les tests A / B. Je suis toujours en train de trébucher sur la logique derrière les plans d'exécution radicalement différents. Du serveur R2 au serveur R2, il exécute uniquement la sélection (en tirant bien sur 150 000 lignes), puis effectue les jointures. Où comme 2005 à R2, il fait les sélections et les jointures sur le serveur distant. Le contexte de sécurité pour les deux scénarios est le même.
RateControl
3

+1 sur le commentaire Réécrire le commentaire de votre requête à partir du datagod.

Je me demande également si vous rencontrez un problème d'autorisations côté serveur lié conduisant à ce ralentissement. J'ai blogué sur ce ralentissement du serveur lié il y a un moment. Peut-être vaut-il la peine de vérifier les perms (est-ce un serveur lié SQL? Ou est-ce un autre SGBD? Si ce dernier, vous n'obtiendrez pas de bonnes statistiques de toute façon)

Avez-vous encore SQL Server 2005 sur l'environnement de test pour essayer cette requête et exclure l'environnement?

Avez-vous reconstruit les statistiques depuis la mise à niveau?

Mike Walsh
la source
3

Il y a tellement de problèmes avec cette comparaison ... Je ne sais pas par où commencer.

  1. Obtenez les spécifications exactes de vos machines de production et de test.

  2. Déterminez les liens réseau entre les différents serveurs liés dans les deux environnements. Sont-ils à la même vitesse? Les serveurs sont-ils situés côte à côte dans les deux environnements?

  3. Existe-t-il un moyen quelconque de réécrire la requête pour NE PAS utiliser de serveurs liés? Rejoindre des tables sur des serveurs vous rend vulnérable aux changements de topologie, et son horriblement lent dans la plupart des cas.

  4. L'utilisation de NOT et OR conduit généralement à des analyses de table complètes. Essayez de réécrire la requête.

datagod
la source