sql >> Base de Datos >  >> RDS >> Database

Otro argumento para los procedimientos almacenados

Este es uno de esos debates religiosos/políticos que ha durado años:¿debo usar procedimientos almacenados o debo incluir consultas ad hoc en mi aplicación? Siempre he sido partidario de los procedimientos almacenados, por varias razones:

  • No puedo implementar protecciones de inyección SQL si la consulta se construye en el código de la aplicación. Los desarrolladores pueden conocer las consultas parametrizadas, pero nada los obliga a usarlas correctamente.
  • No puedo ajustar una consulta que está incrustada en el código fuente de la aplicación, ni puedo hacer cumplir las mejores prácticas.
  • Si encuentro una oportunidad para ajustar consultas, para implementarlo, tengo que volver a compilar y volver a implementar el código de la aplicación, en lugar de simplemente cambiar el procedimiento almacenado.
  • Si la consulta se usa en varios lugares de la aplicación, o en varias aplicaciones, y requiere un cambio, tengo que cambiarlo en varios lugares, mientras que con un procedimiento almacenado solo tengo que cambiarlo una vez (problemas de implementación a un lado).
  • También veo que mucha gente está abandonando los procedimientos almacenados en favor de los ORM. Para aplicaciones simples, esto probablemente funcionará bien, pero a medida que su aplicación se vuelve más compleja, es probable que descubra que su ORM de elección es simplemente incapaz de realizar ciertos patrones de consulta, lo que *lo obliga* a usar un procedimiento almacenado. Si admite procedimientos almacenados, eso es.

    Aunque todavía encuentro todos estos argumentos bastante convincentes, no es de lo que quiero hablar hoy; Quiero hablar sobre el rendimiento.

    Muchos argumentos simplemente dirán, "¡los procedimientos almacenados funcionan mejor!" Eso puede haber sido marginalmente cierto en algún momento, pero desde que SQL Server agregó la capacidad de compilar a nivel de declaración en lugar de a nivel de objeto, y ha adquirido una funcionalidad poderosa como optimize for ad hoc workloads , esto ya no es un argumento muy fuerte. El ajuste de índices y los patrones de consulta sensibles tienen un impacto mucho mayor en el rendimiento que el hecho de elegir utilizar un procedimiento almacenado; en las versiones modernas, dudo que encuentre muchos casos en los que exactamente la misma consulta muestre diferencias de rendimiento notables, a menos que también esté introduciendo otras variables (como ejecutar un procedimiento localmente frente a una aplicación en un centro de datos diferente en un continente diferente).

    Dicho esto, hay un aspecto de rendimiento que a menudo se pasa por alto cuando se trata de consultas ad hoc:el caché del plan. Podemos usar optimize for ad hoc workloads para evitar que los planes de un solo uso llenen nuestro caché (Kimberly Tripp (@KimberlyLTripp) de SQLskills.com tiene información excelente sobre esto aquí), y eso afecta los planes de un solo uso independientemente de si las consultas se ejecutan desde dentro de un procedimiento almacenado o se ejecutan ad hoc. Un impacto diferente que quizás no note, independientemente de esta configuración, es cuando idénticas los planes ocupan múltiples ranuras en el caché debido a las diferencias en SET opciones o deltas menores en el texto de consulta real. Todo el fenómeno "lento en la aplicación, rápido en SSMS" ha ayudado a muchas personas a resolver problemas relacionados con configuraciones como SET ARITHABORT . Hoy quería hablar sobre las diferencias del texto de consulta y demostrar algo que sorprende a la gente cada vez que lo menciono.

    Caché para grabar

    Digamos que tenemos un sistema muy simple que ejecuta AdventureWorks2012. Y solo para demostrar que no ayuda, hemos habilitado optimize for ad hoc workloads :

    EXEC sp_configure 'show advanced options', 1;
    GO
    RECONFIGURE WITH OVERRIDE;
    GO
    EXEC sp_configure 'optimize for ad hoc workloads', 1;
    GO
    RECONFIGURE WITH OVERRIDE;

    Y luego libera el caché del plan:

    DBCC FREEPROCCACHE;

    Ahora generamos algunas variaciones simples a una consulta que, por lo demás, es idéntica. Estas variaciones pueden representar potencialmente estilos de codificación para dos desarrolladores diferentes:ligeras diferencias en espacios en blanco, mayúsculas/minúsculas, etc.

    SELECT TOP (1) SalesOrderID, OrderDate, SubTotal
    FROM Sales.SalesOrderHeader
    WHERE SalesOrderID >= 75120
    ORDER BY OrderDate DESC;
    GO
     
    -- change >= 75120 to > 75119 (same logic since it's an INT)
    GO
     
    SELECT TOP (1) SalesOrderID, OrderDate, SubTotal
    FROM Sales.SalesOrderHeader
    WHERE SalesOrderID > 75119
    ORDER BY OrderDate DESC;
    GO
     
    -- change the query to all lower case
    GO
     
    select top (1) salesorderid, orderdate, subtotal
    from sales.salesorderheader
    where salesorderid > 75119
    order by orderdate desc;
    GO
     
    -- remove the parentheses around the argument for top
    GO
     
    select top 1 salesorderid, orderdate, subtotal
    from sales.salesorderheader
    where salesorderid > 75119
    order by orderdate desc;
    GO 
     
    -- add a space after top 1
    GO
     
    select top 1  salesorderid, orderdate, subtotal
    from sales.salesorderheader
    where salesorderid > 75119
    order by orderdate desc;
    GO
     
    -- remove the spaces between the commas
    GO
     
    select top 1  salesorderid,orderdate,subtotal
    from sales.salesorderheader
    where salesorderid > 75119
    order by orderdate desc;
    GO

    Si ejecutamos ese lote una vez y luego verificamos el caché del plan, vemos que tenemos 6 copias, esencialmente, exactamente del mismo plan de ejecución. Esto se debe a que el texto de la consulta tiene un hash binario, lo que significa que las mayúsculas y minúsculas y los espacios en blanco marcan la diferencia y pueden hacer que consultas idénticas parezcan exclusivas de SQL Server.

    SELECT [text], size_in_bytes, usecounts, cacheobjtype
    FROM sys.dm_exec_cached_plans AS p
    CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
    WHERE LOWER(t.[text]) LIKE '%ales.sales'+'orderheader%';

    Resultados:

    texto tamaño_en_bytes conteos de uso tipo de objeto de caché
    seleccione 1 ID de pedido de ventas superior, o… 272 1 Resumen del plan compilado
    seleccione 1 ID de pedido de ventas principal, … 272 1 Resumen del plan compilado
    seleccione el número 1 de ID de pedido de ventas, o… 272 1 Resumen del plan compilado
    seleccione el (1) ID de pedido de ventas superior,… 272 1 Resumen del plan compilado
    SELECCIONE TOP (1) Id. de pedido de venta,… 272 1 Resumen del plan compilado
    SELECCIONE TOP (1) Id. de pedido de venta,… 272 1 Resumen del plan compilado

    Resultados después de la primera ejecución de consultas "idénticas"

    Por lo tanto, esto no es del todo un desperdicio, ya que la configuración ad hoc ha permitido que SQL Server solo almacene pequeños fragmentos en la primera ejecución. Sin embargo, si volvemos a ejecutar el lote (sin liberar el caché de procedimientos), vemos un resultado un poco más alarmante:

    texto tamaño_en_bytes conteos de uso tipo de objeto de caché
    seleccione 1 ID de pedido de ventas superior, o… 49,152 1 Plan Compilado
    seleccione 1 ID de pedido de ventas principal, … 49,152 1 Plan Compilado
    seleccione el número 1 de ID de pedido de ventas, o… 49,152 1 Plan Compilado
    seleccione el (1) ID de pedido de ventas superior,… 49,152 1 Plan Compilado
    SELECCIONE TOP (1) Id. de pedido de venta,… 49,152 1 Plan Compilado
    SELECCIONE TOP (1) Id. de pedido de venta,… 49,152 1 Plan Compilado

    Resultados después de la segunda ejecución de consultas "idénticas"

    Lo mismo ocurre con las consultas parametrizadas, independientemente de que la parametrización sea simple o forzada. Y lo mismo sucede cuando la configuración ad hoc no está habilitada, excepto que sucede antes.

    El resultado neto es que esto puede producir una gran cantidad de caché de planes, incluso para consultas que parecen idénticas, hasta dos consultas en las que un desarrollador sangra con una tabulación y el otro sangra con 4 espacios. No tengo que decirte que tratar de hacer cumplir este tipo de consistencia en un equipo puede ser desde tedioso hasta imposible. Entonces, en mi opinión, esto da un fuerte guiño a modularizar, ceder a DRY y centralizar este tipo de consulta en un único procedimiento almacenado.

    Una advertencia

    Por supuesto, si coloca esta consulta en un procedimiento almacenado, solo tendrá una copia, por lo que evita por completo la posibilidad de tener varias versiones de la consulta con un texto de consulta ligeramente diferente. Ahora, también podría argumentar que diferentes usuarios pueden crear el mismo procedimiento almacenado con diferentes nombres, y en cada procedimiento almacenado hay una ligera variación del texto de consulta. Si bien es posible, creo que representa un problema completamente diferente. :-)