web-dev-qa-db-fra.com

Procédures stockées vs SQL en ligne

Je sais que les procédures stockées sont plus efficaces via le chemin d'exécution (que le sql en ligne dans les applications). Cependant, une fois pressé, je ne sais pas trop pourquoi.

Je voudrais connaître le raisonnement technique pour cela (d'une manière que je puisse expliquer à quelqu'un plus tard).

Quelqu'un peut-il m'aider à formuler une bonne réponse?

28
webdad3

Je crois que ce sentiment était vrai à un moment donné, mais pas dans les versions actuelles de SQL Server. Tout le problème était que dans le passé, les instructions SQL ad hoc ne pouvaient pas être correctement optimisées car SQL Server ne pouvait optimiser/compiler qu'au niveau du lot. Nous avons maintenant une optimisation au niveau de l'instruction, de sorte qu'une requête correctement paramétrée provenant d'une application peut bénéficier du même plan d'exécution que cette requête incorporée dans une procédure stockée.

Je préfère toujours les procédures stockées du côté DBA pour les raisons suivantes (et plusieurs d'entre elles peuvent avoir un impact énorme sur les performances):

  • Si j'ai plusieurs applications qui réutilisent les mêmes requêtes, une procédure stockée encapsule cette logique, plutôt que de jeter la même requête ad hoc plusieurs fois dans différentes bases de code. Les applications qui réutilisent les mêmes requêtes peuvent également être sujettes à une surcharge du plan, sauf si elles sont copiées textuellement. Même les différences de casse et d'espace blanc peuvent entraîner le stockage de plusieurs versions du même plan (gaspillage).
  • Je peux inspecter et dépanner ce qu'une requête fait sans avoir accès au code source de l'application ou exécuter des traces coûteuses pour voir exactement ce que fait l'application.
  • Je peux également contrôler (et savoir à l'avance) quelles requêtes l'application peut exécuter, à quelles tables elle peut accéder et dans quel contexte, etc. Si les développeurs écrivent des requêtes ad hoc dans leur application, ils devront soit viens tirer ma manche de chemise chaque fois qu'ils ont besoin d'accéder à une table que je ne connaissais pas ou que je ne pouvais pas prédire, ou si je suis moins responsable/enthousiaste et/ou soucieux de la sécurité, je vais juste promouvoir cela utilisateur à dbo afin qu'ils arrêtent de m'embêter. Cela se fait généralement lorsque les développeurs sont plus nombreux que les DBA ou que les DBA sont tenaces. Ce dernier point est notre mauvais, et nous devons être mieux à même de fournir les requêtes dont vous avez besoin.
  • Sur une note connexe, un ensemble de procédures stockées est un moyen très simple d'inventorier exactement quelles requêtes peuvent être exécutées sur mon système. Dès qu'une application est autorisée à contourner les procédures et à soumettre ses propres requêtes ad hoc, afin de les trouver, je dois exécuter une trace qui couvre un cycle commercial entier, ou analyser tout le code de l'application (encore une fois, que Je n'ai peut-être pas accès à) pour trouver tout ce qui ressemble à une requête. Être capable de voir la liste des procédures stockées (et grep une seule source, sys.sql_modules, pour les références à des objets spécifiques) facilite la vie de chacun.
  • Je peux aller beaucoup plus loin pour empêcher l'injection SQL; même si je prends des données et les exécute avec du SQL dynamique, je peux contrôler une grande partie de ce qui peut se produire. Je n'ai aucun contrôle sur ce que fait un développeur lors de la construction d'instructions SQL en ligne.
  • Je peux optimiser la requête (ou les requêtes) sans avoir accès au code source de l'application, la possibilité d'apporter des modifications, la connaissance du langage d'application pour le faire efficacement, l'autorité (sans se soucier des tracas) de recompiler et de redéployer l'application, etc. Cela est particulièrement problématique si l'application est distribuée.
  • Je peux forcer certaines options définies dans la procédure stockée pour éviter que les requêtes individuelles ne soient soumises à certains des problèmes lent dans l'application, rapide dans SSMS? . Cela signifie que pour deux applications différentes appelant une requête ad hoc, on pourrait avoir SET ANSI_WARNINGS ON, et l'autre aurait pu SET ANSI_WARNINGS OFF, et ils auraient chacun leur propre copie du plan. Le plan qu'ils obtiennent dépend des paramètres utilisés, des statistiques en place, etc. la première fois que la requête est appelée dans chaque cas, ce qui peut conduire à des plans différents et donc à des performances très différentes.
  • Je peux contrôler des choses comme les types de données et la façon dont les paramètres sont utilisés, contrairement à certains ORM - certaines versions antérieures de choses comme EF paramétreraient une requête en fonction de la longueur d'un paramètre, donc si j'avais un paramètre N'Smith 'et un autre N' Johnson 'J'obtiendrais deux versions différentes du plan. Ils ont corrigé cela. Ils ont corrigé cela, mais quoi d'autre est encore cassé?
  • Je peux faire des choses que les ORM et autres frameworks et bibliothèques "utiles" ne sont pas encore capables de supporter.

Cela dit, cette question est susceptible de susciter plus d'arguments religieux qu'un débat technique. Si nous voyons cela se produire, nous le fermerons probablement.

44
Aaron Bertrand

TLDR: Il n'y a pas de différence de performance appréciable entre les deux tant que votre sql inline est paramétré.

C'est la raison pour laquelle j'ai progressivement supprimé les procédures stockées:

  • Nous exécutons un environnement d'application "bêta" - un environnement parallèle à la production qui partage la base de données de production. Étant donné que le code db se situe au niveau de l'application et que les modifications de la structure db sont rares, nous pouvons permettre aux utilisateurs de confirmer de nouvelles fonctionnalités au-delà du contrôle qualité et d'effectuer des déploiements en dehors de la fenêtre de déploiement de la production tout en fournissant des fonctionnalités de production et des correctifs non critiques. Cela ne serait pas possible si la moitié du code d'application se trouvait dans la base de données.

  • Nous pratiquons les devops au niveau de la base de données (octopus + dacpacs). Cependant, alors que la couche métier et les versions ultérieures peuvent être purgées et remplacées et la récupération juste à l'inverse, cela n'est pas vrai pour les modifications incrémentielles et potentiellement destructrices qui doivent être apportées aux bases de données. Par conséquent, nous préférons garder nos déploiements DB plus légers et moins fréquents.

  • Afin d'éviter des copies presque exactes du même code pour les paramètres facultatifs, nous utilisons souvent un modèle "où @var est nul ou @ var = table.field". Avec un proc stocké, vous obtiendrez probablement le même plan d'exécution, malgré des intentions plutôt différentes, et donc rencontrez des problèmes de performances ou éliminez les plans mis en cache avec des astuces de "recompilation". Cependant, avec un simple bout de code qui ajoute un commentaire "signature" à la fin du sql, nous pouvons forcer différents plans en fonction des variables qui étaient nulles (ne pas être interprété comme un plan différent pour toutes les combinaisons de variables - seulement nul vs non nul).

  • Je peux apporter des changements spectaculaires aux résultats avec seulement des changements mineurs à la volée au sql. Par exemple, je peux avoir une déclaration qui se termine avec deux CTE, "Raw" et "ReportReady". Rien ne dit que les deux CTE doivent être utilisés. Ma déclaration SQL peut alors être:

    ...

    sélectionnez * parmi {(format)} "

Cela me permet d'utiliser exactement la même méthode de logique métier pour un appel API simplifié et un rapport qui doit être plus détaillé en veillant à ne pas dupliquer une logique compliquée.

  • lorsque vous avez une règle "procs uniquement", vous vous retrouvez avec une tonne de redondance dans la grande majorité de votre sql qui finit par être CRUD - Vous liez tous les paramètres, vous listez tous ces paramètres dans la signature proc, (et maintenant vous êtes dans un fichier différent dans un projet différent), vous mappez ces paramètres simples à leurs colonnes. Cela crée une expérience de développement assez disjointe.

Il existe des raisons valables d'utiliser procs:

  • Sécurité - Vous avez ici une autre couche sur laquelle l'application doit passer. Si le compte du service d'application n'est pas autorisé à toucher les tables, mais ne dispose que de l'autorisation "exécuter" sur les procs, vous bénéficiez d'une protection supplémentaire. Cela n'en fait pas une donnée car cela a un coût, mais c'est une possibilité.

  • Réutilisation - Bien que je dirais que la réutilisation devrait se produire en grande partie au niveau de la couche métier pour s'assurer que vous ne contournez pas les règles métier non liées à la base de données, nous avons toujours le type occasionnel de bas niveau "utilisé partout" de procs et fonctions utilitaires.

Il y a quelques arguments qui ne supportent pas vraiment les procs ou qui sont facilement atténués IMO:

  • Réutilisation - J'ai mentionné cela ci-dessus comme un "plus", mais je voulais également le mentionner ici que la réutilisation devrait se produire en grande partie au niveau de l'entreprise. Un processus pour insérer un enregistrement ne doit pas être considéré comme "réutilisable" lorsque la couche métier peut également vérifier d'autres services non-db.

  • Cache plan balloat - la seule façon dont cela va être un problème est si vous concaténez des valeurs plutôt que de paramétrer. Le fait que vous obtenez rarement plus d'un plan par proc vous fait souvent du mal lorsque vous avez un "ou" dans une requête

  • Taille de l'instruction - un kb supplémentaire d'instructions SQL sur le nom de proc va généralement être négligeable par rapport aux données qui reviennent. Si c'est ok pour les Entités, c'est ok pour moi.

  • Voir la requête exacte - Rendre les requêtes faciles à trouver dans le code est aussi simple que d'ajouter l'emplacement d'appel en tant que commentaire au code. Rendre le code copiable du code c # vers ssms est aussi simple qu'une interpolation créative et une utilisation des commentaires:

        //Usage /*{SSMSOnly_}*/Pure Sql To run in SSMS/*{_SSMSOnly}*/
        const string SSMSOnly_ = "*//*<SSMSOnly>/*";
        const string _SSMSOnly = "*/</SSMSOnly>";
        //Usage /*{NetOnly_}{InterpolationVariable}{_NetOnly}*/
        const string NetOnly_ = "*/";
        const string _NetOnly = "/*";
    
  • Sql Injection - Paramétrez vos requêtes. Terminé. Cela peut en fait être annulé si le proc utilise à la place SQL dynamique.

  • Contournement du déploiement - Nous pratiquons également les devops au niveau de la base de données, ce n'est donc pas une option pour nous.

  • "Lent dans l'application, rapide dans SSMS" - Il s'agit d'un problème de mise en cache du plan qui affecte les deux côtés. Les options définies provoquent simplement la compilation d'un nouveau plan qui semble résoudre le problème pour THE ONE SET de variables. Cela ne fait que répondre à la raison pour laquelle vous voyez des résultats différents - les options définies elles-mêmes ne résolvent PAS le problème du reniflage des paramètres.

  • Les plans d'exécution SQL en ligne ne sont pas mis en cache - tout simplement faux. Une instruction paramétrée, tout comme le nom du proc, est rapidement hachée, puis un plan est recherché par ce hachage. C'est 100% pareil.

  • Pour être clair, je parle de code SQL brut en ligne non généré à partir d'un ORM - nous utilisons uniquement Dapper qui est au mieux un micro ORM.

https://weblogs.asp.net/fbouma/38178

https://stackoverflow.com/a/15277/852208

1
b_levitt

Bien que je respecte l'auteur de la communication, je suis humblement en désaccord avec la réponse fournie et non pour des "raisons religieuses". En d'autres termes, je crois qu'il n'y a aucune installation que Microsoft a fournie qui diminue le besoin de conseils pour utiliser les procédures stockées.

Tout conseil fourni à un développeur qui favorise l'utilisation de requêtes SQL en texte brut doit être rempli de nombreuses mises en garde, de sorte que je pense que le conseil le plus prudent est d'encourager fortement l'utilisation des procédures stockées et de décourager vos équipes de développeurs de s'engager dans la pratique. d'incorporer des instructions SQL dans le code, ou de soumettre des requêtes SQL brutes, en texte brut, en dehors des SPROC SQL (procédures stockées).

Je pense que la réponse simple à la question de savoir pourquoi utiliser un SPROC est celle du soumissionnaire: les SPROC sont analysés, optimisés et compilés. En tant que tels, leurs plans de requête/d'exécution sont mis en cache car vous avez enregistré une représentation statique d'une requête et vous ne la modifierez normalement que par des paramètres, ce qui n'est pas vrai dans le cas d'instructions SQL copiées/collées qui se transforment probablement de page en page et de composant/niveau, et sont souvent variablisés dans la mesure où différentes tables, même des noms de base de données, peuvent être spécifiés d'appel à appel. Permettre ce type de soumission SQL dynamique ad hoc , diminue considérablement la probabilité que le moteur de base de données réutilise le plan de requête pour vos instructions ad hoc, selon des règles très strictes. Ici, je fais la distinction entre les requêtes ad hoc dynamiques (dans l'esprit de la question posée) par rapport à l'utilisation du système SPROC sp_executesql efficace.

Plus précisément, il existe les composants suivants:

  • Plans de requête série et parallèle qui ne contiennent pas de contexte utilisateur et permettent leur réutilisation par le moteur de base de données.
  • Contexte d'exécution qui permet la réutilisation d'un plan de requête par un nouvel utilisateur avec différents paramètres de données.
  • Cache de procédure qui est ce que le moteur de base de données interroge afin de créer les efficacités que nous recherchons.

Lorsqu'une instruction SQL est émise à partir d'une page Web, appelée "instruction ad hoc", le moteur recherche un plan d'exécution existant pour gérer la demande. Comme il s'agit d'un texte soumis par un utilisateur, il sera ingéré, analysé, compilé et exécuté, s'il est valide. À ce moment, il recevra un coût de requête de zéro. Le coût de la requête est utilisé lorsque le moteur de base de données utilise son algorithme afin de déterminer les plans d'exécution à expulser du cache.

Les requêtes ad hoc reçoivent une valeur de coût de requête d'origine de zéro, par défaut. Lors de l'exécution ultérieure du même texte de requête ad hoc exact, par un autre processus utilisateur (ou le même), le coût de la requête actuelle est réinitialisé au coût de compilation d'origine. Étant donné que notre coût de compilation de requêtes ad hoc est nul, cela n'augure rien de bon pour la possibilité de réutilisation. Évidemment, zéro est l'entier le moins valorisé, mais pourquoi serait-il expulsé?

Lorsque des pressions de mémoire surviennent, et ce sera le cas si vous avez un site souvent utilisé, le moteur de base de données utilise un algorithme de nettoyage pour déterminer comment il peut récupérer la mémoire utilisée par le cache de procédures. Il utilise le coût de requête actuel pour décider des plans à expulser. Comme vous pouvez le deviner, les plans avec un coût de zéro sont les premiers à être expulsés du cache car zéro signifie essentiellement "aucun utilisateur actuel ou référence à ce plan".

  • Remarque: Plans d'exécution ad hoc - Le coût actuel est augmenté par chaque processus utilisateur, par le coût de compilation d'origine du plan. Cependant, aucun coût maximal d'un plan ne peut être supérieur à son coût de compilation d'origine ... dans le cas de requêtes ad hoc ... zéro. Ainsi, il sera "augmenté" de cette valeur ... zéro - ce qui signifie essentiellement qu'il restera le plan le moins coûteux.

Par conséquent, il est tout à fait probable qu'un tel plan sera expulsé en premier lorsque des pressions de mémoire surviendront.

Donc, si vous disposez d'un build-out de serveur avec beaucoup de mémoire "au-delà de vos besoins", vous pouvez ne pas rencontrer ce problème aussi souvent qu'un serveur occupé qui n'a que "suffisamment" de mémoire pour gérer sa charge de travail. (Désolé, la capacité et l'utilisation de la mémoire du serveur sont quelque peu subjectives/relatives, bien que l'algorithme ne le soit pas.)

Maintenant, si je me trompe sur un ou plusieurs points, je suis certainement prêt à être corrigé.

Enfin, l'auteur a écrit:

"Nous avons maintenant une optimisation au niveau de l'instruction, de sorte qu'une requête correctement paramétrée provenant d'une application peut bénéficier du même plan d'exécution que cette requête incorporée dans une procédure stockée."

Je pense que l'auteur fait référence à l'option "Optimiser pour les charges de travail ad hoc".

Si tel est le cas, cette option permet un processus en deux étapes qui évite d'envoyer immédiatement le plan de requête complet au cache de procédure. Il n'envoie qu'un stub de requête plus petit. Si un appel de requête exact est renvoyé vers le serveur alors que le talon de requête est toujours dans le cache de procédure, le plan d'exécution de requête complet est enregistré dans le cache de procédure, à ce moment. Cela économise de la mémoire qui, lors d'incidents de pression de mémoire, peut permettre à l'algorithme d'éviction d'expulser votre talon moins fréquemment qu'un plan de requête plus grand qui a été mis en cache. Encore une fois, cela dépend de la mémoire et de l'utilisation de votre serveur.

Cependant, vous devez activer cette option, car elle est désactivée par défaut.

Enfin, je tiens à souligner que, souvent, la raison même pour laquelle les développeurs intègrent SQL dans des pages, des composants et d'autres emplacements, c'est parce qu'ils souhaitent être flexibles et soumettre une requête SQL dynamique au moteur de base de données. Par conséquent, dans un cas d'utilisation réel, la soumission du même texte, appel sur appel, est peu susceptible de se produire, tout comme la mise en cache/l'efficacité que nous recherchons, lors de la soumission de requêtes ad hoc à SQL Server.

Pour plus d'informations, veuillez consulter:

https://technet.Microsoft.com/en-us/library/ms181055 (v = sql.105) .aspx
http://sqlmag.com/database-performance-tuning/don-t-fear-dynamic-sql

Meilleur,
Henri

0
Henry