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

Parametrización Simple y Planes Triviales — Parte 1

Esta es la primera parte de una serie sobre parametrización simple y planes triviales . Estas dos características de compilación están estrechamente conectadas y tienen objetivos similares. Tanto el rendimiento como la eficiencia de los objetivos para las cargas de trabajo que envían declaraciones simples con frecuencia.

A pesar de los nombres "simple" y "trivial", ambos tienen comportamientos sutiles y detalles de implementación que pueden dificultar la comprensión de cómo funcionan. Esta serie no se detiene demasiado en los conceptos básicos, sino que se concentra en aspectos menos conocidos que pueden hacer tropezar incluso a los profesionales de bases de datos más experimentados.

En esta primera parte, después de una breve introducción, analizo los efectos de la parametrización simple en el caché del plan.

Parametrización simple

Casi siempre es mejor parametrizar explícitamente declaraciones, en lugar de depender del servidor para hacerlo. Ser explícito le brinda un control completo sobre todos los aspectos del proceso de parametrización, incluido dónde se usan los parámetros, los tipos de datos precisos que se usan y cuándo se reutilizan los planes.

La mayoría de los clientes y controladores proporcionan formas específicas de utilizar la parametrización explícita. También hay opciones como sp_executesql , procedimientos almacenados y funciones.

No voy a entrar en los temas relacionados con la detección de parámetros o la inyección de SQL porque, si bien son importantes, no son el enfoque de esta serie. Aún así, debe escribir código con ambos al frente de su mente.

Para aplicaciones heredadas u otro código de terceros que no se puede cambiar fácilmente, la parametrización explícita puede no ser siempre posible. Es posible que pueda superar algunos obstáculos utilizando guías de planes de plantilla. En cualquier caso, sería una carga de trabajo inusual que no contiene al menos algunas declaraciones parametrizadas del lado del servidor.

Planes Shell

Cuando SQL Server 2005 introdujo la Parametrización forzada , la parametrización automática existente la función se renombró a Parametrización simple . A pesar del cambio de terminología, parametrización simple funciona igual que la parametrización automática siempre lo hizo:SQL Server intenta reemplazar valores literales constantes en declaraciones ad hoc con marcadores de parámetros. El objetivo es reducir las compilaciones aumentando la reutilización del plan en caché.

Veamos un ejemplo, usando la base de datos Stack Overflow 2010 en SQL Server 2019 CU 14. La compatibilidad de la base de datos se establece en 150 y el umbral de costo para el paralelismo se establece en 50 para evitar el paralelismo por el momento:

EXECUTE sys.sp_configure
    @configname = 'show advanced options',
    @configvalue = 1;
RECONFIGURE;
GO
EXECUTE sys.sp_configure
    @configname = 'cost threshold for parallelism',
    @configvalue = 50;
RECONFIGURE;

Código de ejemplo:

-- Clear the cache of plans for this database
ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2521;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2827;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3144;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3151;
GO

Esas declaraciones presentan predicados que difieren solo en sus valores literales constantes. SQL Server aplica con éxito parametrización simple , dando como resultado un plan parametrizado. El plan parametrizado único se usa cuatro veces, como podemos ver consultando el caché del plan:

SELECT
    CP.usecounts,
    CP.cacheobjtype,
    CP.objtype,
    CP.size_in_bytes,
    ST.[text],
    QP.query_plan
FROM sys.dm_exec_cached_plans AS CP
OUTER APPLY sys.dm_exec_sql_text (CP.plan_handle) AS ST
OUTER APPLY sys.dm_exec_query_plan (CP.plan_handle) AS QP
WHERE 
    ST.[text] NOT LIKE '%dm_exec_cached_plans%'
    AND ST.[text] LIKE '%DisplayName%Users%'
ORDER BY 
    CP.usecounts ASC;

Los resultados muestran un Adhoc planifique la entrada de caché para cada declaración original y una sola Preparada planificar:

Cuatro planes ad hoc y un plan preparado

Un Preparado es similar a un procedimiento almacenado, con parámetros deducidos de valores literales encontrados en el Adhoc declaración. Menciono esto como un modelo mental útil cuando pienso en el proceso de parametrización del lado del servidor.

Tenga en cuenta que SQL Server almacena en caché ambos el texto original y la forma parametrizada. Cuando la parametrización simple es exitosa, el plan asociado al texto original es Adhoc y no contiene un plan de ejecución completo. En cambio, el plan en caché es un shell con muy poco además de un puntero a Prepared plan parametrizado.

La representación XML de los planes de shell contener texto como:

<ShowPlanXML xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan" Version="1.539" Build="15.0.4188.2">
<BatchSequence>
<Batch>
<Statements>
<StmtSimple 
  StatementText="SELECT U.DisplayName&#xD;&#xA;FROM dbo.Users AS U &#xD;&#xA;WHERE U.Reputation = 3151"
  StatementId="1" 
  StatementCompId="1" 
  StatementType="SELECT" 
  RetrievedFromCache="true" 
  ParameterizedPlanHandle="0x0600050090C8321CE04B4B079E01000001000000000000000000000000000000000000000000000000000000" 
  ParameterizedText="(@1 smallint)SELECT [U].[DisplayName] FROM [dbo].[Users] [U] WHERE [U].[Reputation]=@1" />
</Statements>
</Batch>
</BatchSequence>
</ShowPlanXML>

Ese es todo el plan. El ParameterizedPlanHandle puntos del Adhoc shell al plan parametrizado completo. El valor del identificador es el mismo para los cuatro planes de shell.

Resguardos del plan

Los planes Shell son más pequeños que un plan compilado completo:16 KB en lugar de 40 KB en el ejemplo. Esto aún puede sumar una cantidad significativa de memoria si tiene muchas declaraciones que usan una parametrización simple o muchos valores de parámetros diferentes. La mayoría de las instancias de SQL Server no están tan llenas de memoria como para permitirse el lujo de desperdiciarla de esta manera. SQL Server considera que los planes shell son muy desechables, pero encontrarlos y eliminarlos consume recursos y puede convertirse en un punto de controversia.

Podemos reducir el consumo total de memoria para los planes de shell habilitando la opción optimizar para cargas de trabajo ad hoc.

EXECUTE sys.sp_configure
    @configname = 'show advanced options',
    @configvalue = 1;
RECONFIGURE;
GO
EXECUTE sys.sp_configure
    @configname = 'optimize for ad hoc workloads',
    @configvalue = 1;
RECONFIGURE;

Esto almacena en caché un pequeño código auxiliar la primera vez que se encuentra una declaración ad hoc en lugar de un shell. El resguardo sirve como un marcador para que el servidor pueda recordar que ha visto el texto de declaración exacto antes. Al encontrar el mismo texto por segunda vez, la compilación y el almacenamiento en caché continúan como si optimizar para cargas de trabajo ad hoc no estaban habilitados.

Volver a ejecutar el ejemplo con optimizar para cargas de trabajo ad hoc habilitado muestra el efecto en el caché del plan.

Resguardos de planes compilados

No se almacena en caché ningún plan para las declaraciones ad-hoc, solo un resguardo. No hay ParameterizedPlanHandle puntero a Preparado plan, aunque un plan parametrizado completo es en caché.

Ejecutar los lotes de prueba por segunda vez (sin borrar la memoria caché del plan) da el mismo resultado que cuando optimizar para cargas de trabajo ad hoc no estaba habilitado:cuatro Adhoc planes de shell que apuntan a Preparado planificar.

Antes de continuar, restablezca la optimización para cargas de trabajo ad hoc puesta a cero:

EXECUTE sys.sp_configure
    @configname = 'optimize for ad hoc workloads',
    @configvalue = 0;
RECONFIGURE;

Límites de tamaño de caché del plan

Ya sea que se utilicen estructuras de planes o resguardos de planes, aún existen desventajas en todos estos Adhoc. entradas de caché. Ya mencioné el uso total de la memoria, pero cada caché del plan también tiene un número máximo de entradas Incluso cuando el uso total de la memoria no es una preocupación, la gran cantidad puede serlo.

Los límites se pueden aumentar con el indicador de seguimiento documentado 174 (número de entradas) y el indicador de seguimiento 8032 (tamaño total). Según la carga de trabajo y otras demandas de memoria, es posible que esta no sea la mejor solución. Después de todo, solo significa almacenar en caché más Adhoc de bajo valor planes, quitando memoria a otras necesidades.

Almacenamiento en caché solo de planes preparados

Si la carga de trabajo rara vez emite lotes ad-hoc con exactamente el mismo texto de declaración, el almacenamiento en caché de shells de planes o stubs de planes es una pérdida de recursos. Consume memoria y puede causar contención cuando los Planes SQL almacén de caché (CACHESTORE_SQLCP ) debe reducirse para ajustarse a los límites configurados.

Lo ideal sería parametrizar los lotes ad-hoc entrantes, pero solo almacenar en caché la versión parametrizada. Hacer esto tiene un costo, porque las declaraciones ad-hoc futuras deben parametrizarse antes de que puedan coincidir con el plan en caché parametrizado. Por otro lado, esto habría sucedido de todos modos ya que ya hemos dicho exacto las coincidencias textuales son raras para la carga de trabajo de destino.

Para cargas de trabajo que se benefician de una parametrización simple, pero no del almacenamiento en caché de Adhoc entradas, hay un par de opciones.

Indicador de seguimiento no documentado

La primera opción es habilitar el indicador de seguimiento no documentado 253. Esto previene el almacenamiento en caché de Adhoc planes por completo. No restringe simplemente la cantidad de tales planes, o evita que "permanezcan" en el caché, como se ha sugerido a veces.

El indicador de rastreo 253 se puede habilitar a nivel de sesión, restringiendo sus efectos solo a esa conexión, o más ampliamente como un indicador global o de inicio. También funciona como una sugerencia de consulta, pero su uso evita la parametrización simple, lo que sería contraproducente aquí. Hay una lista parcial de las cosas que impiden la parametrización simple en el documento técnico de Microsoft, Planear el almacenamiento en caché y la recompilación en SQL Server 2012.

Con el indicador de seguimiento 253 activo antes de compilar el lote , solo los Preparados las declaraciones se almacenan en caché:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
-- Do not cache ad-hoc plans
DBCC TRACEON (253);
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2521;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2827;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3144;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3151;
GO
-- Cache ad-hoc plans again
DBCC TRACEOFF (253);
GO

La consulta de caché del plan confirma solo el Preparado la declaración se almacena en caché y se reutiliza.

Solo se almacena en caché la declaración preparada

El Lote No Cacheable

La segunda opción es incluir una declaración que marque todo el lote como no almacenable en caché . Las declaraciones adecuadas a menudo están relacionadas con la seguridad o son sensibles de alguna manera.

Esto puede sonar poco práctico, pero hay un par de mitigaciones. En primer lugar, no es necesario ejecutar la declaración confidencial, solo debe estar presente . Cuando se cumple esa condición, el usuario que ejecuta el lote ni siquiera necesita permiso para ejecutar la declaración confidencial. Tenga en cuenta que el efecto se limita al lote que contiene la declaración confidencial.

A continuación se muestran dos declaraciones adecuadamente sensibles y ejemplos de uso (con las declaraciones de prueba ahora en un solo lote):

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
-- Prevent caching of all statements in this batch.
-- Neither KEY nor CERTIFICATE need to exist.
-- No special permissions are needed.
-- GOTO is used to ensure the statements are not executed.
GOTO Start
    OPEN SYMMETRIC KEY Banana 
        DECRYPTION BY CERTIFICATE Banana;
Start:
 
/* Another way to achieve the same effect without GOTO
IF 1 = 0
BEGIN
    CREATE APPLICATION ROLE Banana 
    WITH PASSWORD = '';
END;
*/
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2521;
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2827;
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3144;
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3151;
GO

El Preparado planes creados por simple parametrización aún se almacenan en caché y se reutilizan a pesar de que el lote principal está marcado como no almacenable en caché.

Solo se almacena en caché la declaración preparada

Ninguna solución es ideal, pero hasta que Microsoft proporcione una solución documentada y compatible para este problema, son las mejores opciones que conozco.

Fin de la Parte 1

Hay mucho más terreno que cubrir en este tema. La segunda parte cubrirá los tipos de datos asignados cuando parametrización simple está empleado.