Procedimientos almacenados vs. SQL en línea

27

Sé que los procedimientos almacenados son más eficientes a través de la ruta de ejecución (que el sql en línea en las aplicaciones). Sin embargo, cuando se presiona, no sé muy bien por qué.

Me gustaría saber el razonamiento técnico para esto (de manera que pueda explicárselo a alguien más adelante).

¿Alguien puede ayudarme a formular una buena respuesta?

webdad3
fuente
1
Una consulta correctamente parametrizada es tan buena como un procedimiento almacenado, desde el punto de vista del rendimiento. Ambos se compilan antes del primer uso, ambos reutilizarán el plan de ejecución en caché en ejecuciones posteriores, ambos planes se almacenarán en la misma caché del plan y ambos recibirán el mismo nombre. Ya no hay beneficio de rendimiento para un procedimiento almacenado, en SQL Server hoy.
marc_s
@marc_s eso es cierto si las consultas son idénticas. Sin embargo, como señalé en mi respuesta, hay algunas características de las consultas ad hoc que pueden ser problemas de rendimiento incluso para consultas que parecen idénticas.
Aaron Bertrand

Respuestas:

42

Creo que este sentimiento era cierto en un momento, pero no en las versiones actuales de SQL Server. Todo el problema era que en los viejos tiempos las sentencias SQL ad hoc no podían optimizarse correctamente porque SQL Server solo podía optimizar / compilar a nivel de lote. Ahora tenemos una optimización a nivel de enunciado, por lo que una consulta correctamente parametrizada proveniente de una aplicación puede aprovechar el mismo plan de ejecución que esa consulta incrustada en un procedimiento almacenado.

Todavía prefiero los procedimientos almacenados del lado de DBA por los siguientes motivos (y varios de ellos pueden tener un gran impacto en el rendimiento):

  • Si tengo varias aplicaciones que reutilizan las mismas consultas, un procedimiento almacenado encapsula esa lógica, en lugar de ensuciar la misma consulta ad hoc varias veces en diferentes bases de código. Las aplicaciones que reutilizan las mismas consultas también pueden estar sujetas a un plan de relleno de caché, a menos que se copien textualmente. Incluso las diferencias en el caso y el espacio en blanco pueden llevar a que se almacenen múltiples versiones del mismo plan (desperdicio).
  • Puedo inspeccionar y solucionar problemas de lo que está haciendo una consulta sin tener acceso al código fuente de la aplicación o ejecutar rastros costosos para ver exactamente qué está haciendo la aplicación.
  • También puedo controlar (y saber de antemano) qué consultas puede ejecutar la aplicación, a qué tablas puede acceder y en qué contexto, etc. Si los desarrolladores escriben consultas ad-hoc en su aplicación, van a tener que ven a tirar de la manga de mi camisa cada vez que necesiten acceder a una mesa que no conocía o que no podía predecir, o si soy menos responsable / entusiasmado y / o consciente de la seguridad, solo voy a promover eso usuario a dbo para que dejen de molestarme. Por lo general, esto se hace cuando los desarrolladores superan en número a los DBA o los DBA son tercos. Ese último punto es nuestro mal, y debemos ser mejores para proporcionarle las consultas que necesita.
  • En una nota relacionada, un conjunto de procedimientos almacenados es una manera muy fácil de inventariar exactamente qué consultas pueden estar ejecutándose en mi sistema. Tan pronto como se permita a una aplicación omitir los procedimientos y enviar sus propias consultas ad-hoc, para encontrarlas, tengo que ejecutar un seguimiento que cubra un ciclo comercial completo o analizar todo el código de la aplicación (nuevamente, eso Es posible que no tenga acceso a) para encontrar algo que parezca una consulta. Poder ver la lista de procedimientos almacenados (y grep una sola fuente sys.sql_modules, para referencias a objetos específicos) hace la vida de todos mucho más fácil.
  • Puedo ir mucho más lejos para evitar la inyección de SQL; incluso si tomo datos y los ejecuto con SQL dinámico, puedo controlar mucho de lo que se permite. No tengo control sobre lo que está haciendo un desarrollador al construir instrucciones SQL en línea.
  • Puedo optimizar la consulta (o consultas) sin tener acceso al código fuente de la aplicación, la capacidad de hacer cambios, el conocimiento del idioma de la aplicación para hacerlo de manera efectiva, la autoridad (no importa la molestia) de volver a compilar y volver a implementar la aplicación, etc. Esto es particularmente problemático si la aplicación se distribuye.
  • ¿Puedo forzar ciertas opciones establecidas dentro del procedimiento almacenado para evitar que las consultas individuales estén sujetas a algunos de los lentos en la aplicación, rápido en SSMS? problemas. Lo que significa que para dos aplicaciones diferentes que llaman una consulta ad hoc, una podría tener SET ANSI_WARNINGS ONy la otra podría tener SET ANSI_WARNINGS OFF, y cada una tendría su propia copia del plan. El plan que obtienen depende de los parámetros en uso, las estadísticas establecidas, etc. la primera vez que se llama a la consulta en cada caso, lo que podría conducir a planes diferentes y, por lo tanto, a un rendimiento muy diferente.
  • Puedo controlar cosas como los tipos de datos y cómo se usan los parámetros, a diferencia de ciertos ORM: algunas versiones anteriores de cosas como EF parametrizarían una consulta en función de la longitud de un parámetro, por lo que si tuviera un parámetro N'Smith 'y otro N' Johnson 'obtendría dos versiones diferentes del plan. Han arreglado esto. Han solucionado esto, pero ¿qué más está roto?
  • Puedo hacer cosas que los ORM y otros marcos y bibliotecas "útiles" aún no pueden soportar.

Dicho todo esto, es probable que esta pregunta suscite más argumentos religiosos que el debate técnico. Si vemos que eso sucede, probablemente lo cerremos.

Aaron Bertrand
fuente
2
¿Otra razón para los procedimientos almacenados? Para consultas largas y complicadas, debe enviar la consulta al servidor cada vez, a menos que sea una sproc, básicamente solo está presionando "exec sprocname" y algunos parámetros. Esto podría marcar la diferencia en una red lenta (u ocupada).
David Crowell
0

Si bien respeto al remitente, discrepo humildemente con la respuesta proporcionada y no por "razones religiosas". En otras palabras, creo que no hay ninguna instalación que haya proporcionado Microsoft que disminuya la necesidad de orientación para usar los procedimientos almacenados.

Cualquier orientación proporcionada a un desarrollador que favorezca el uso de consultas SQL de texto sin procesar debe estar llena de muchas advertencias, de modo que creo que el consejo más prudente es alentar en gran medida el uso de procedimientos almacenados y desalentar a sus equipos de desarrolladores a participar en la práctica de incrustar sentencias de SQL en el código, o enviar solicitudes de SQL basadas en texto sin formato y sin formato, fuera de los SPROC de SQL (procedimientos almacenados).

Creo que la respuesta simple a la pregunta de por qué usar un SPROC es como supuso el remitente: los SPROC se analizan, optimizan y compilan. Como tal, sus planes de consulta / ejecución se almacenan en caché porque ha guardado una representación estática de una consulta y, normalmente, la variará solo por parámetros, lo que no es cierto en el caso de las instrucciones SQL copiadas / pegadas que probablemente se transformarán de página a página y componente / nivel, y a menudo se varían en la medida en que se pueden especificar diferentes tablas, incluso nombres de bases de datos, de llamada a llamada. Permitiendo este tipo de dinámica ad hocEl envío de SQL disminuye en gran medida la probabilidad de que DB Engine reutilice el plan de consulta para sus declaraciones ad hoc, de acuerdo con algunas reglas muy estrictas. Aquí, hago la distinción entre consultas dinámicas ad hoc (en el espíritu de la pregunta planteada) versus el uso del eficiente Sistema SPROC sp_executesql.

Más específicamente, hay los siguientes componentes:

  • Planes de consulta en serie y paralelos que no contienen el contexto del usuario y permiten su reutilización por parte del motor DB
  • Contexto de ejecución que permite la reutilización de un plan de consulta por un nuevo usuario con diferentes parámetros de datos.
  • Procedimiento de caché que es lo que consulta el motor de base de datos para crear las eficiencias que buscamos.

Cuando se emite una declaración SQL desde una página web, denominada "declaración ad hoc", el motor busca un plan de ejecución existente para manejar la solicitud. Debido a que este es un texto enviado por un usuario, será ingerido, analizado, compilado y ejecutado, si es válido. En este momento recibirá un costo de consulta de cero. El costo de la consulta se usa cuando el motor de base de datos usa su algoritmo para determinar qué planes de ejecución desalojar de la memoria caché.

Las consultas ad hoc reciben un valor de costo de consulta original de cero, por defecto. Tras la posterior ejecución del mismo texto de consulta ad hoc, por otro proceso de usuario (o el mismo), el costo de la consulta actual se restablece al costo de compilación original. Dado que nuestro costo de compilación de consultas ad hoc es cero, esto no es un buen augurio para la posibilidad de reutilización. Obviamente, cero es el número entero menos valorado, pero ¿por qué sería desalojado?

Cuando surgen presiones de memoria, y lo harán si tiene un sitio de uso frecuente, el motor de base de datos utiliza un algoritmo de limpieza para determinar cómo puede recuperar la memoria que está utilizando la caché de procedimientos. Utiliza el costo de la consulta actual para decidir qué planes desalojar. Como puede suponer, los planes con un costo de cero son los primeros en ser desalojados de la memoria caché porque cero significa esencialmente "no hay usuarios actuales o referencias a este plan".

  • Nota: Planes de ejecución ad hoc: el costo actual se incrementa por cada proceso de usuario, por el costo de compilación original del plan. Sin embargo, el costo máximo de ningún plan puede ser mayor que su costo de compilación original ... en el caso de consultas ad hoc ... cero. Por lo tanto, se "aumentará" en ese valor ... cero, lo que esencialmente significa que seguirá siendo el plan de menor costo.

Por lo tanto, es bastante probable que dicho plan sea desalojado primero cuando surjan presiones de memoria.

Por lo tanto, si tiene un servidor incorporado con mucha memoria "más allá de sus necesidades", es posible que no experimente este problema con tanta frecuencia como un servidor ocupado que solo tiene memoria "suficiente" para manejar su carga de trabajo. (Lo sentimos, la capacidad de memoria del servidor y la utilización son algo subjetivas / relativas, aunque el algoritmo no lo es).

Ahora, si estoy objetivamente incorrecto sobre uno o más puntos, ciertamente estoy abierto a ser corregido.

Por último, el autor escribió:

"Ahora tenemos una optimización a nivel de declaración, por lo que una consulta correctamente parametrizada proveniente de una aplicación puede aprovechar el mismo plan de ejecución que esa consulta incorporada en un procedimiento almacenado".

Creo que el autor se refiere a la opción "optimizar para cargas de trabajo ad hoc".

Si es así, esta opción permite un proceso de dos pasos que evita enviar inmediatamente el plan de consulta completo a la caché de procedimientos. Solo envía un trozo de consulta más pequeño allí. Si se envía una llamada de consulta exacta al servidor mientras el código auxiliar de consulta todavía está en la caché de procedimientos, el plan completo de ejecución de la consulta se guarda en la caché de procedimientos, en ese momento. Esto ahorra memoria, que durante los incidentes de presión de memoria, puede permitir que el algoritmo de desalojo expulse su código auxiliar con menos frecuencia que un plan de consulta más grande que se almacenó en caché. Nuevamente, esto depende de la memoria y la utilización de su servidor.

Sin embargo, debe activar esta opción, ya que está desactivada de forma predeterminada.

Por último, quiero enfatizar que, a menudo, la razón por la cual los desarrolladores incrustarían SQL en páginas, componentes y otros lugares, es porque desean ser flexibles y enviar consultas SQL dinámicas al motor de la base de datos. Por lo tanto, en un caso de uso del mundo real, es poco probable que se envíe el mismo texto, llamada sobre llamada, al igual que el almacenamiento en caché / eficiencia que buscamos, al enviar consultas ad hoc a SQL Server.

Para obtener información adicional, consulte:

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

Mejor
Henry

Enrique
fuente
44
He leído varios párrafos de su publicación, cuidadosamente, dos o tres veces, y todavía no tengo idea de qué pensamientos está tratando de transmitir. En algunos casos, parece que al final de las oraciones dice exactamente lo contrario de lo que la oración comenzó a intentar decir. Realmente necesita corregir y editar esta presentación con cuidado.
Pieter Geerkens
Gracias por los comentarios de Pieter. Si este es el caso, es posible que deba acortar mis oraciones para aclarar el punto. ¿Puedes dar un ejemplo de dónde parezco decir lo contrario del pensamiento original? Muy apreciado.
Henry
No, no me refería a Optimizar para cargas de trabajo ad hoc, me refería a la optimización a nivel de declaración. En SQL Server 2000, por ejemplo, un procedimiento almacenado se compilaría en conjunto, por lo que no había forma de que la aplicación reutilizara un plan para su propia consulta ad hoc que coincidía con algo en el procedimiento. Diré que estoy de acuerdo con Pieter; muchas de las cosas que dices son difíciles de seguir. Cosas como "Creo que no hay ninguna instalación que haya proporcionado Microsoft que disminuya la necesidad de orientación para usar los procedimientos almacenados". son innecesariamente complejas y requieren demasiado análisis para comprender. EN MI HUMILDE OPINIÓN.
Aaron Bertrand
1
parece que su aversión al sql "ad hoc" se basa en la idea de que el sql está cambiando de alguna manera entre ejecuciones ... esto es completamente falso cuando se trata de parametrización.
b_levitt
0

TLDR: no hay una diferencia de rendimiento apreciable entre los dos, siempre que su sql en línea esté parametrizado.

Estas son las razones por las que he eliminado gradualmente los procedimientos almacenados:

  • Ejecutamos un entorno de aplicación 'beta', un entorno paralelo a la producción que comparte la base de datos de producción. Debido a que el código db está en el nivel de la aplicación y que los cambios en la estructura db son raros, podemos permitir que las personas confirmen nuevas funciones más allá del control de calidad y realicen implementaciones fuera de la ventana de implementación de producción, pero aún así proporcionan funcionalidad de producción y soluciones no críticas. Esto no sería posible si la mitad del código de la aplicación estuviera en la base de datos.

  • Practicamos devops a nivel de base de datos (octopus + dacpacs). Sin embargo, si bien la capa empresarial y las versiones superiores se pueden purgar y reemplazar básicamente, y la recuperación es todo lo contrario, eso no es cierto para los cambios incrementales y potencialmente destructivos que deben ir a las bases de datos. En consecuencia, preferimos mantener nuestras implementaciones de DB más ligeras y menos frecuentes.

  • Para evitar copias casi exactas del mismo código para parámetros opcionales, a menudo usaremos un patrón 'donde @var es nulo o @ var = table.field'. Con un proceso almacenado, es probable que obtenga el mismo plan de ejecución, a pesar de las intenciones bastante diferentes, y por lo tanto experimente problemas de rendimiento o elimine los planes en caché con sugerencias de 'recompilación'. Sin embargo, con un simple código que agrega un comentario de "firma" al final del sql, podemos forzar diferentes planes basados ​​en qué variables eran nulas (no se debe interpretar como un plan diferente para todas las combinaciones de variables, solo nulo vs no nulo).

  • Puedo hacer cambios dramáticos en los resultados con solo cambios menores sobre la marcha al sql. Por ejemplo, puedo tener una declaración que se cierra con dos CTE, "Raw" y "ReportReady". No hay nada que diga que se deben usar ambos CTE. Mi declaración sql puede ser:

    ...

    seleccione * de {(formato)} "

Esto me permite utilizar exactamente el mismo método de lógica de negocios para una llamada API simplificada y un informe que debe ser más detallado para garantizar que no duplique la lógica complicada.

  • cuando tienes una regla de "solo procs", terminas con una tonelada de redundancia en la gran mayoría de tu sql que termina siendo CRUDO: unes todos los parámetros, enumeras todos esos parámetros en la firma de proc (y ahora está en un archivo diferente en un proyecto diferente), asigna esos parámetros simples a sus columnas. Esto crea una experiencia de desarrollo bastante disjunta.

Hay razones válidas para usar procs:

  • Seguridad: aquí tienes otra capa por la que debe pasar la aplicación. Si la cuenta del servicio de la aplicación no puede tocar tablas, pero solo tiene permiso de "ejecución" en los procs, tiene alguna protección adicional. Esto no lo hace un hecho dado que tiene un costo, pero es una posibilidad.

  • Reutilización: si bien diría que la reutilización debería suceder en gran medida en la capa empresarial para asegurarse de que no se pasen por alto las reglas comerciales no relacionadas con la base de datos, todavía tenemos el tipo de software y funciones de utilidad de bajo nivel "utilizado en todas partes".

Hay algunos argumentos que realmente no son compatibles con los procs o que se mitigan fácilmente de la OMI:

  • Reutilización: mencioné esto anteriormente como un "plus", pero también quería mencionarlo aquí que la reutilización debería ocurrir en gran medida en la capa empresarial. Un proceso para insertar un registro no debe considerarse "reutilizable" cuando la capa empresarial también puede estar verificando otros servicios que no son de base de datos.

  • Hinchazón del plan de caché: la única forma en que esto será un problema es si está concatenando valores en lugar de parametrizando. El hecho de que rara vez obtienes más de un plan por proceso en realidad a menudo te perjudica cuando tienes un 'o' en una consulta

  • Tamaño de la declaración: un kb adicional de declaraciones sql sobre el nombre del proceso generalmente será insignificante en relación con los datos que regresan. Si está bien para las entidades, está bien para mí.

  • Ver la consulta exacta: hacer que las consultas sean fáciles de encontrar en el código es tan simple como agregar la ubicación de la llamada como un comentario al código. Hacer que el código se pueda copiar de código C # a ssms es tan fácil como una interpolación creativa y el uso de comentarios:

        //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 = "/*";
  • Inyección SQL: parametrice sus consultas. Hecho. En realidad, esto se puede deshacer si el proceso está usando sql dinámico.

  • Omitir la implementación: también practicamos devops a nivel de base de datos, por lo que esta no es una opción para nosotros.

  • "Lento en la aplicación, rápido en SSMS": este es un problema de almacenamiento en caché del plan que afecta a ambos lados. Las opciones establecidas simplemente provocan la compilación de un nuevo plan que parece solucionar el problema de las variables THE ONE SET OFF. Esto solo responde por qué ve resultados diferentes: las opciones establecidas NO solucionan el problema del rastreo de parámetros.

  • Los planes de ejecución de SQL en línea no se almacenan en caché: simplemente son falsos. Una declaración parametrizada, al igual que el nombre del proceso, se codifica rápidamente y luego ese hash busca un plan. Es 100% igual.

  • Para ser claros, estoy hablando de código sql en línea no generado sin procesar de un ORM: solo usamos Dapper, que es un micro ORM en el mejor de los casos.

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

/programming//a/15277/852208

b_levitt
fuente