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.
20160707 11:04:58
y ahora actualiza todas las filas con esa marca de tiempo. Pero esta actualización también se ejecuta durante unos segundos y termina en20160707 11:05:02
, ahora, ¿qué marca de tiempo es el final correcto de la transacción? O suponga que usóRead Uncommited
at20160707 11:05:00
, y obtuvo filas devueltas, pero luegoAS OF
no las muestra.