Procédures stockées et SQL en ligne


27

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?


1
Une requête correctement paramétrée est tout aussi bonne qu'une procédure stockée, du point de vue des performances. Les deux sont compilés avant la première utilisation, les deux réutilisent le plan d'exécution mis en cache lors des exécutions suivantes, les deux plans sont stockés dans le même cache de plan et les deux reçoivent le même nom. Il n'y a plus d'avantage de performances pour une procédure stockée, dans SQL Server aujourd'hui.
marc_s

@marc_s c'est vrai si les requêtes sont identiques. Cependant, comme je l'ai souligné dans ma réponse, il existe certaines caractéristiques des requêtes ad hoc qui peuvent être des problèmes de performances même pour des requêtes qui semblent identiques.
Aaron Bertrand

Réponses:


42

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 qu'optimiser / compiler au niveau du lot. Nous avons maintenant une optimisation au niveau de l'instruction, donc 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. En règle générale, cela se fait 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 en mesure de voir la liste des procédures stockées (et grep une seule source,, sys.sql_modulespour les références à des objets spécifiques) rend la vie de chacun beaucoup plus facile.
  • 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.
  • Puis-je forcer certaines options définies dans la procédure stockée pour éviter que les requêtes individuelles ne soient soumises à certains ralentissements de l'application, rapidement dans SSMS? problèmes. Cela signifie que pour deux applications différentes appelant une requête ad hoc, l'une pourrait avoir SET ANSI_WARNINGS ON, et l'autre pourrait avoir 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.


2
Une autre raison des procédures stockées? Pour les requêtes longues et compliquées, vous devez envoyer la requête au serveur à chaque fois, à moins qu'il ne s'agisse d'un sproc, alors vous appuyez simplement sur "exec sprocname" et quelques paramètres. Cela pourrait faire une différence sur un réseau lent (ou occupé).
David Crowell

0

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 que Microsoft n'a fourni aucune installation qui diminue le besoin de conseils pour utiliser les procédures stockées.

Toute directive fournie à un développeur qui favorise l'utilisation de requêtes SQL en texte brut doit être remplie de nombreuses mises en garde, de sorte que je pense que le conseil le plus prudent est d'encourager considérablement 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 dynamique ad hocLa soumission SQL réduit considérablement la probabilité que le moteur DB réutilise le plan de requête pour vos instructions ad hoc, selon certaines règles très strictes. Ici, je fais la distinction entre les requêtes dynamiques ad hoc (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.

Par conséquent, si vous disposez d'un serveur avec beaucoup de mémoire "au-delà de vos besoins", vous ne rencontrerez peut-être pas ce problème aussi souvent qu'un serveur occupé qui ne dispose que de mémoire "suffisante" 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 tirer parti 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 la 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

Bien,
Henry


4
J'ai lu attentivement plusieurs paragraphes de votre message, deux ou trois fois, et je n'ai toujours aucune idée des pensées que vous essayez de transmettre. Dans certains cas, vous semblez à la fin des phrases dire exactement le contraire de ce que la phrase a commencé à dire. Vous devez vraiment relire et modifier soigneusement cette soumission.
Pieter Geerkens

Merci pour les commentaires Pieter. Si tel est le cas, il est possible que je raccourcisse mes phrases pour clarifier ce point. Pouvez-vous s'il vous plaît fournir un exemple de l'endroit où je semble affirmer le contraire de la pensée originale? Très appréciée.
Henry

Non, je ne voulais pas dire Optimiser pour les charges de travail ad hoc, je voulais dire l'optimisation au niveau des instructions. Dans SQL Server 2000, par exemple, une procédure stockée était compilée dans son ensemble, il n'y avait donc aucun moyen pour l'application de réutiliser un plan pour sa propre requête ad hoc qui correspondait à quelque chose dans la procédure. Je dirai que je suis d'accord avec Pieter - beaucoup de choses que vous dites sont difficiles à suivre. Des choses comme «Je crois qu'il n'y a aucun service fourni par Microsoft qui diminue le besoin de conseils pour utiliser les procédures stockées». sont inutilement complexes et nécessitent beaucoup trop d'analyse pour être compris. A MON HUMBLE AVIS.
Aaron Bertrand

1
il semble que votre aversion pour le sql "ad hoc" soit basée sur l'idée que le sql change d'une manière ou d'une autre entre les exécutions ... ceci est totalement faux lorsque le paramétrage est impliqué.
b_levitt

0

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

Voici 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". Il n'y a rien qui 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 prennent pas vraiment en charge les procs ou qui sont facilement atténués par l'OMI:

  • Réutilisation - J'ai mentionné cela ci-dessus comme un "plus", mais je voulais également 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 que 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 certaine 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 des variables THE ONE SET OFF. 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 - 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

/programming//a/15277/852208

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.