J'ai un algorithme que je dois exécuter contre chaque ligne d'une table avec 800K lignes et 38 colonnes. L'algorithme est implémenté dans VBA et fait un tas de mathématiques en utilisant les valeurs de certaines colonnes pour manipuler d'autres colonnes.
J'utilise actuellement Excel (ADO) pour interroger SQL et utiliser VBA avec des curseurs côté client pour appliquer l'algorithme en boucle sur chaque ligne. Il fonctionne mais prend 7 heures pour fonctionner.
Le code VBA est suffisamment complexe pour qu'il soit beaucoup de travail de le recoder en T-SQL.
J'ai lu sur l'intégration du CLR et les UDF comme itinéraires possibles. J'ai également pensé à mettre le code VBA dans une tâche de script SSIS pour se rapprocher de la base de données, mais je suis sûr qu'il existe une méthodologie experte pour ce type de problème de performances.
Idéalement, je serais en mesure d'exécuter l'algorithme sur autant de lignes (toutes?) Que possible d'une manière basée sur un ensemble parallèle.
Toute aide dépendait grandement de la manière d'obtenir les meilleures performances avec ce type de problème.
--Éditer
Merci pour les commentaires, j'utilise MS SQL 2014 Enterprise, voici quelques détails supplémentaires:
L'algorithme trouve des modèles caractéristiques dans les données de séries chronologiques. Les fonctions de l'algorithme effectuent un lissage polynomial, un fenêtrage et trouvent des régions d'intérêt en fonction de critères d'entrée, renvoyant une douzaine de valeurs et quelques résultats booléens.
Ma question concerne plus la méthodologie que l'algorithme réel: si je veux réaliser un calcul parallèle sur plusieurs lignes à la fois, quelles sont mes options.
Je vois que le recodage en T-SQL est recommandé, ce qui est beaucoup de travail mais possible, mais le développeur de l'algorithme fonctionne en VBA et il change fréquemment, donc je devrais rester synchronisé avec la version T-SQL et revalider chaque changement.
T-SQL est-il le seul moyen d'implémenter des fonctions basées sur des ensembles?
la source
N
lots et exécuter desN
instances de votre algorithme surN
des processeurs / ordinateurs distincts. D'un autre côté, quel est votre principal goulot d'étranglement - le transfert des données de SQL Server vers Excel ou des calculs réels? Si vous modifiez la fonction VBA pour renvoyer immédiatement un résultat fictif, combien de temps le processus prendra-t-il? Si cela prend encore des heures, le goulot d'étranglement est dans le transfert de données. Si cela prend quelques secondes, vous devez optimiser le code VBA qui effectue les calculs.SELECT AVG([AD_Sensor_Data]) OVER (ORDER BY [RowID] ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING) as 'AD_Sensor_Data' FROM [AD_Points] WHERE [FileID] = @FileID ORDER BY [RowID] ASC
dans Management Studio, cette fonction qui est appelée pour chacune des lignes prend 50 ms(FileID, RowID)
.Réponses:
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:
Il existe une procédure stockée qui est appelée pour chaque ligne:
La définition (au moins en partie) est:
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:@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.@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:
Je soupçonne que:
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 chacunRowID
. Donc:FileID
dans un tableauFileID
) ont été calculées:RowID
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 surFileID
.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 4000INSERT
s regroupés en une seule transaction. Mais le gain provenant de l'utilisation de TVP de plus de 4000INSERT
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 laWHERE
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 celaFileID
, 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:
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éeFileID
, 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:
Table:
Opération:
TRUNCATE TABLE dbo.ManyInserts;
(étant donné la nature de ce test, faire les FREEPROCCACHE, FREESYSTEMCACHE et DROPCLEANBUFFERS ne semblait pas ajouter beaucoup de valeur.)Résultats:
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.
la source
À mon humble avis et en partant de l'hypothèse qu'il n'est pas possible de recoder le sous-VBA en SQL, avez-vous envisagé de permettre au script VBA de terminer l'évaluation dans le fichier Excel, puis de réécrire les résultats sur le serveur SQL via SSIS?
Vous pouvez faire démarrer et terminer le sous-VBA en retournant un indicateur dans un objet de système de fichiers ou sur le serveur (si vous avez déjà configuré la connexion pour réécrire sur le serveur), puis utilisez une expression SSIS pour vérifier cet indicateur pour le
disable
propriété d'une tâche donnée dans votre solution SSIS (de sorte que le processus d'importation attend la fin du sous-VBA si vous craignez qu'il ne dépasse son planning).De plus, vous pouvez faire démarrer le script VBA par programmation (un peu bancal, mais j'ai utilisé la
workbook_open()
propriété pour déclencher des tâches de type "tirer et oublier" de cette nature dans le passé).Si le temps d'évaluation du script VB commence à devenir un problème, vous pouvez voir si votre développeur VB est disposé et capable de porter son code dans une tâche de script VB dans la solution SSIS - d'après mon expérience, l'application Excel tire beaucoup de frais lorsque travailler avec des données à ce volume.
la source