En ce qui concerne la méthodologie, je crois que vous aboyez le mauvais arbre b ;-).
Ce que nous savons:
Tout d'abord, consolidons et examinons ce que nous savons de la situation:
Ce que nous pouvons supposer:
Ensuite, nous pouvons examiner tous ces points de données ensemble pour voir si nous pouvons synthétiser des détails supplémentaires qui nous aideront à trouver un ou plusieurs goulots d'étranglement, et soit pointer vers une solution, soit au moins exclure certaines solutions possibles.
L'orientation actuelle de la réflexion dans les commentaires est que le principal problème est le transfert de données entre SQL Server et Excel. Est-ce vraiment le cas? Si la procédure stockée est appelée pour chacune des 800 000 lignes et prend 50 ms par chaque appel (c'est-à-dire par chaque ligne), cela ajoute jusqu'à 40 000 secondes (et non ms). Et cela équivaut à 666 minutes (hhmm ;-), soit un peu plus de 11 heures. Pourtant, l'ensemble du processus ne durerait que 7 heures. Nous avons déjà 4 heures sur le temps total, et nous avons même ajouté du temps pour faire les calculs ou sauvegarder les résultats dans SQL Server. Donc, quelque chose ne va pas ici.
En regardant la définition de la procédure stockée, il n'y a qu'un paramètre d'entrée pour @FileID
; il n'y a pas de filtre @RowID
. Je soupçonne donc que l'un des deux scénarios suivants se produit:
- Cette procédure stockée n'est pas réellement appelée pour chaque ligne, mais plutôt pour chaque ligne
@FileID
, qui semble s'étendre sur environ 4 000 lignes. Si les 4000 lignes retournées sont un montant assez cohérent, alors il n'y a que 200 de ces groupes dans les 800 000 lignes. Et 200 exécutions de 50 ms chacune ne représentent que 10 secondes sur ces 7 heures.
- Si cette procédure stockée est effectivement appelée pour chaque ligne, la première fois qu'une nouvelle
@FileID
est passée ne prendrait pas un peu plus de temps pour tirer de nouvelles lignes dans le pool de tampons, mais les 3999 prochaines exécutions reviendraient généralement plus rapidement car elles sont déjà mis en cache, non?
Je pense que se concentrer sur cette procédure stockée «filtre», ou tout transfert de données de SQL Server vers Excel, est un redingue .
Pour le moment, je pense que les indicateurs les plus pertinents de performances médiocres sont:
- Il y a 800 000 lignes
- L'opération fonctionne sur une ligne à la fois
- Les données sont enregistrées sur SQL Server, d'où "[utilise] les valeurs de certaines colonnes pour manipuler d'autres colonnes " [mes phases sont ;-)]
Je soupçonne que:
- bien qu'il y ait une marge d'amélioration sur la récupération et les calculs des données, les améliorer ne représenterait pas une réduction significative du temps de traitement.
- le principal goulot d'étranglement est l'émission de 800 000
UPDATE
relevés distincts , soit 800 000 transactions distinctes.
Ma recommandation (basée sur les informations actuellement disponibles):
Votre principal domaine d'amélioration serait de mettre à jour plusieurs lignes à la fois (c'est-à-dire en une seule transaction). Vous devez mettre à jour votre processus pour travailler en termes de chacun FileID
au lieu de chacun RowID
. Donc:
- lire dans les 4000 lignes d'un particulier
FileID
dans un tableau
- le tableau doit contenir des éléments représentant les champs manipulés
- parcourir le tableau, en traitant chaque ligne comme vous le faites actuellement
- une fois que toutes les lignes du tableau (c'est-à-dire pour ce point particulier
FileID
) ont été calculées:
- démarrer une transaction
- appeler chaque mise à jour pour chaque
RowID
- si aucune erreur, valider la transaction
- en cas d'erreur, annulez et gérez correctement
Si votre index cluster n'est pas déjà défini comme (FileID, RowID)
alors vous devriez considérer cela (comme l'a suggéré @MikaelEriksson dans un commentaire sur la question). Cela n'aidera pas ces mises à jour singleton, mais cela améliorerait au moins légèrement les opérations d'agrégation, telles que ce que vous faites dans cette procédure stockée de "filtrage" car elles sont toutes basées sur FileID
.
Vous devriez envisager de déplacer la logique vers un langage compilé. Je suggère de créer une application .NET WinForms ou même une application console. Je préfère l'application console car elle est facile à planifier via l'agent SQL ou les tâches planifiées de Windows. Peu importe que cela soit fait en VB.NET ou en C #. VB.NET pourrait être un ajustement plus naturel pour votre développeur, mais il y aura toujours une courbe d'apprentissage.
Je ne vois aucune raison pour l'instant de passer au SQLCLR. Si l'algorithme change fréquemment, cela deviendrait ennuyeux de devoir redéployer l'assembly tout le temps. Reconstruire une application console et placer le fichier .exe dans le dossier partagé approprié sur le réseau de sorte que vous exécutez simplement le même programme et qu'il se trouve qu'il soit toujours à jour, devrait être assez facile à faire.
Je ne pense pas que le déplacement complet du traitement dans T-SQL aiderait si le problème est ce que je soupçonne et que vous ne faites qu'une seule MISE À JOUR à la fois.
Si le traitement est déplacé dans .NET, vous pouvez ensuite utiliser des paramètres table (TVP) de sorte que vous passiez le tableau dans une procédure stockée qui appellerait un UPDATE
qui se joint à la variable de table TVP et est donc une transaction unique . Le TVP devrait être plus rapide que de faire 4000 INSERT
s regroupés en une seule transaction. Mais le gain provenant de l'utilisation de TVP de plus de 4000 INSERT
s en 1 transaction ne sera probablement pas aussi important que l'amélioration observée lors du passage de 800000 transactions distinctes à seulement 200 transactions de 4000 lignes chacune.
L'option TVP n'est pas nativement disponible pour le côté VBA, mais quelqu'un a trouvé une solution qui pourrait valoir la peine d'être testée:
Comment puis-je améliorer les performances de la base de données lors du passage de VBA à SQL Server 2008 R2?
SI le filtre proc n'utilise que FileID
dans la WHERE
clause, et si ce proc est réellement appelé pour chaque ligne, vous pouvez gagner du temps de traitement en mettant en cache les résultats de la première exécution et en les utilisant pour le reste des lignes par cela FileID
, droite?
Une fois que vous obtenez le traitement fait par FileID , alors nous pouvons commencer à parler de traitement parallèle. Mais cela pourrait ne pas être nécessaire à ce stade :). Étant donné que vous avez affaire à 3 parties non idéales assez importantes: les transactions Excel, VBA et 800k, toute discussion sur SSIS, ou parallélogrammes, ou qui sait quoi, est une optimisation prématurée / des trucs de type chariot avant le cheval . Si nous pouvons réduire ce processus de 7 heures à 10 minutes ou moins, pensez-vous toujours à des moyens supplémentaires pour l'accélérer? Y a-t-il un délai d'achèvement que vous avez en tête? Gardez à l'esprit qu'une fois le traitement effectué sur un ID de fichier Si vous aviez une application console VB.NET (par exemple, la ligne de commande .EXE), rien ne vous empêcherait d'exécuter quelques-uns de ces FileID à la fois :), que ce soit via l'étape SQL Agent CmdExec ou les tâches planifiées Windows, etc.
ET, vous pouvez toujours adopter une approche "progressive" et apporter quelques améliorations à la fois. Telles que commencer par faire les mises à jour par FileID
et donc utiliser une transaction pour ce groupe. Ensuite, voyez si vous pouvez faire fonctionner le TVP. Ensuite, voyez comment prendre ce code et le déplacer vers VB.NET (et les TVP fonctionnent dans .NET pour qu'il soit bien porté).
Ce que nous ne savons pas qui pourrait encore aider:
- Le « filtre » run procédure stockée par RowID ou par FileID ? Avons-nous même la définition complète de cette procédure stockée?
- Schéma complet de la table. Quelle est la largeur de cette table? Combien de champs de longueur variable existe-t-il? Combien de champs sont NULLables? Si certains sont NULLable, combien contiennent des NULL?
- Index de ce tableau. Est-il partitionné? La compression ROW ou PAGE est-elle utilisée?
- Quelle est la taille de ce tableau en termes de Mo / Go?
- Comment la maintenance d'index est-elle gérée pour cette table? Dans quelle mesure les index sont-ils fragmentés? Quelle est la mise à jour des statistiques à ce jour?
- D'autres processus écrivent-ils dans ce tableau pendant ce processus de 7 heures? Source possible de conflit.
- D'autres processus sont-ils lus dans ce tableau pendant ce processus de 7 heures? Source possible de conflit.
MISE À JOUR 1:
** Il semble y avoir une certaine confusion sur ce que VBA (Visual Basic pour Applications) et ce qui peut être fait avec, donc c'est juste pour nous assurer que nous sommes tous sur la même page Web:
MISE À JOUR 2:
Un autre point à considérer: comment les connexions sont-elles gérées? Le code VBA ouvre-t-il et ferme-t-il la connexion pour chaque opération, ou ouvre-t-il la connexion au début du processus et la ferme-t-il à la fin du processus (c'est-à-dire 7 heures plus tard)? Même avec le regroupement de connexions (qui, par défaut, devrait être activé pour ADO), il devrait toujours y avoir un certain impact entre l'ouverture et la fermeture une fois par opposition à l'ouverture et la fermeture 800 800 ou 1 600 000 fois. Ces valeurs sont basées sur au moins 800 000 MISES À JOUR plus 200 ou 800 000 EXEC (selon la fréquence à laquelle la procédure stockée de filtrage est réellement exécutée).
Ce problème de trop de connexions est automatiquement atténué par la recommandation que j'ai décrite ci-dessus. En créant une transaction et en effectuant toutes les MISES À JOUR au sein de cette transaction, vous allez garder cette connexion ouverte et la réutiliser pour chacune UPDATE
. Que la connexion soit maintenue ouverte depuis l'appel initial pour obtenir les 4000 lignes par spécifiée FileID
, ou fermée après cette opération "get" et rouverte pour les MISES À JOUR, a beaucoup moins d'impact car nous parlons maintenant d'une différence de 200 ou 400 connexions au total sur l'ensemble du processus.
MISE À JOUR 3:
J'ai fait quelques tests rapides. Veuillez garder à l'esprit qu'il s'agit d'un test à petite échelle, et pas exactement la même opération (INSERT pur vs EXEC + UPDATE). Cependant, les différences de calendrier liées à la façon dont les connexions et les transactions sont traitées sont toujours pertinentes, par conséquent, les informations peuvent être extrapolées pour avoir un impact relativement similaire ici.
Paramètres de test:
- SQL Server 2012 Developer Edition (64 bits), SP2
Table:
CREATE TABLE dbo.ManyInserts
(
RowID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
InsertTime DATETIME NOT NULL DEFAULT (GETDATE()),
SomeValue BIGINT NULL
);
Opération:
INSERT INTO dbo.ManyInserts (SomeValue) VALUES ({LoopIndex * 12});
- Inserts totaux pour chaque test: 10 000
- Réinitialise pour chaque test:
TRUNCATE TABLE dbo.ManyInserts;
(étant donné la nature de ce test, faire les FREEPROCCACHE, FREESYSTEMCACHE et DROPCLEANBUFFERS ne semblait pas ajouter beaucoup de valeur.)
- Modèle de récupération: SIMPLE (et peut-être 1 Go gratuit dans le fichier journal)
- Les tests qui utilisent des transactions n'utilisent qu'une seule connexion, quel que soit le nombre de transactions.
Résultats:
Test Milliseconds
------- ------------
10k INSERTs across 10k Connections 3968 - 4163
10k INSERTs across 1 Connection 3466 - 3654
10k INSERTs across 1 Transaction 1074 - 1086
10k INSERTs across 10 Transactions 1095 - 1169
Comme vous pouvez le voir, même si la connexion ADO à la base de données est déjà partagée entre toutes les opérations, leur regroupement en lots à l'aide d'une transaction explicite (l'objet ADO devrait être capable de gérer cela) est garanti de manière significative (c'est-à-dire plus de 2x amélioration) réduire le temps de traitement global.