Quels sont le sens et les avantages de l'utilisation de SqlCommand.Prepare ()?

14

Je suis tombé sur du code développeur où la méthode SqlCommand.Prepare () (voir MSDN) est largement utilisée avant l'exécution des requêtes SQL. Et je me demande quel est l'avantage de cela?

Échantillon:

command.Prepare();
command.ExecuteNonQuery();
//...
command.Parameters[0].Value = 20;
command.ExecuteNonQuery();

J'ai joué un peu et tracé. L'exécution de la commande après l'appel de la Prepare()méthode oblige Sql Server à exécuter l'instruction suivante:

declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@id int,@desc text',N'INSERT INTO dbo.testtable (id) VALUES (@id)',@id=20'
select @p1

Après cela, lorsque le paramètre obtient sa valeur et SqlCommand.ExecuteNonQuery()est appelé, ce qui suit est exécuté sur Sql-Server:

exec sp_execute 1,@id=20

Pour moi, cela ressemble à la déclaration qui est en quelque sorte compilée dès qu'elle Prepare()est exécutée. Je me demande quel est l'avantage de cela? Cela signifie-t-il qu'il est placé dans le cache du plan et peut être réutilisé dès que la requête finale est exécutée avec les valeurs de paramètre souhaitées?

J'ai compris (et documenté cela dans une autre question ) que les commandes SqlCommands qui sont exécutées avec SqlParameters sont toujours enveloppées dans sp_executesqldes appels de procédure. Cela rend Sql Server capable de stocker et de réutiliser les plans indépendamment des valeurs des paramètres.

À ce sujet, je me demande si la prepare()méthode est un peu inutile ou obsolète ou si je manque quelque chose ici?

Magier
la source
J'ai toujours pensé que cela avait quelque chose à voir avec la prévention de l'injection SQL, donc vous prépariez la requête pour accepter certaines variables de certains types. De cette façon, si vous ne transmettez pas de variables de ces types, la requête échoue
Mark Sinkinson
Bien qu'en disant cela, cette explication PHP est probablement tout aussi valable pour .NET php.net/manual/en/pdo.prepared-statements.php
Mark Sinkinson
"Oui, monsieur. Conducteur, préparez-vous à partir." "Que préparez-vous?! Vous vous préparez toujours! Allez-y!"
Jon of All Trades

Réponses:

19

La préparation d'un lot SQL séparément de l'exécution du lot SQL préparé est une construction qui est effectivement **inutile pour SQL Server étant donné la façon dont les plans d'exécution sont mis en cache. Séparer les étapes de préparation (analyse, liaison des paramètres et compilation) et d'exécution n'a de sens que lorsqu'il n'y a pas de mise en cache. Le but est de gagner du temps sur l'analyse et la compilation en réutilisant un plan existant, et c'est ce que fait le cache du plan interne. Mais tous les SGBDR ne font pas ce niveau de mise en cache (clairement à cause de l'existence de ces fonctions) et il est donc laissé au code client de demander qu'un plan soit "mis en cache", et donc de sauvegarder cet ID de cache, puis de réutiliser il. C'est une charge supplémentaire sur le code client (et le programmeur de se rappeler de le faire et de bien faire les choses) qui n'est pas nécessaire. En fait, la page MSDN de la méthode IDbCommand.Prepare () indique:

Le serveur met automatiquement en cache les plans de réutilisation si nécessaire; par conséquent, il n'est pas nécessaire d'appeler cette méthode directement dans votre application cliente.

Il convient de noter que, pour autant que mes tests le montrent (ce qui correspond à ce que vous montrez dans la question), appeler SqlCommand.Prepare () , ne fait pas une opération de "préparation" uniquement: il appelle sp_prepexec qui prépare et exécute le SQL ; il n'appelle pas sp_prepare qui est uniquement d'analyse et de compilation (et n'accepte aucune valeur de paramètre, uniquement leurs noms et types de données). Par conséquent, il ne peut y avoir aucun avantage de «précompilation» de l'appel SqlCommand.Preparecar il effectue une exécution immédiate (en supposant que le but est de différer l'exécution). CEPENDANT , il y a un avantage mineur potentiel intéressant d'appeler SqlCommand.Prepare(), même s'il appelle sp_prepexecau lieu de sp_prepare. Veuillez consulter la note (** ) en bas pour plus de détails.

Mes tests montrent que même lorsque l' SqlCommand.Prepare()on appelle (et veuillez noter que cet appel ne pas émettre de commandes sur SQL Server), le ExecuteNonQueryou ExecuteReaderqui suit est entièrement exécutée, et si elle est ExecuteReader, il renvoie des lignes. Néanmoins, SQL Server Profiler montre que le premier SqlCommand.Execute______()appel après l' SqlCommand.Prepare()appel est enregistré en tant qu'événement «Préparer SQL» et que les .Execute___()appels suivants s'enregistrent en tant qu'événements «Exec Prepared SQL».


Code de test

Il a été téléchargé sur PasteBin à: http://pastebin.com/Yc2Tfvup . Le code crée une application console .NET / C # qui est destinée à être exécutée pendant l'exécution d'une trace SQL Server Profiler (ou une session d'événements étendus). Il marque une pause après chaque étape afin de savoir clairement quelles instructions ont un effet particulier.


MISE À JOUR

Trouvé plus d'informations et une légère raison potentielle pour éviter d'appeler SqlCommand.Prepare(). Une chose que j'ai remarquée lors de mes tests, et ce qu'il convient de noter pour quiconque exécute cette application console de test: il n'y a jamais d'appel explicite sp_unprepare. J'ai fait quelques recherches et trouvé le message suivant sur les forums MSDN:

SqlCommand - Se préparer ou ne pas se préparer?

La réponse acceptée contient un tas d'informations, mais les points saillants sont:

  • ** Ce que vous préparez réellement vous fait économiser, c'est principalement le temps nécessaire pour transmettre la chaîne de requête sur le fil.
  • Une requête préparée n'a aucune garantie qu'un plan mis en cache est enregistré et n'est pas plus rapide qu'une requête ad-hoc une fois qu'elle atteint le serveur.
  • Les RPC (CommandType.StoredProcedure) ne gagnent rien à se préparer.
  • Sur le serveur, un descripteur préparé est essentiellement un index dans une carte en mémoire contenant TSQL à exécuter.
  • La carte est stockée par connexion, aucune utilisation de connexion croisée n'est possible.
  • La réinitialisation envoyée lorsque le client réutilise la connexion du pool efface la carte.

J'ai également trouvé le message suivant de l'équipe SQLCAT, qui semble être lié, mais pourrait simplement être un problème spécifique à ODBC alors que SqlClient se nettoie correctement lors de la suppression de SqlConnection. Il est difficile de dire que le post SQLCAT ne mentionne aucun test supplémentaire qui pourrait aider à prouver cela comme une cause, comme l'effacement du pool de connexions, etc.

Méfiez-vous de ces instructions SQL préparées


CONCLUSION

Étant donné que:

  • le seul véritable avantage de l'appel SqlCommand.Prepare()semble être que vous n'avez pas besoin de soumettre à nouveau le texte de la requête sur le réseau,
  • appeler sp_prepareet sp_prepexecstocker le texte de la requête dans la mémoire de la connexion (c'est-à-dire la mémoire SQL Server)

Je recommande contre l' appel SqlCommand.Prepare()parce que le seul avantage potentiel est de sauver les paquets réseau alors que la baisse prend plus de mémoire de serveur. Bien que la quantité de mémoire consommée soit probablement très faible dans la majorité des cas, la bande passante réseau est rarement un problème, car la plupart des serveurs de base de données sont directement connectés aux serveurs d'applications via des connexions minimales de 10 mégabits (plus probablement 100 mégabits ou gigabits de nos jours) ( et certains sont même sur la même boîte ;-). Cela et la mémoire sont presque toujours une ressource plus rare que la bande passante réseau.

Je suppose que pour quiconque écrit un logiciel pouvant se connecter à plusieurs bases de données, la commodité d'une interface standard pourrait changer cette analyse coûts / avantages. Mais même dans ce cas, je dois croire qu'il est encore assez facile d'abstraire une interface DB "standard" qui permet à différents fournisseurs (et donc des différences entre eux, comme le fait de ne pas avoir besoin d'appeler Prepare()) étant donné que cela est un objet de base Programmation orientée que vous faites probablement déjà dans votre code d'application ;-).

Solomon Rutzky
la source
Merci pour cette réponse détaillée. J'ai testé vos étapes et j'ai eu deux aberrations par rapport à vos attentes / résultats: à l'étape 4, j'ai obtenu un résultat supplémentaire. À l'étape 10, j'ai reçu un autre plan_handle qu'à l'étape 2.
Magier
1
@Magier Vous n'êtes pas sûr de l'étape 4 ... peut-être que vous aviez différentes options SET ou que le texte de la requête entre les deux était différent? Même une seule différence de caractère (visible ou non) sera un plan différent. Le copier-coller à partir de cette page Web peut ne pas aider, alors copiez la requête (tout entre les guillemets simples) de sp_preparedans l'appel à sp_executesql, juste pour être sûr. Pour l'étape 10, oui, j'ai fait plus de tests hier et j'ai vu quelques occasions avec des poignées différentes pour sp_prepare, mais toujours jamais pareilles sp_executesql. Je vais tester une dernière chose et mettre à jour ma réponse.
Solomon Rutzky
1
@Magier En fait, le test que j'ai effectué avant l'a couvert et je ne pense pas qu'il y ait une véritable réutilisation du plan, en particulier à la lumière de ce post sur le forum MSDN disant qu'il ne cache que le SQL. Je suis toujours curieux de savoir comment il est capable de réutiliser le plan_handle alors sp_executesqlqu'il ne peut pas ou ne le fait pas. Mais quoi qu'il en soit, le temps d'analyse est toujours activé sp_preparesi le plan a été supprimé du cache, je supprimerai donc cette partie de ma réponse (intéressante mais non pertinente). Cette partie était de toute façon plus intéressante, car elle ne répondait pas à votre question directe. Tout le reste s'applique toujours.
Solomon Rutzky
1
C'était le genre d'explication que je cherchais.
CSharpie
5

À ce sujet, je me demande si la méthode prepare () est un peu inutile ou obsolète ou si je manque quelque chose ici?

Je dirais que la méthode Prepare a un monde SqlCommand de valeur limitée, pas qu'elle soit entièrement inutile. L'un des avantages est que Preparer fait partie de l'interface IDbCommand que SqlCommand implémente. Cela permet au même code de s'exécuter sur d'autres fournisseurs de SGBD (qui peuvent nécessiter la préparation) sans logique conditionnelle pour déterminer si la préparation doit être appelée ou non.

Préparer n'a aucune valeur avec le fournisseur .NET pour SQL Server en dehors de ces cas d'utilisation, AFAIK.

Dan Guzman
la source