¿Cuál es el sentido y el beneficio de usar SqlCommand.Prepare ()?


14

Me encontré con un código de desarrollador donde el método SqlCommand.Prepare () (ver MSDN) se usa ampliamente antes de la ejecución de consultas SQL. Y me pregunto cuál es el beneficio de esto.

Muestra:

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

He jugado un poco y rastreado. La ejecución del comando después de llamar al Prepare()método hace que SQL Server ejecute la siguiente instrucción:

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

Después de eso, cuando el parámetro obtiene su valor y SqlCommand.ExecuteNonQuery()se llama, se ejecuta lo siguiente en Sql-Server:

exec sp_execute 1,@id=20

Para mí, esto parece que la declaración se compila en cuanto Prepare()se ejecuta. Me pregunto cuál es el beneficio de esto. ¿Significa esto que se coloca en la caché del plan y se puede reutilizar tan pronto como se ejecute la consulta final con los valores de los parámetros deseados?

Descubrí (y lo documenté en otra pregunta ) que los comandos SqlCommands que se ejecutan con SqlParameters siempre están envueltos en sp_executesqlllamadas a procedimientos. Esto hace que SQL Server pueda almacenar y reutilizar los planes independientemente de los valores de los parámetros.

Con respecto a esto, me pregunto si el prepare()método es inútil u obsoleto o si me falta algo aquí.


Siempre pensé que tenía algo que ver con la prevención de la inyección de SQL, por lo que prepararía la consulta para aceptar ciertas variables de ciertos tipos. De esa manera, si no pasa variables de ese tipo, la consulta falla
Mark Sinkinson

Aunque al decir eso, esta explicación de PHP es probablemente igual de válida para .NET php.net/manual/en/pdo.prepared-statements.php
Mark Sinkinson

"Sí, señor. Conductor, prepárese para mudarse". "¿Qué estás preparando? ¡Siempre te estás preparando! ¡Solo vete!"
Jon de todos los oficios

Respuestas:


19

Preparar un lote SQL por separado de ejecutar el lote SQL preparado es una construcción que es efectiva ** inútil para SQL Server dada la forma en que se almacenan en caché los planes de ejecución. Separar los pasos de preparación (análisis, vinculación de parámetros y compilación) y ejecución solo tiene sentido cuando no hay almacenamiento en caché. El propósito es ahorrar el tiempo dedicado a analizar y compilar reutilizando un plan existente, y esto es lo que hace el caché interno del plan. Pero no todos los RDBMS realizan este nivel de almacenamiento en caché (claramente por la existencia de estas funciones) y, por lo tanto, le corresponde al código del cliente solicitar que un plan se "almacene en caché", y así guardar esa ID de caché, y luego reutilizarla eso. Esta es una carga adicional sobre el código del cliente (y el programador para recordar hacerlo y hacerlo bien) que es innecesario. De hecho, la página de MSDN paraIDbCommand.Prepare () método establece:

El servidor almacena automáticamente en caché los planes de reutilización según sea necesario; por lo tanto, no es necesario llamar a este método directamente en su aplicación cliente.

Cabe señalar que, en lo que mi muestra de prueba (que coincide con lo que mostramos en la pregunta), llamando SqlCommand.Prepare () , no no hacer un "preparar" la operación -sólo: llama sp_prepexec que prepara y ejecuta el SQL ; no llama a sp_prepare, que solo analiza y compila (y no toma ningún valor de parámetro, solo sus nombres y tipos de datos). Por lo tanto, no puede haber ningún beneficio de "precompilación" de la llamada, SqlCommand.Prepareya que realiza una ejecución inmediata (suponiendo que el objetivo es diferir la ejecución). SIN EMBARGO , hay un interesante beneficio menor potencial de llamarSqlCommand.Prepare() , incluso si llama en sp_prepexeclugar de hacerlo sp_prepare. Por favor vea la nota (** ) en la parte inferior para más detalles.

Mi prueba muestra que incluso cuando SqlCommand.Prepare()se llama (y tenga en cuenta que esta llamada no emite ningún comando en SQL Server), el ExecuteNonQueryo ExecuteReaderque sigue se ejecuta completamente, y si es ExecuteReader, devuelve filas. Aún así, SQL Server Profiler muestra que la primera SqlCommand.Execute______()llamada después de la SqlCommand.Prepare()llamada se registra como un evento "Preparar SQL", y las .Execute___()llamadas posteriores se registran como eventos "Exec Prepared SQL".


Código de prueba

Se ha subido a PasteBin en: http://pastebin.com/Yc2Tfvup . El código crea una aplicación de consola .NET / C # que está diseñada para ejecutarse mientras se ejecuta un seguimiento del Analizador de SQL Server (o una sesión de Eventos extendidos). Hace una pausa después de cada paso para que quede claro qué declaraciones tienen un efecto particular.


ACTUALIZAR

Encontré más información y una pequeña razón potencial para evitar llamar SqlCommand.Prepare(). Una cosa que noté en mis pruebas, y lo que debería tenerse en cuenta para cualquiera que ejecute esa aplicación de consola de prueba: nunca se realiza una llamada explícita sp_unprepare. Investigué un poco y encontré la siguiente publicación en los foros de MSDN:

SqlCommand - ¿Prepararse o no prepararse?

La respuesta aceptada contiene un montón de información, pero los puntos más destacados son:

  • ** ** Lo que prepara realmente le ahorra es principalmente el tiempo que lleva transmitir la cadena de consulta a través del cable.
  • Una consulta preparada no tiene garantía de que se guarde un plan en caché, y no es más rápido que una consulta ad-hoc una vez que llega al servidor.
  • Los RPC (CommandType.StoredProcedure) no obtienen nada de la preparación.
  • En el servidor, un identificador preparado es básicamente un índice en un mapa en memoria que contiene TSQL para ejecutar.
  • El mapa se almacena por conexión, no es posible el uso de conexión cruzada.
  • El restablecimiento enviado cuando el cliente reutiliza la conexión del grupo borra el mapa.

También encontré la siguiente publicación del equipo de SQLCAT, que parece estar relacionada, pero podría ser simplemente un problema específico de ODBC, mientras que SqlClient se limpia correctamente al deshacerse de SqlConnection. Es difícil de decir ya que la publicación de SQLCAT no menciona ninguna prueba adicional que pueda ayudar a probar esto como una causa, como borrar el grupo de conexiones, etc.

Cuidado con las declaraciones SQL preparadas


CONCLUSIÓN

Dado que:

  • El único beneficio real de las llamadas SqlCommand.Prepare()parece ser que no necesita enviar el texto de la consulta nuevamente a través de la red,
  • llamar sp_preparey sp_prepexecalmacenar el texto de la consulta como parte de la memoria de la conexión (es decir, la memoria del servidor SQL)

Recomendaría no llamar SqlCommand.Prepare()porque el único beneficio potencial es guardar paquetes de red mientras que la desventaja es ocupar más memoria del servidor. Si bien la cantidad de memoria consumida es probablemente muy pequeña en la mayoría de los casos, el ancho de banda de la red rara vez es un problema, ya que la mayoría de los servidores de bases de datos están conectados directamente a los servidores de aplicaciones con conexiones de más de 10 megabits (probablemente 100 megabits o gigabits en estos días) ( y algunos incluso están en la misma caja ;-). Eso y la memoria es casi siempre un recurso más escaso que el ancho de banda de la red.

Supongo que para cualquiera que escriba software que pueda conectarse a múltiples bases de datos, la conveniencia de una interfaz estándar podría cambiar este análisis de costo / beneficio. Pero incluso en ese caso, tengo que creer que todavía es bastante fácil abstraer una interfaz de base de datos "estándar" que permite diferentes proveedores (y, por lo tanto, diferencias entre ellos, como no tener que llamar Prepare()) dado que hacerlo es un objeto básico Programación orientada que probablemente ya esté haciendo en el código de su aplicación ;-).


Gracias por esta extensa respuesta. Probé tus pasos y tuve dos aberraciones de tus expectativas / resultados: en el paso 4 obtuve un resultado adicional. En el paso 10 obtuve otro plan_handle que en el paso 2.
Magier

1
@Magier ¿No está seguro sobre el paso 4 ... tal vez tenía opciones SET diferentes o de alguna manera el texto de consulta entre los dos era diferente? Incluso una única diferencia de carácter (visible o no) será un plan diferente. Copiar y pegar desde esta página web podría no ayudar, así que copie la consulta (todo entre comillas simples) desde sp_preparela llamada a sp_executesql, solo para estar seguro. Para el paso 10, sí, hice más pruebas ayer y vi algunas ocasiones con diferentes identificadores para sp_prepare, pero aún así nunca fue lo mismo sp_executesql. Probaré una cosa más y actualizaré mi respuesta.
Solomon Rutzky

1
@Magier En realidad, la prueba que realicé antes lo cubrió y no creo que haya una verdadera reutilización del plan, especialmente a la luz de esa publicación en el foro de MSDN que dice que solo almacena en caché el SQL. Todavía tengo curiosidad de cómo puede reutilizar el plan_handle mientras sp_executesqlque no puede o no. Pero independientemente, el tiempo de análisis aún está allí sp_preparesi el plan se ha eliminado del caché, por lo que eliminaré esa parte de mi respuesta (interesante pero irrelevante). Esa parte fue más una nota al margen interesante de todos modos ya que no abordaba su pregunta directa. Todo lo demás todavía se aplica.
Solomon Rutzky

1
Este fue el tipo de explicación que estaba buscando.
CSharpie

5

Con respecto a esto, me pregunto si el método prepare () es inútil u obsoleto o si me falta algo aquí.

Diría que el método Prepare tiene un mundo SqlCommand de valor limitado, no es que sea completamente inútil. Una ventaja es que Preparar es parte de la interfaz IDbCommand que implementa SqlCommand. Esto permite que el mismo código se ejecute contra otros proveedores de DBMS (que pueden requerir Preparar) sin lógica condicional para determinar si Preparar debe ser llamado o no.

Prepare no tiene ningún valor con .NET Provider para SQL Server además de estos casos de uso, AFAIK.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.