¿Por qué las tablas temporales registran la hora de inicio de la transacción?

8

Al actualizar una fila en una tabla temporal, los valores antiguos de la fila se almacenan en la tabla de historial con la hora de inicio de la transacción como SysEndTime. Los nuevos valores en la tabla actual tendrán la hora de inicio de la transacción como SysStartTime.

SysStartTimey SysEndTimeson datetime2columnas utilizadas por tablas temporales para registrar cuándo una fila era la versión actual. La hora de inicio de la transacción es la hora en que comenzó la transacción que contiene las actualizaciones.

BOL dice:

Los tiempos registrados en las columnas datetime2 del sistema se basan en la hora de inicio de la transacción misma. Por ejemplo, todas las filas insertadas dentro de una sola transacción tendrán la misma hora UTC registrada en la columna correspondiente al inicio del período SYSTEM_TIME.

Ejemplo: empiezo a actualizar todas las filas de mi tabla de pedidos en 20160707 11:00:00y la transacción tarda 5 minutos en ejecutarse. Esto crea una fila en la tabla de historial para cada fila con SysEndTimetan 20160707 11:00:00. Todas las filas en la tabla actual tendrán un SysStartTimede 20160707 11:00:00.

Si alguien ejecutara una consulta en 20160707 11:01:00(mientras se ejecuta la actualización), vería los valores antiguos (suponiendo que el nivel de aislamiento confirmado de lectura predeterminado).

Pero si alguien usara la AS OFsintaxis para consultar la tabla temporal como estaba 20160707 11:01:00, vería los nuevos valores porque SysStartTimeserían los suyos 20160707 11:00:00.

Para mí, esto significa que no muestra esas filas como estaban en ese momento. Si utilizara la hora de finalización de la transacción, el problema no existiría.

Preguntas: ¿Esto es por diseño? ¿Me estoy perdiendo de algo?

La única razón por la que puedo pensar que está usando la hora de inicio de la transacción es que es la única "conocida" cuando comienza la transacción. No sabe cuándo terminará la transacción cuando comience y llevaría tiempo aplicar la hora de finalización al final, lo que invalidaría la hora de finalización que estaba aplicando. ¿Esto tiene sentido?

Esto debería permitirle recrear el problema.

James Anderson
fuente
1
Respondió su propia pregunta, si usa la hora de finalización de la transacción, tiene otra actualización al final de la transacción: la actualización finaliza 20160707 11:04:58y ahora actualiza todas las filas con esa marca de tiempo. Pero esta actualización también se ejecuta durante unos segundos y termina en 20160707 11:05:02, ahora, ¿qué marca de tiempo es el final correcto de la transacción? O suponga que usó Read Uncommitedat 20160707 11:05:00, y obtuvo filas devueltas, pero luego AS OFno las muestra.
Dnoeth
@dnoeth Sí, supongo que esta 'pregunta' es más una aclaración de mi teoría.
James Anderson
No me sumergí en la implementación de SQL Server, pero Teradata tuvo tablas bi-temporales durante años y siempre recomiendo leer este Caso de Estudio de Richard Snodgrass (el tipo que "inventó" las consultas temporales), está basado en la sintaxis SQL anterior a ANSI de Teradata , pero los conceptos son los mismos: cs.ulb.ac.be/public/_media/teaching/infoh415/…
dnoeth

Respuestas:

4

La idea es rastrear el tiempo lógico frente al tiempo físico. Lógico simplemente se refiere a lo que un usuario / aplicación espera que sea el momento de una inserción / actualización / eliminación. El hecho de que la operación DML pueda tomar un tiempo por cualquier razón, no tiene sentido ni es fácilmente determinado y entendido por un usuario. Si alguna vez ha tenido que explicar la contención de bloqueo vs bloqueo a un contador (lo tengo), es una situación comparable.

Por ejemplo, cuando Bob "le dice" a la aplicación que todos los empleados del departamento de Bob comenzarán a ganar $ 42 / min a las 20160707 11:00:00, Bob (y sus empleados) esperan que el pago de todos ahora se calcule a $ 42 / min a partir de ese momento. A Bob no le importa que para que esto se realice, la aplicación tiene que hacer 2 lecturas y 6 escrituras en la base de datos por empleado y sus archivos de datos + registro se encuentran en un montón de unidades RAID-5 SATA II, por lo que demora aproximadamente 7 minutos para finalizar la tarea para los 256 empleados de Bob. Bob, su contador y el gerente de nómina se preocupan de que a todos sus empleados se les pague $ 42 / min a partir 20160707 11:00:00. De lo contrario, los empleados que se actualizaron en 20160707 11:00:01estarán un poco molestos, mientras que aquellos cuyos registros se actualizaron 20160707 11:00:07se reunirán fuera del departamento de nómina.

Hay casos de uso válidos para rastrear el tiempo físico, como la depuración y el análisis forense, pero para el usuario final, generalmente no tiene sentido. El Tlog mantiene tanto la información de orden como de tiempo para cada una de las operaciones de escritura (entre otras cosas), por lo que está ahí si sabes cómo mirar.

SQLmojoe
fuente
Bonitos puntos. Supongo que la tecnología solo es adecuada para ciertos casos de uso como el que mencionas. Por las razones que indico anteriormente, parece que sería un mal uso para rastrear el precio o los valores de las acciones que pueden modificarse en períodos muy cortos de tiempo.
James Anderson
En realidad no. Ese es un problema de rendimiento y escala. Las tablas temporales aún funcionan si necesita mantener un historial de precios del precio de las acciones. Solo tiene que asegurarse de que los insertos sean muy granulares y se puedan completar dentro de una ventana muy pequeña. De lo contrario, los cambios posteriores se bloquearán y si la tasa entrante es lo suficientemente alta, se producirán tiempos de espera y la posible pérdida de datos si la aplicación no puede manejar los reintentos. Si ejecuta el DB fuera de IO de fusión o con tablas optimizadas para memoria, puede manejar fácilmente decenas de miles de inserciones por segundo a más de cien mil por segundo.
SQLmojoe
3

Creo que este es realmente un defecto de diseño, aunque no es específico de SQL Server 2016, ya que todas las demás implementaciones existentes de tablas temporales (que yo sepa) tienen el mismo defecto. Los problemas que pueden surgir con las tablas temporales debido a esto son bastante graves; El escenario en su ejemplo es leve en comparación con lo que puede salir mal en general:

Referencias de clave externa rotas : supongamos que tenemos dos tablas temporales, con la tabla A que tiene una referencia de clave externa a la tabla B. Ahora supongamos que tenemos dos transacciones, ambas ejecutándose en un nivel de aislamiento LECTURA COMPROMETIDA: la transacción 1 comienza antes de la transacción 2, la transacción 2 inserta una fila en la tabla B y confirma, luego la transacción 1 inserta una fila en la tabla A con una referencia a la fila recién agregada de B. Dado que la adición de la nueva fila a B ya se confirmó, se cumple la restricción de clave externa y la transacción 1 puede comprometerse con éxito. Sin embargo, si tuviéramos que ver la base de datos "COMO DE" en algún momento entre el momento en que comenzó la transacción 1 y cuando comenzó la transacción 2, entonces veríamos la tabla A con una referencia a una fila de B que no existe. Entonces en este caso,la tabla temporal proporciona una vista inconsistente de la base de datos . Por supuesto, esta no era la intención del estándar SQL: 2011, que establece:

Las filas históricas del sistema en una tabla con versión del sistema forman instantáneas inmutables del pasado. Las restricciones que estaban vigentes cuando se creó una fila del sistema histórico ya se habrían verificado cuando esa fila era una fila del sistema actual, por lo que nunca es necesario imponer restricciones en las filas del sistema histórico.

Claves primarias no únicas : Digamos que tenemos una tabla con una clave primaria y dos transacciones, ambas en un nivel de aislamiento LEÍDO COMPROMETIDO, en el que sucede lo siguiente: después de que la transacción 1 comienza pero antes de tocar esta tabla, la transacción 2 elimina un cierto fila de la tabla y confirmaciones. Luego, la transacción 1 inserta una nueva fila con la misma clave primaria que la que se eliminó. Esto funciona bien, pero cuando mira la tabla EN EL MOMENTO de cuando comenzó la transacción 1 y cuando comenzó la transacción 2, veremos dos filas con la misma clave primaria.

Errores en actualizaciones concurrentes : Digamos que tenemos una tabla y dos transacciones que actualizan la misma fila, nuevamente en un nivel de aislamiento LEÍDO COMPROMETIDO. La transacción 1 comienza primero, pero la transacción 2 es la primera en actualizar la fila. La transacción 2 luego se confirma y la transacción 1 realiza una actualización diferente en la fila y se confirma. Todo esto está bien, excepto que si se trata de una tabla temporal, al ejecutar la actualización en la transacción 1 cuando el sistema va a insertar la fila requerida en la tabla del historial, el SysStartTime generado será la hora de inicio de la transacción 2, mientras que el SysEndTime será la hora de inicio de la transacción 1, que no es un intervalo de tiempo válido ya que SysEndTime sería anterior a SysStartTime. En este caso, SQL Server arroja un error y revierte la transacción (por ejemplo, veaesta discusión ). Esto es muy desagradable, ya que en el nivel de aislamiento LEÍDO COMPROMETIDO no se esperaría que los problemas de concurrencia condujeran a fallas directas, lo que significa que las aplicaciones no necesariamente estarán preparadas para hacer intentos de reintento. En particular, esto es contrario a una "garantía" en la documentación de Microsoft:

Este comportamiento garantiza que sus aplicaciones heredadas continuarán funcionando cuando habilite el control de versiones del sistema en tablas que se beneficiarán del control de versiones. ( enlace )

Otras implementaciones de tablas temporales se han ocupado de este escenario (dos transacciones simultáneas que actualizan la misma fila) al ofrecer una opción para "ajustar" automáticamente las marcas de tiempo si no son válidas (ver aquí y aquí ). Esta es una solución fea, ya que tiene la desafortunada consecuencia de romper la atomicidad de las transacciones, ya que otras declaraciones dentro de las mismas transacciones generalmente no tendrán sus marcas de tiempo ajustadas de la misma manera; es decir, con esta solución alternativa, si vemos la base de datos "COMO DE" ciertos puntos en el tiempo, entonces podemos ver transacciones parcialmente ejecutadas.

Solución: Ya ha sugerido la solución obvia, que es que la implementación utilice la hora de finalización de la transacción (es decir, la hora de confirmación) en lugar de la hora de inicio. Sí, es cierto que cuando ejecutamos una declaración en el medio de una transacción, es imposible saber cuál será el tiempo de confirmación (como es en el futuro, o incluso podría no existir si la transacción se realizara) espalda). Pero esto no significa que la solución no sea implementable; solo tiene que hacerse de otra manera. Por ejemplo, al realizar una instrucción UPDATE o DELETE, al crear la fila del historial, el sistema podría simplemente ingresar el ID de la transacción actual en lugar de una hora de inicio, y luego el sistema puede convertir el ID a una marca de tiempo después de que la transacción se confirme .

En el contexto de este tipo de implementación, sugeriría que antes de que se confirme la transacción, las filas que agrega a la tabla de historial no deben ser visibles para el usuario. Desde la perspectiva del usuario, simplemente debería parecer que estas filas se agregan (con la marca de tiempo de confirmación) en el momento de la confirmación. En particular, si la transacción nunca se confirma correctamente, nunca debería aparecer en el historial. Por supuesto, esto es inconsistente con el estándar SQL: 2011 que describe las inserciones en el historial (incluidas las marcas de tiempo) que ocurren en el momento de las declaraciones UPDATE y DELETE (en oposición al momento de la confirmación). Pero no creo que esto realmente importe, teniendo en cuenta que el estándar nunca se ha implementado correctamente (y posiblemente nunca se pueda) debido a los problemas descritos anteriormente,

Desde el punto de vista del rendimiento, puede parecer indeseable que el sistema tenga que retroceder y volver a visitar las filas del historial para completar la marca de tiempo de confirmación. Pero dependiendo de cómo se haga esto, el costo podría ser bastante bajo. No estoy realmente familiarizado con el funcionamiento interno de SQL Server, pero PostgreSQL, por ejemplo, utiliza un registro de escritura anticipada, lo que hace que si se realizan varias actualizaciones en las mismas partes de una tabla, esas actualizaciones se consoliden para que el los datos solo deben escribirse una vez en las páginas de la tabla física, y eso normalmente se aplicaría en este escenario. En todo caso,

Por supuesto, dado que (hasta donde yo sé) este tipo de sistema nunca se ha implementado, no puedo decir con certeza que funcionaría, tal vez hay algo que me falta, pero no veo ninguna razón por qué no pudo funcionar.

Brent Kerby
fuente
0

En el momento de confirmar su transacción, todos los datos deben escribirse dentro de las páginas de datos (en la memoria y en el disco en el archivo de registro). Incluye SysStartTimey SysEndTimecolumnas. ¿Cómo puede saber la hora de finalización de la transacción antes de que se complete realmente?

A menos que pueda predecir el futuro, usar la hora de inicio de la transacción es la única opción, incluso si es menos intuitivo.

jods
fuente