Esta parece ser un área con bastantes mitos y puntos de vista conflictivos.
Entonces, ¿cuál es la diferencia entre una variable de tabla y una tabla temporal local en SQL Server?
sql-server
t-sql
temporary-tables
Martin Smith
fuente
fuente
Respuestas:
Contenido
Consideración
Esta respuesta analiza las variables de tabla "clásicas" introducidas en SQL Server 2000. SQL Server 2014 en memoria OLTP presenta los tipos de tabla con memoria optimizada. ¡Las instancias de variables de tabla de esas son diferentes en muchos aspectos a las que se analizan a continuación! ( más detalles )
Ubicación de almacenamiento
Ninguna diferencia. Ambos se almacenan en
tempdb
.He visto que sugiere que para las variables de tabla esto no siempre es el caso, pero esto se puede verificar a continuación.
Resultados de ejemplo (
tempdb
se almacenan las ubicaciones que muestran las 2 filas)Ubicación lógica
@table_variables
se comportan más como si fueran parte de la base de datos actual que las#temp
tablas. Para las variables de tabla (desde 2005), las intercalaciones de columnas si no se especifican explícitamente serán las de la base de datos actual, mientras que para las#temp
tablas utilizarán la intercalación predeterminada detempdb
( Más detalles ). Además, los tipos de datos definidos por el usuario y las colecciones XML deben estar en tempdb para usar en las#temp
tablas, pero las variables de tabla pueden usarlos desde la base de datos actual ( Fuente ).SQL Server 2012 presenta bases de datos contenidas. El comportamiento de las tablas temporales en estos difiere (h / t Aaron)
Visibilidad a diferentes ámbitos
@table_variables
solo se puede acceder dentro del lote y el alcance en el que se declaran.#temp_tables
son accesibles dentro de lotes secundarios (disparadores anidados, procedimiento,exec
llamadas).#temp_tables
creado en el ámbito externo (@@NESTLEVEL=0
) también puede abarcar lotes a medida que persisten hasta que finaliza la sesión. Sin embargo, no se puede crear ningún tipo de objeto en un lote secundario y acceder a él en el alcance de la llamada, como se discute a continuación (sin embargo, las##temp
tablas globales pueden serlo).Toda la vida
@table_variables
se crean implícitamente cuandoDECLARE @.. TABLE
se ejecuta un lote que contiene una instrucción (antes de que se ejecute cualquier código de usuario en ese lote) y se eliminan implícitamente al final.Aunque el analizador no le permitirá probar y usar la variable de tabla antes de la
DECLARE
declaración, la creación implícita se puede ver a continuación.#temp_tables
se crean explícitamente cuandoCREATE TABLE
se encuentra la instrucción TSQL y se pueden descartar explícitamenteDROP TABLE
o se descartarán implícitamente cuando finalice el lote (si se creó en un lote secundario con@@NESTLEVEL > 0
) o cuando la sesión finalice de otra manera.NB: Dentro de las rutinas almacenadas, ambos tipos de objetos se pueden almacenar en caché en lugar de crear y soltar tablas nuevas repetidamente. Sin embargo, existen restricciones sobre cuándo puede ocurrir este almacenamiento en caché que es posible violar
#temp_tables
pero que las restricciones@table_variables
impiden de todos modos. La sobrecarga de mantenimiento para las#temp
tablas en caché es ligeramente mayor que para las variables de tabla como se ilustra aquí .Metadatos de objeto
Esto es esencialmente lo mismo para ambos tipos de objeto. Se almacena en las tablas base del sistema en
tempdb
. Sin#temp
embargo, es más sencillo ver una tabla, ya queOBJECT_ID('tempdb..#T')
se puede usar para ingresar las tablas del sistema y el nombre generado internamente está más estrechamente relacionado con el nombre definido en laCREATE TABLE
declaración. Para las variables de tabla, laobject_id
función no funciona y el nombre interno se genera completamente por el sistema sin relación con el nombre de la variable. Sin embargo, a continuación se muestra que los metadatos todavía están allí al ingresar un nombre de columna (con suerte único). Para las tablas sin nombres de columna únicos, el object_id se puede determinar usandoDBCC PAGE
siempre que no estén vacíos.Salida
Actas
Las operaciones
@table_variables
se llevan a cabo como transacciones del sistema, independientemente de cualquier transacción de usuario externo, mientras que las#temp
operaciones de tabla equivalentes se llevarían a cabo como parte de la transacción del usuario en sí. Por esta razón, unROLLBACK
comando afectará a una#temp
tabla pero dejará@table_variable
intacta la tabla .Inicio sesión
Ambos generan registros de anotaciones en el registro de
tempdb
transacciones. Una idea errónea común es que este no es el caso para las variables de tabla, por lo que a continuación se muestra un script que demuestra esto, declara una variable de tabla, agrega un par de filas, luego las actualiza y las elimina.Debido a que la variable de la tabla se crea y se elimina implícitamente al inicio y al final del lote, es necesario usar varios lotes para ver el registro completo.
Devoluciones
Vista detallada
Vista de resumen (incluye el registro de la caída implícita y las tablas base del sistema)
Por lo que he podido discernir, las operaciones en ambos generan cantidades aproximadamente iguales de registro.
Si bien la cantidad de registros es muy similar, una diferencia importante es que los registros de registros relacionados con las
#temp
tablas no se pueden borrar hasta que finalice cualquier transacción de usuario que contenga, por lo que una transacción de larga duración que en algún momento escriba en las#temp
tablas evitará el truncamiento del registro,tempdb
mientras que las transacciones autónomas generado para las variables de tabla no.Las variables de tabla no son compatibles,
TRUNCATE
por lo que puede estar en desventaja de registro cuando el requisito es eliminar todas las filas de una tabla (aunque para tablas muy pequeñasDELETE
puede funcionar mejor de todos modos )Cardinalidad
Muchos de los planes de ejecución que involucran variables de tabla mostrarán una sola fila estimada como la salida de ellos. La inspección de las propiedades de la variable de tabla muestra que SQL Server cree que la variable de la tabla tiene cero filas ( aquí @Paul White explica por qué estima que se emitirá 1 fila de una tabla de cero filas ).
Sin embargo, los resultados mostrados en la sección anterior muestran un
rows
recuento preciso ensys.partitions
. El problema es que en la mayoría de las ocasiones las declaraciones que hacen referencia a las variables de la tabla se compilan mientras la tabla está vacía. Si la declaración se (re) compila después de haber@table_variable
sido poblada, se utilizará para la cardinalidad de la tabla (esto podría suceder debido a unarecompile
declaración explícita o quizás porque la declaración también hace referencia a otro objeto que causa una compilación diferida o una recompilación).El plan muestra el recuento de filas estimado exacto después de la compilación diferida.
En SQL Server 2012 SP2, se introduce el indicador de traza 2453. Más detalles están en "Motor relacional" aquí .
Cuando este indicador de rastreo está habilitado, puede hacer que las recompilaciones automáticas tengan en cuenta la cardinalidad modificada, como se explica más adelante en breve.
NB: en Azure en el nivel de compatibilidad 150, la compilación de la declaración ahora se aplaza hasta la primera ejecución . Esto significa que ya no estará sujeto al problema de estimación de fila cero.
No hay estadísticas de columna
Sin embargo, tener una cardinalidad de tabla más precisa no significa que el recuento de filas estimado sea más preciso (a menos que se realice una operación en todas las filas de la tabla). SQL Server no mantiene estadísticas de columna para las variables de la tabla, por lo que recurrirá a las suposiciones basadas en el predicado de comparación (por ejemplo, que el 10% de la tabla se devolverá para una
=
columna no exclusiva o el 30% para una>
comparación). En contraste, las estadísticas de columna se mantienen para las#temp
tablas.SQL Server mantiene un recuento del número de modificaciones realizadas en cada columna. Si el número de modificaciones desde que se compiló el plan supera el umbral de recompilación (RT), el plan se volverá a compilar y se actualizarán las estadísticas. El RT depende del tipo y tamaño de la tabla.
Del almacenamiento en caché del plan en SQL Server 2008
la
KEEP PLAN
sugerencia se puede usar para establecer el RT para#temp
tablas igual que para tablas permanentes.El efecto neto de todo esto es que, a menudo, los planes de ejecución generados para las
#temp
tablas son de órdenes de magnitud mejor que@table_variables
cuando intervienen muchas filas, ya que SQL Server tiene mejor información para trabajar.NB1: Las variables de tabla no tienen estadísticas, pero aún pueden incurrir en un evento de recompilación "Estadísticas modificadas" bajo la marca de seguimiento 2453 (no se aplica a planes "triviales") Esto parece ocurrir bajo los mismos umbrales de recompilación que se muestran para las tablas temporales anteriores con un uno adicional que si
N=0 -> RT = 1
. es decir, todas las declaraciones compiladas cuando la variable de la tabla está vacía terminarán obteniendo una recompilación y corregidasTableCardinality
la primera vez que se ejecutan cuando no están vacías. La cardinalidad de la tabla de tiempos de compilación se almacena en el plan y si la declaración se ejecuta nuevamente con la misma cardinalidad (ya sea debido a las declaraciones de flujo de control o la reutilización de un plan en caché) no se produce la recompilación.NB2: Para las tablas temporales almacenadas en caché en procedimientos almacenados, la historia de compilación es mucho más complicada que la descrita anteriormente. Ver Tablas temporales en procedimientos almacenados para todos los detalles sangrientos.
Recompila
Además de las recompilaciones basadas en modificaciones descritas anteriormente, las
#temp
tablas también pueden asociarse con compilaciones adicionales simplemente porque permiten operaciones que están prohibidas para las variables de tabla que desencadenan una compilación (por ejemploCREATE INDEX
, cambios DDLALTER TABLE
)Cierre
Se ha dicho que las variables de tabla no participan en el bloqueo. Este no es el caso. Al ejecutar los siguientes resultados en la pestaña de mensajes SSMS, se detallan los bloqueos tomados y liberados para una instrucción de inserción.
Para las consultas que
SELECT
de las variables de la tabla, Paul White señala en los comentarios que estas vienen automáticamente con unaNOLOCK
pista implícita . Esto se muestra a continuaciónSalida
Sin embargo, el impacto de esto en el bloqueo podría ser bastante menor.
Ninguno de estos resultados devuelve un orden de clave de índice que indica que SQL Server utilizó una exploración ordenada por asignación para ambos.
Ejecuté el script anterior dos veces y los resultados para la segunda ejecución están debajo
La salida de bloqueo para la variable de tabla es realmente mínima ya que SQL Server simplemente adquiere un bloqueo de estabilidad de esquema en el objeto. Pero para una
#temp
mesa es casi tan liviano ya que saca unS
bloqueo de nivel de objeto . Por supuesto, también se puede especificar explícitamente unaNOLOCK
pista o unREAD UNCOMMITTED
nivel de aislamiento cuando se trabaja con#temp
tablas.De manera similar al problema con el registro de una transacción de usuario circundante, puede significar que los bloqueos se mantengan más tiempo para las
#temp
tablas. Con el guión a continuacióncuando se ejecuta fuera de una transacción de usuario explícita para ambos casos, el único bloqueo devuelto cuando se verifica
sys.dm_tran_locks
es un bloqueo compartido enDATABASE
.Al descomentar las
BEGIN TRAN ... ROLLBACK
26 filas se devuelven mostrando que los bloqueos se mantienen tanto en el objeto como en las filas de la tabla del sistema para permitir la reversión y evitar que otras transacciones lean datos no confirmados. La operación de variable de tabla equivalente no está sujeta a reversión con la transacción del usuario y no tiene necesidad de mantener estos bloqueos para que podamos verificar en la siguiente declaración, pero los bloqueos de rastreo adquiridos y liberados en Profiler o usando el indicador de rastreo 1200 muestran que todavía hay muchos eventos de bloqueo ocurrir.Índices
Para las versiones anteriores a SQL Server 2014, los índices solo se pueden crear implícitamente en las variables de la tabla como efecto secundario de agregar una restricción única o clave primaria. Por supuesto, esto significa que solo se admiten índices únicos. Sin embargo, se puede simular un índice no agrupado no único en una tabla con un índice agrupado único simplemente declarándolo
UNIQUE NONCLUSTERED
y agregando la clave CI al final de la clave NCI deseada (SQL Server haría esto detrás de escena de todos modos, incluso si no fuera único NCI podría especificarse)Como se demostró anteriormente,
index_option
se pueden especificar varios s en la declaración de restricciónDATA_COMPRESSION
, incluidos ,IGNORE_DUP_KEY
yFILLFACTOR
(aunque no tiene sentido establecerlo, ya que solo haría una diferencia en la reconstrucción del índice y no se pueden reconstruir índices en las variables de la tabla).Además, las variables de tabla no admiten
INCLUDE
columnas d, índices filtrados (hasta 2016) o particiones, las#temp
tablas sí (el esquema de partición debe crearse entempdb
).Índices en SQL Server 2014
Los índices no únicos se pueden declarar en línea en la definición de variable de tabla en SQL Server 2014. A continuación se muestra una sintaxis de ejemplo.
Índices en SQL Server 2016
Desde CTP 3.1 ahora es posible declarar índices filtrados para variables de tabla. Por RTM puede darse el caso de que las columnas incluidas también estén permitidas, aunque es probable que no lleguen a SQL16 debido a limitaciones de recursos
Paralelismo
Las consultas que se insertan (o modifican)
@table_variables
no pueden tener un plan paralelo,#temp_tables
no están restringidas de esta manera.Existe una solución alternativa aparente en que la reescritura de la siguiente manera permite que la
SELECT
parte tenga lugar en paralelo, pero eso termina usando una tabla temporal oculta (detrás de escena)No existe tal limitación en las consultas que seleccionan de las variables de la tabla como se ilustra en mi respuesta aquí
Otras diferencias funcionales
#temp_tables
no se puede usar dentro de una función.@table_variables
se puede usar dentro de UDF de tabla escalar o de múltiples instrucciones.@table_variables
no puede tener restricciones con nombre.@table_variables
no puede serSELECT
-edINTO
,ALTER
-ed,TRUNCATE
do ser el objetivo deDBCC
comandos comoDBCC CHECKIDENT
o ofSET IDENTITY INSERT
y no admite sugerencias de tabla comoWITH (FORCESCAN)
CHECK
El optimizador no considera las restricciones en las variables de la tabla para la simplificación, los predicados implícitos o la detección de contradicciones.PAGELATCH_EX
esperas. ( Ejemplo )¿Solo memoria?
Como se indicó al principio, ambos se almacenan en páginas en
tempdb
. Sin embargo, no mencioné si hubo alguna diferencia en el comportamiento cuando se trata de escribir estas páginas en el disco.He hecho una pequeña cantidad de pruebas en esto ahora y hasta ahora no he visto tal diferencia. En la prueba específica que hice en mi instancia de SQL Server, 250 páginas parecen ser el punto de corte antes de que se escriba el archivo de datos.
Ejecutando el siguiente script
Y el monitoreo escribe en el
tempdb
archivo de datos con Process Monitor. No vi ninguno (excepto ocasionalmente en la página de inicio de la base de datos en el desplazamiento 73,728). Después de cambiar250
a251
Comencé a ver escrituras como a continuación.La captura de pantalla de arriba muestra 5 * 32 páginas escritas y una sola página que indica que 161 de las páginas fueron escritas en el disco. Obtuve el mismo punto de corte de 250 páginas cuando probé con variables de tabla también. El siguiente script muestra una forma diferente al mirar
sys.dm_os_buffer_descriptors
Resultados
Mostrando que se escribieron 192 páginas en el disco y se borró la bandera sucia. También muestra que estar escrito en el disco no significa que las páginas serán expulsadas del grupo de búferes inmediatamente. Las consultas contra esta variable de tabla aún podrían satisfacerse por completo de la memoria.
En un servidor inactivo con
max server memory
conjunto2000 MB
eDBCC MEMORYSTATUS
informes de Buffer Pool Páginas asignadas como aproximadamente 1,843,000 KB (c. 23,000 páginas) inserté en las tablas anteriores en lotes de 1,000 filas / páginas y para cada iteración registrada.Tanto la variable de tabla como la
#temp
tabla dieron gráficos casi idénticos y lograron maximizar el grupo de búferes antes de llegar al punto de que no estaban completamente almacenados en la memoria, por lo que no parece haber ninguna limitación particular en la cantidad de memoria cualquiera puede consumir.fuente
Hay algunas cosas que me gustaría señalar basadas más en experiencias particulares en lugar de estudiar. Como DBA, soy muy nuevo, así que corríjame donde sea necesario.
fuente