sql >> Base de Datos >  >> RDS >> Sqlserver

Almacenamiento en caché de objetos temporales de SQL Server

La creación de una tabla es una operación que consume relativamente muchos recursos y mucho tiempo. El servidor debe ubicar y asignar espacio de almacenamiento para las nuevas estructuras de datos e índices, y realizar las entradas correspondientes en varias tablas de metadatos del sistema. Todo este trabajo debe realizarse de manera que siempre funcione correctamente en condiciones de alta simultaneidad y que cumpla con todas las garantías de ACID que se esperan de una base de datos relacional.

En SQL Server, esto significa tomar los tipos correctos de bloqueos y pestillos, en la secuencia correcta, y al mismo tiempo garantizar que las entradas detalladas del registro de transacciones se confirmen de manera segura en el almacenamiento persistente antes de cualquier cambio físico en la base de datos. Estas entradas de registro garantizan que el sistema pueda devolver la base de datos a un estado coherente en caso de que se revierta una transacción o se bloquee el sistema.

Dejar caer una mesa es una operación igualmente costosa. Afortunadamente, la mayoría de las bases de datos no crean ni descartan tablas con mucha frecuencia. La excepción obvia a esto es la base de datos del sistema tempdb . Esta única base de datos contiene el almacenamiento físico, las estructuras de asignación, los metadatos del sistema y las entradas del registro de transacciones para todas las tablas temporales y variables de tabla en toda la instancia de SQL Server.

Está en la naturaleza de las tablas temporales y las variables de tabla que se crean y eliminan con mucha más frecuencia que otros tipos de objetos de base de datos. Cuando esta alta frecuencia natural de creación y destrucción se combina con el efecto de concentración de todas las tablas temporales y las variables de tabla asociadas con una sola base de datos, no sorprende que puedan surgir conflictos en las estructuras de asignación y metadatos de tempdb base de datos.

Almacenamiento en caché de objetos temporales

Para reducir el impacto en tempdb estructuras, SQL Server puede almacenar en caché objetos temporales para su reutilización. En lugar de eliminar un objeto temporal, SQL Server conserva los metadatos del sistema y trunca los datos de la tabla. Si la tabla tiene 8 MB o menos, el truncamiento se realiza sincrónicamente; de lo contrario, se utiliza la caída diferida. En cualquier caso, el truncamiento reduce el requisito de almacenamiento a una sola página de datos (vacía) y la información de asignación a una sola página de IAM.

El almacenamiento en caché evita casi todos los costos de asignación y metadatos de crear el objeto temporal la próxima vez. Como efecto secundario de realizar menos cambios en tempdb base de datos que un ciclo completo de eliminación y recreación, el almacenamiento temporal de objetos en caché también reduce la cantidad de registro de transacciones requerido.

Lograr almacenamiento en caché

Las variables de tabla y las tablas temporales locales se pueden almacenar en caché. Para calificar para el almacenamiento en caché, una tabla temporal local o variable de tabla debe ser creado en un módulo:

  • Procedimiento almacenado (incluido un procedimiento almacenado temporal)
  • Desencadenador
  • Función con valores de tabla de instrucciones múltiples
  • Función escalar definida por el usuario

El valor de retorno de una función con valores de tabla de varias declaraciones es una variable de tabla, que a su vez puede almacenarse en caché. Los parámetros con valores de tabla (que también son variables de tabla) se pueden almacenar en caché cuando el parámetro se envía desde una aplicación cliente, por ejemplo, en código .NET usando SqlDbType.Structured . Cuando la declaración está parametrizada, las estructuras de parámetros con valores de tabla solo se pueden almacenar en caché en SQL Server 2012 o posterior.

Los siguientes no pueden ser almacenado en caché:

  • Tablas temporales globales
  • Objetos creados con SQL ad-hoc
  • Objetos creados usando SQL dinámico (por ejemplo, usando EXECUTE o sys.sp_executesql )

Para ser almacenado en caché, un objeto temporal además no debe :

  • Tener restricciones con nombre (las restricciones sin nombres explícitos están perfectamente bien)
  • Ejecutar "DDL" después de la creación del objeto
  • Estar en un módulo definido usando WITH RECOMPILE opción
  • Ser llamado usando WITH RECOMPILE opción del EXECUTE declaración

Para abordar explícitamente algunos conceptos erróneos comunes:

  • TRUNCATE TABLE no evitar el almacenamiento en caché
  • DROP TABLE no evitar el almacenamiento en caché
  • UPDATE STATISTICS no evitar el almacenamiento en caché
  • La creación automática de estadísticas no evitar el almacenamiento en caché
  • Manual CREATE STATISTICS voluntad evitar el almacenamiento en caché

Todos los objetos temporales de un módulo se evalúan por separado para determinar su idoneidad de almacenamiento en caché. Un módulo que contiene uno o más objetos temporales que no se pueden almacenar en caché aún puede calificar para el almacenamiento en caché de otros objetos temporales dentro del mismo módulo.

Un patrón común que deshabilita el almacenamiento en caché para las tablas temporales es la creación de índices después de la instrucción de creación de la tabla inicial. En la mayoría de los casos, esto se puede solucionar utilizando la clave principal y las restricciones únicas. En SQL Server 2014 y versiones posteriores, tenemos la opción de agregar índices no agrupados no exclusivos directamente en la declaración de creación de la tabla usando el INDEX cláusula.

Monitoreo y Mantenimiento

Podemos ver cuántos objetos temporales están almacenados en caché actualmente usando los contadores de caché DMV:

SELECT
    DOMCC.[type],
    DOMCC.pages_kb,
    DOMCC.pages_in_use_kb,
    DOMCC.entries_count,
    DOMCC.entries_in_use_count
FROM sys.dm_os_memory_cache_counters AS DOMCC 
WHERE 
    DOMCC.[name] = N'Temporary Tables & Table Variables';

Un ejemplo de resultado es:

Se considera que una entrada de caché está en uso mientras se esté ejecutando cualquier parte del módulo contenedor. Las ejecuciones simultáneas del mismo módulo darán como resultado la creación de múltiples objetos temporales almacenados en caché. Múltiples planes de ejecución para el mismo módulo (quizás debido a diferentes sesiones SET) opciones) también conducirá a múltiples entradas de caché para el mismo módulo.

Las entradas de la memoria caché se pueden agotar con el tiempo en respuesta a las necesidades competitivas de memoria. Los objetos temporales almacenados en caché también se pueden eliminar (asincrónicamente, mediante un subproceso del sistema en segundo plano) cuando el plan de ejecución del módulo principal se elimina del caché del plan.

Si bien no es compatible (o se recomienda de alguna manera) para los sistemas de producción, el almacenamiento de caché de objetos temporales se puede borrar completamente de forma manual con fines de prueba con:

DBCC FREESYSTEMCACHE('Temporary Tables & Table Variables')
    WITH MARK_IN_USE_FOR_REMOVAL;
WAITFOR DELAY '00:00:05';

El retraso de cinco segundos da tiempo para que se ejecute la tarea de limpieza en segundo plano. Tenga en cuenta que este comando es realmente peligroso . Solo debe emplearlo (bajo su propio riesgo) en una instancia de prueba a la que tenga acceso exclusivo. Una vez que haya terminado de probar, reinicie la instancia de SQL Server.

Almacenamiento en caché de detalles de implementación

Las variables de tabla se implementan mediante una tabla de usuario 'real' en tempdb base de datos (aunque no es una tabla que podamos consultar directamente). El nombre de la tabla asociada es "#" seguido de la representación hexadecimal de ocho dígitos de la identificación del objeto. La siguiente consulta muestra la relación:

-- A table variable
DECLARE @Z AS table (z integer NULL);
 
-- Corresponding sys.tables entry
SELECT
    T.[name],
    ObjIDFromName = CONVERT(integer, CONVERT(binary(4), RIGHT(T.[name], 8), 2)),
    T.[object_id],
    T.[type_desc],
    T.create_date,
    T.modify_date
FROM tempdb.sys.tables AS T 
WHERE
    T.[name] LIKE N'#[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F]';

A continuación se muestra un resultado de muestra. Observe cómo la identificación del objeto calculada a partir del nombre del objeto coincide con la identificación del objeto real:

Ejecutar ese script como SQL ad-hoc producirá un tempdb diferente ID de objeto (y nombre de objeto) en cada ejecución (sin almacenamiento en caché). Colocar el mismo script dentro de un módulo (por ejemplo, un procedimiento almacenado) permitirá que la variable de la tabla se almacene en caché (siempre que no se use SQL dinámico), de modo que el ID y el nombre del objeto serán los mismos en cada ejecución.

Cuando la variable de la tabla no se almacena en caché, la tabla subyacente se crea y se elimina cada vez. Cuando el almacenamiento en caché de objetos temporales está habilitado, la tabla se trunca al final del módulo en lugar de descartarse. No hay ningún cambio a los metadatos del sistema cuando se almacena en caché una variable de tabla. El impacto en las estructuras de asignación y el registro de transacciones se limita a eliminar las filas de la tabla y eliminar cualquier exceso de datos y páginas de asignación cuando finaliza el módulo.

Mesas Temporales

Cuando se usa una tabla temporal en lugar de una variable de tabla, el mecanismo básico es esencialmente el mismo, con solo un par de pasos de cambio de nombre adicionales:Cuando una tabla temporal no está en caché , es visible en tempdb con el nombre familiar proporcionado por el usuario, seguido de un montón de guiones bajos y la representación hexadecimal de la identificación del objeto como sufijo final. La tabla temporal local permanece hasta que se descarta explícitamente o hasta que finaliza el ámbito en el que se creó. Para SQL ad-hoc, esto significa que la sesión se desconecta del servidor.

Para una tabla temporal en caché , la primera vez que se ejecuta el módulo, la tabla temporal se crea al igual que para el caso no almacenado en caché. Al final del módulo, en lugar de eliminarse automáticamente (ya que finaliza el ámbito en el que se creó), la tabla temporal se trunca y luego se renombra. a la representación hexadecimal del ID del objeto (exactamente como se ve para la variable de tabla). La próxima vez que se ejecute el módulo, la tabla almacenada en caché se renombrará del formato hexadecimal al nombre proporcionado por el usuario (más guiones bajos más id de objeto hexadecimal).

Las operaciones adicionales de cambio de nombre al principio y al final del módulo implican una pequeña cantidad de cambios de metadatos del sistema. . Por lo tanto, las tablas temporales almacenadas en caché aún pueden experimentar al menos cierta contención de metadatos con tasas de reutilización muy altas. Sin embargo, el impacto en los metadatos de una tabla temporal almacenada en caché es mucho menor que en el caso no almacenado en caché (creando y eliminando la tabla cada vez).

Puede encontrar más detalles y ejemplos de cómo funciona el almacenamiento en caché de objetos temporales en mi artículo anterior.

Estadísticas en tablas temporales en caché

Como se mencionó anteriormente, las estadísticas pueden ser automáticamente creados en tablas temporales sin perder las ventajas del almacenamiento en caché de objetos temporales (como recordatorio, la creación manual de estadísticas será desactivar el almacenamiento en caché).

Una advertencia importante es que las estadísticas asociados con una tabla temporal almacenada en caché no se restablecen cuando el objeto se almacena en la memoria caché al final del módulo, o cuando el objeto en la memoria caché se recupera de la memoria caché al comienzo del módulo. Como consecuencia, las estadísticas en una tabla temporal almacenada en caché pueden quedar de una ejecución anterior no relacionada. En otras palabras, las estadísticas pueden tener absolutamente ninguna relación al contenido actual de la tabla temporal.

Obviamente, esto no es deseable, dado que la principal razón para preferir una tabla temporal local a una variable de tabla es la disponibilidad de estadísticas de distribución precisas. Como mitigación, las estadísticas se actualizarán automáticamente cuando (si) la cantidad acumulada de cambios en el objeto almacenado en caché subyacente alcanza el Umbral de recompilación interno. Esto es difícil de evaluar de antemano, porque los detalles son complejos y algo contradictorios.

La solución alternativa más completa, al tiempo que conserva los beneficios del almacenamiento temporal de objetos en caché, es:

  • Manualmente UPDATE STATISTICS en la mesa temporal dentro del módulo; y
  • Agregar una OPTION (RECOMPILE) sugerencia de declaraciones que hacen referencia a la tabla temporal

Naturalmente, hay un costo involucrado en hacer esto, pero esto suele ser aceptable. De hecho, al elegir usar una tabla temporal local en primer lugar, el autor del módulo implícitamente dice que la selección del plan probablemente sea sensible al contenido de la tabla temporal, por lo que la recompilación puede tener sentido. La actualización manual de las estadísticas garantiza que las estadísticas utilizadas durante la recopilación reflejen el contenido actual de la tabla (como seguramente esperaríamos).

Para obtener más detalles sobre cómo funciona esto, consulte mi artículo anterior sobre el tema.

Resumen y recomendaciones

El almacenamiento en caché de objetos temporales dentro de un módulo puede reducir en gran medida la presión sobre la asignación compartida y las estructuras de metadatos en tempdb base de datos. La mayor reducción se producirá cuando se utilicen variables de tabla porque el almacenamiento en caché y la reutilización de estos objetos temporales no implican modificar los metadatos en absoluto (sin operaciones de cambio de nombre). La contención en las estructuras de asignación aún se puede ver si la única página de datos en caché es insuficiente para contener todos los datos de la variable de la tabla en tiempo de ejecución.

El impacto en la calidad del plan debido a la falta de información de cardinalidad para las variables de la tabla se puede mitigar usando OPTION(RECOMPILE) o marca de seguimiento 2453 (disponible desde SQL Server 2012 en adelante). Tenga en cuenta que estas mitigaciones solo brindan al optimizador información sobre el número total de filas en la tabla.

Para generalizar, variables de tabla se usan mejor cuando los datos son pequeños (idealmente caben dentro de una sola página de datos para obtener los máximos beneficios de contención) y cuando la selección del plan no depende de los valores presentes en la variable de la tabla.

Si la información sobre la distribución de datos (densidad e histogramas) es importante para la selección del plan, use una tabla temporal local en cambio. Asegúrese de cumplir las condiciones para el almacenamiento temporal de tablas en caché, lo que en la mayoría de los casos significa no crear índices o estadísticas después de la instrucción de creación de la tabla inicial. Esto se hace más conveniente desde SQL Server 2014 en adelante debido a la introducción del INDEX cláusula de CREATE TABLE declaración.

Un UPDATE STATISTICS explícito después de cargar los datos en la tabla temporal, y OPTION (RECOMPILE) Es posible que se necesiten sugerencias sobre declaraciones que hagan referencia a la tabla para producir todos los beneficios esperados de las tablas temporales almacenadas en caché dentro de un módulo.

Es importante usar solo objetos temporales cuando produzcan un beneficio claro, más a menudo en términos de calidad del plan. El uso excesivo, ineficiente o innecesario de objetos temporales puede generar tempdb contención, incluso cuando se logra el almacenamiento en caché de objetos temporales.

El almacenamiento en caché óptimo de objetos temporales puede no ser suficiente para reducir tempdb contención a niveles aceptables en todos los casos, incluso cuando los objetos temporales solo se utilizan cuando está plenamente justificado. El uso de variables de tabla en memoria o tablas en memoria no duraderas puede proporcionar soluciones específicas en tales casos, aunque siempre hay que hacer concesiones y ninguna solución representa actualmente la mejor opción en todos los casos.