2011-02-15 6 views
0

J'ai une application ASP.NET qui a la requête SQL suivante dans une procédure stockée:SQL Server stockées performances proc

SELECT @result = COUNT(id) 
FROM course_tracking 
WHERE employee_number = @employee_number 
AND course_code = @course_code 

IF @result = 0 
BEGIN 
    INSERT INTO COURSE TRACKING 
END 
ELSE 
    UPDATE COURSE TRACKING 

Le tableau Course Tracking a près d'un million de lignes dans et Générateur de profils SQL montre entre 40k et 50k lectures chaque fois que ce proc est exécuté qui est une fois jamais quelques secondes.

Depuis que je ne suis pas un perf de SQL Server ... quelle est la meilleure façon de corriger cela?

Toute aide est grandement appréciée.

Merci

+1

Assurez-vous que '' course_code' et employee_number' sont tous deux indexés . Un indice de couverture devrait fonctionner correctement. – JNK

+3

Quelle version de SQL Server? Parce que SQLServer 2008 vous donne la capacité 'MERGE' qui est idéale pour les upserts comme celui-ci. – Matthew

+0

@JNK, n'aura-t-il pas à inclure la colonne id pour le compte? – DForck42

Répondre

1

Alex,

Il peut y avoir un certain nombre de raisons pour la grande quantité de lectures, et je crois que @ la réponse de Jonathan vous aidera ou masquer le problème pour l'instant . La raison pour obtenir beaucoup de lectures sera parce qu'il vous manque certains index critiques, et/ou que les statistiques de votre table ne sont pas à jour, ou qu'elles sont désactivées.

Il peut être compliqué d'effectuer un réglage de précision sur une grande table, si elle est utilisée par de nombreuses autres procédures, vues et requêtes. Parce que la modification d'un index peut avoir un impact considérable sur d'autres requêtes qui fonctionnent bien maintenant, mais si votre table ne dispose d'aucun index, toute modification pourrait vous aider. Les choses à considérer lorsque vous essayez d'optimiser quelque chose comme ceci est:

  1. Comment souvent cette requête a couru
  2. Quand il fonctionne de t-elle un impact sur d'autres processus sur le système
  3. Quelle est l'opération la plus fréquente sur cette table, inserts ou mises à jour?

Si cette requête est exécutée une fois par jour, ou très rarement, et qu'elle n'a pas d'impact sur d'autres processus, je pourrais être enclin à la quitter. Mais si c'est un processus qui fonctionne très fréquemment et/ou qui a un impact sur d'autres processus, alors vous devez le réparer. Maintenant, en le corrigeant, il peut être aussi simple que d'ajouter un indice de table WITH(NOLOCK) si vous trouvez qu'il bloque, mais s'il est exécuté très fréquemment, regardez les index et les statistiques.

Si vous avez beaucoup d'insertions sur cette table, alors tout index supplémentaire aura un impact sur les performances d'insertion, mais cela accélérera les mises à jour. Puis vient le facteur de remplissage de l'index, etc. Mon point est que le réglage de perf sur une boîte Sql est très compliqué, et nécessite la totalité de l'image la plupart du temps. Donc, ce que je suggérerais, c'est que vous preniez quelques indices ici et, parce que personne ne connaît votre système aussi bien que vous, vous pouvez prendre des décisions, expérimenter et en tirer des leçons! Activez votre plan d'exécution de requête et regardez-le, voyez si vous voyez des scans de table, ils sont mauvais, et indiquent un manque d'index appropriés, tout comme les recherches de clés.

Mais en regardant votre requête que vous avez donnée ici et en ignorant tous les autres facteurs, voici ce que je suggérerais.

Assurez-vous d'avoir un index NON CLUSTERED ayant employee_number et course_code dans l'index. Si c'est Sql 2008, vous pouvez également ajouter des colonnes incluses si vous avez d'autres requêtes filtrant sur ces deux colonnes, mais en produisant d'autres colonnes. Aussi loin qu'ils devraient être ascendants ou descendants dans l'index est à vous, mais laissez-les tous les deux comme ascendants.

Ensuite, pour la requête, utilisez ce que @Jonathan (+1 pour vous!) A proposé, avec un changement mineur, ajoutez un indice WITH (NOLOCK) sur la table. Cela indiquera à l'optimiseur Sql de ne pas verrouiller la table quand il la lit, mais vous pourrez lire les données corrompues. Normalement, ce n'est pas un problème, à moins qu'il s'agisse d'une table très transactionnelle, de nombreuses mises à jour et insertions en cours.

IF EXISTS(SELECT 1 FROM course_tracking WITH(NOLOCK) 
WHERE employee_number = @employee_number 
     AND course_code = @course_code) 
BEGIN 
    UPDATE COURSE TRACKING 
END 
ELSE 
BEGIN 
    INSERT INTO COURSE TRACKING 
END 

également, vérifiez si vos statistiques sont automatiquement mises à jour sur la base de données, sinon, créez un travail de maintenance qui mettra à jour une fois par jour, lorsque le serveur est moins actif. Créer un emploi Agent SQL avec ce que la requête EXEC sp_MSForEachTable 'UPDATE STATISTICS ? WITH FULLSCAN'

+0

Merci pour l'explication complète – Alex

4

Essayez cette

IF EXISTS(SELECT 1 FROM course_tracking 
WHERE employee_number = @employee_number 
AND course_code = @course_code) 
BEGIN 
    UPDATE COURSE TRACKING 
END 
ELSE 
BEGIN 
    INSERT INTO COURSE TRACKING 
END 
0

Si SQL 2008, la syntaxe MERGE est bonne. Pinal Dave a une bonne visite virtuelle http://blog.sqlauthority.com/2008/08/28/sql-server-2008-introduction-to-merge-statement-one-statement-for-insert-update-delete/

MERGE course_tracking AS c 
ON c.employee_number = @employee_number 
AND c.course_code = @course_code 
WHEN MATCHED THEN 
UPDATE STUFF 
WHEN NOT MATCHED THEN 
INSERT STUFF 
GO 
+0

La syntaxe est bonne, mais sans les index appropriés et la mauvaise conception de la table, cela masquera seulement le problème, s'il le masque du tout. – Ryk

+0

@Ryk sans une bonne conception et des indices aucune syntaxe upsert n'aura d'importance. C'est, au moins, une méthode concise sur une base de données correcte. – Matthew

+0

Je suis d'accord que c'est concis, mais perf sage, il est sans doute le même que dans une condition IF..ELSE, et à mon avis beaucoup plus lisible. Mais cela revient à un goût et une lisibilité personnels. – Ryk

1

Si votre requête est mise à jour en fait les lignes WHERE employee_number = @employee_number AND course_code = @course_code), alors vous pouvez essayer cette technique:

UPDATE course_tracking 
SET ... 
WHERE employee_number = @employee_number 
    AND course_code = @course_code; 
IF @@ROWCOUNT = 0 
    INSERT INTO course_tracking 
    ... 
+0

Ceci est inférieur à la réponse «EXISTS» ci-dessus – Matthew

+0

@Matthew PK: Il y a deux d'entre eux, mais presque la même chose. De toute façon, je ne le pense pas. Avec l'approche EXISTS, la table est recherchée deux fois si la condition est remplie. Avec la solution que j'offre, cela n'arrive qu'une seule fois. Qu'est-ce que je rate? –

+0

Je peux me tromper et je m'excuse si ... Je m'attendrais à ce que 'EXISTS' soit plus performant parce qu'il peut arrêter de chercher après avoir trouvé un match alors que votre approche continuera un balayage complet (sauf s'il y a une règle pour l'unicité en place) – Matthew

Questions connexes