En un intento de desacoplar una aplicación de nuestra base de datos monolítica, hemos tratado de cambiar las columnas INT IDENTITY de varias tablas para que sean una columna computada PERSISTADA que use COALESCE. Básicamente, necesitamos que la aplicación desacoplada tenga la capacidad de actualizar la base de datos para datos comunes compartidos en muchas aplicaciones y al mismo tiempo permitir que las aplicaciones existentes creen datos en estas tablas sin la necesidad de modificar el código o el procedimiento.
Entonces, esencialmente, nos hemos movido de una definición de columna de;
PkId INT IDENTITY(1,1) PRIMARY KEY
a;
PkId AS AS COALESCE(old_id, external_id, new_id) PERSISTED NOT NULL,
old_id INT NULL, -- Values here are from existing records of PkId before table change
external_id INT NULL,
new_id INT IDENTITY(2000000,1) NOT NULL
En todos los casos, el PkId también es una CLAVE PRIMARIA y, en todos los casos, excepto uno, está CLUSTERADO. Todas las tablas tienen las mismas claves e índices externos que antes. En esencia, el nuevo formato permite que el PkId sea suministrado por la aplicación desacoplada (como external_id), pero también permite que el PkId sea el valor de la columna IDENTITY, lo que permite el código existente que se basa en la columna IDENTITY mediante el uso de SCOPE_IDENTITY y @@ IDENTITY trabajar como solía hacerlo.
El problema que hemos tenido es que nos hemos encontrado con un par de consultas que solían ejecutarse en un momento aceptable para explotar por completo. Los planes de consulta generados utilizados por estas consultas no se parecen en nada a lo que solían ser antes.
Dado que la nueva columna es una CLAVE PRIMARIA, el mismo tipo de datos que antes, y PERSISTED, habría esperado que las consultas y los planes de consulta se comportaran igual que antes. ¿Debería el PkId INT COMPUTADO PERSISTADO esencialmente comportarse de la misma manera que una definición INT explícita en términos de cómo SQL Server producirá el plan de ejecución? ¿Hay otros problemas probables con este enfoque que pueda ver?
Se suponía que el propósito de este cambio nos permitiría cambiar la definición de la tabla sin la necesidad de modificar los procedimientos y el código existentes. Dados estos problemas, no creo que podamos seguir con este enfoque.
fuente

Respuestas:
PRIMERO
Es probable que no necesita las tres columnas:
old_id,external_id,new_id. Lanew_idcolumna, siendo unIDENTITY, tendrá un nuevo valor generado para cada fila, incluso cuando inserte enexternal_id. Pero, entreold_idyexternal_id, esos son más o menos mutuamente excluyentes: o ya hay unold_idvalor o esa columna, en la concepción actual, solo lo seráNULLsi se usaexternal_idonew_id. Como no va a agregar una nueva identificación "externa" a una fila que ya existe (es decir, una que tenga unold_idvalor), y no habrá valores nuevosold_id, entonces puede haber una columna que se utilice para ambos propósitosEntonces, deshágase de la
external_idcolumna y cambie el nombreold_idpara que sea algo asíold_or_external_ido lo que sea. Esto no debería requerir ningún cambio real en nada, pero reduce algunas de las complicaciones. A lo sumo, es posible que deba llamar a la columnaexternal_id, incluso si contiene valores "antiguos", si el código de la aplicación ya está escrito para insertarloexternal_id.Eso reduce la nueva estructura para ser justa:
Ahora solo ha agregado 8 bytes por fila en lugar de 12 bytes (suponiendo que no esté utilizando la
SPARSEopción o la compresión de datos). Y no necesitaba cambiar ningún código, T-SQL o código de aplicación.SEGUNDO
Continuando por este camino de simplificación, veamos lo que nos queda:
old_or_external_idcolumna ya tiene valores, o se le dará un nuevo valor desde la aplicación, o se dejará comoNULL.new_idsiempre tendrá un nuevo valor generado, pero ese valor sólo se utilizará si elold_or_external_idcolumna esNULL.Nunca hay un momento en que necesite valores en ambos
old_or_external_idynew_id. Sí, habrá momentos en que ambas columnas tienen valores debido anew_idser unIDENTITY, pero esosnew_idvalores se ignoran. De nuevo, estos dos campos son mutuamente excluyentes. ¿Y ahora qué?Ahora podemos ver por qué necesitábamos el
external_iden primer lugar. Teniendo en cuenta que es posible insertar en unaIDENTITYcolumna usandoSET IDENTITY_INSERT {table_name} ON;, podría salirse con la suya sin hacer ningún cambio de esquema y solo modificar el código de su aplicación para envolver lasINSERTdeclaraciones / operaciones enSET IDENTITY_INSERT {table_name} ON;ySET IDENTITY_INSERT {table_name} OFF;declaraciones. Luego, debe determinar a qué rango inicial restablecer laIDENTITYcolumna (para los valores recién generados), ya que deberá estar muy por encima de los valores que el código de la aplicación insertará, ya que la inserción de un valor más alto hará que el siguiente valor generado automáticamente ser mayor que el valor MAX actual. Pero siempre puede insertar un valor que esté por debajo del valor IDENT_CURRENT .La combinación de las columnas
old_or_external_idynew_idtampoco aumenta las posibilidades de encontrarse con una situación de valor superpuesto entre los valores generados automáticamente y los valores generados por la aplicación, ya que la intención de tener las columnas 2, o incluso 3, es combinarlas en un valor de clave primaria, y esos son siempre valores únicos.En este enfoque, solo necesita:
Deje las tablas como:
Esto agrega 0 bytes a cada fila, en lugar de 8, o incluso 12.
SET IDENTITY_INSERT {table_name} ON;ySET IDENTITY_INSERT {table_name} OFF;declaraciones.SEGUNDO, Parte B
Una variación en el enfoque anotado directamente arriba sería hacer que los valores de inserción del código de la aplicación comiencen con -1 y desciendan desde allí. Esto deja a los
IDENTITYvalores por ser los únicos que van hacia arriba . El beneficio aquí es que no solo no complica el esquema, sino que tampoco tiene que preocuparse por encontrarse con ID superpuestos (si los valores generados por la aplicación se encuentran en el nuevo rango generado automáticamente). Esta es solo una opción si aún no está utilizando valores de ID negativos (y parece bastante raro que las personas usen valores negativos en columnas generadas automáticamente, por lo que esta debería ser una posibilidad en la mayoría de las situaciones).En este enfoque, solo necesita:
Deje las tablas como:
Esto agrega 0 bytes a cada fila, en lugar de 8, o incluso 12.
-1.SET IDENTITY_INSERT {table_name} ON;ySET IDENTITY_INSERT {table_name} OFF;declaraciones.Aquí aún debe hacer lo siguiente
IDENTITY_INSERT, pero: no agrega ninguna columna nueva, no necesita "volver a colocar" ningunaIDENTITYcolumna y no tiene riesgo futuro de superposiciones.SEGUNDO, Parte 3
Una última variación de este enfoque sería posiblemente intercambiar las
IDENTITYcolumnas y, en su lugar, usar Secuencias . La razón para adoptar este enfoque es poder tener los valores de inserción del código de la aplicación que son: positivo, superior al rango generado automáticamente (no inferior) y sin necesidad de hacerloSET IDENTITY_INSERT ON / OFF.En este enfoque, solo necesita:
Copie la
IDENTITYcolumna a una nueva columna que no tenga laIDENTITYpropiedad, pero que tenga unaDEFAULTRestricción usando la función NEXT VALUE FOR :Esto agrega 0 bytes a cada fila, en lugar de 8, o incluso 12.
SET IDENTITY_INSERT {table_name} ON;ySET IDENTITY_INSERT {table_name} OFF;declaraciones.Sin embargo , debido a la exigencia de que el código, ya sea con
SCOPE_IDENTITY()o@@IDENTITYaún funciona correctamente, el cambio a secuencias no es actualmente una opción ya que parece que no existe un equivalente de esas funciones para las secuencias :-(. Triste!fuente
IDENTITY_INSERT, pero no lo he probado. No estoy seguro de que la opción # 1 vaya a resolver su problema general, fue solo una observación para reducir la complejidad innecesaria. Aún así, si tiene varios subprocesos que insertan nuevas ID "externas", ¿cómo garantiza que sean únicos?IDENTITY_INSERT ONpara la misma tabla en dos sesiones y estaba insertando en ambas sin problemas.