SQL Server siempre utiliza la combinación de operadores Dividir, Ordenar y Contraer cuando mantiene un índice único como parte de una actualización que afecta (o podría afectar) a más de una fila.
Trabajando a través del ejemplo en la pregunta, podríamos escribir la actualización como una actualización de una sola fila por separado para cada una de las cuatro filas presentes:
-- Per row updates
UPDATE dbo.Banana SET pk = 2 WHERE pk = 1;
UPDATE dbo.Banana SET pk = 3 WHERE pk = 2;
UPDATE dbo.Banana SET pk = 4 WHERE pk = 3;
UPDATE dbo.Banana SET pk = 5 WHERE pk = 4;
El problema es que la primera instrucción fallará, ya que cambia pk
de 1 a 2, y ya hay una fila donde pk
= 2. El motor de almacenamiento de SQL Server requiere que los índices únicos permanezcan únicos en cada etapa del procesamiento, incluso dentro de una sola instrucción . Este es el problema resuelto por Split, Sort y Collapse.
División
El primer paso es dividir cada declaración de actualización en una eliminación seguida de una inserción:
DELETE dbo.Banana WHERE pk = 1;
INSERT dbo.Banana (pk, c1, c2) VALUES (2, 'A', 'W');
DELETE dbo.Banana WHERE pk = 2;
INSERT dbo.Banana (pk, c1, c2) VALUES (3, 'B', 'X');
DELETE dbo.Banana WHERE pk = 3;
INSERT dbo.Banana (pk, c1, c2) VALUES (4, 'C', 'Y');
DELETE dbo.Banana WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
El operador Split agrega una columna de código de acción a la secuencia (aquí denominada Act1007):
El código de acción es 1 para una actualización, 3 para una eliminación y 4 para una inserción.
Ordenar
Las declaraciones divididas anteriores aún producirían una violación de clave única transitoria falsa, por lo que el siguiente paso es ordenar las declaraciones por las claves del índice único que se actualiza ( pk
en este caso), luego por el código de acción. Para este ejemplo, esto simplemente significa que las eliminaciones (3) en la misma clave se ordenan antes de las inserciones (4). El orden resultante es:
-- Sort (pk, action)
DELETE dbo.Banana WHERE pk = 1;
DELETE dbo.Banana WHERE pk = 2;
INSERT dbo.Banana (pk, c1, c2) VALUES (2, 'A', 'W');
DELETE dbo.Banana WHERE pk = 3;
INSERT dbo.Banana (pk, c1, c2) VALUES (3, 'B', 'X');
DELETE dbo.Banana WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (4, 'C', 'Y');
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
Colapso
La etapa anterior es suficiente para garantizar la prevención de infracciones de unicidad falsas en todos los casos. Como optimización, Collapse combina eliminaciones e inserciones adyacentes en el mismo valor clave en una actualización:
-- Collapse (pk)
DELETE dbo.Banana WHERE pk = 1;
UPDATE dbo.Banana SET c1 = 'A', c2 = 'W' WHERE pk = 2;
UPDATE dbo.Banana SET c1 = 'B', c2 = 'X' WHERE pk = 3;
UPDATE dbo.Banana SET c1 = 'C', c2 = 'Y' WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
Los pares eliminar / insertar para los pk
valores 2, 3 y 4 se combinaron en una actualización, dejando una eliminación simple en pk
= 1 y una inserción para pk
= 5.
El operador Contraer agrupa filas por columnas clave y actualiza el código de acción para reflejar el resultado del colapso:
Actualización de índice agrupado
Este operador está etiquetado como Actualización, pero es capaz de insertar, actualizar y eliminar. La acción que realiza la Actualización de índice agrupado por fila está determinada por el valor del código de acción en esa fila. El operador tiene una propiedad Action para reflejar este modo de operación:
Contadores de modificación de filas
Tenga en cuenta que las tres actualizaciones anteriores no modifican las claves del índice único que se mantiene. En efecto, hemos transformado las actualizaciones de las columnas clave en el índice en actualizaciones de las columnas no clave ( c1
y c2
), más una eliminación y una inserción. Ni una eliminación ni una inserción pueden causar una violación falsa de clave única.
Una inserción o una eliminación afecta a cada columna de la fila, por lo que las estadísticas asociadas con cada columna tendrán sus contadores de modificación incrementados. Para las actualizaciones, solo las estadísticas con cualquiera de las columnas actualizadas como columna inicial tienen sus contadores de modificación incrementados (incluso si el valor no ha cambiado).
Por lo tanto, los contadores de modificación de la fila de estadísticas muestran 2 cambios a pk
, y 5 para c1
y c2
:
-- Collapse (pk)
DELETE dbo.Banana WHERE pk = 1; -- All columns modified
UPDATE dbo.Banana SET c1 = 'A', c2 = 'W' WHERE pk = 2; -- c1 and c2 modified
UPDATE dbo.Banana SET c1 = 'B', c2 = 'X' WHERE pk = 3; -- c1 and c2 modified
UPDATE dbo.Banana SET c1 = 'C', c2 = 'Y' WHERE pk = 4; -- c1 and c2 modified
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z'); -- All columns modified
Nota: Solo los cambios aplicados al objeto base (montón o índice agrupado) afectan a los contadores de modificación de filas de estadísticas. Los índices no agrupados son estructuras secundarias, que reflejan los cambios ya realizados en el objeto base. No afectan a los contadores de modificación de filas de estadísticas en absoluto.
Si un objeto tiene múltiples índices únicos, se usa una combinación separada de Split, Sort, Collapse para organizar las actualizaciones de cada uno. SQL Server optimiza este caso para índices no agrupados guardando el resultado de la división en un spool de tabla Eager, y luego vuelve a reproducir ese conjunto para cada índice único (que tendrá su propio Ordenar por teclas de índice + código de acción y Contraer).
Efecto sobre actualizaciones estadísticas
Las actualizaciones de estadísticas automáticas (si están habilitadas) ocurren cuando el optimizador de consultas necesita información estadística y nota que las estadísticas existentes están desactualizadas (o no son válidas debido a un cambio de esquema). Las estadísticas se consideran desactualizadas cuando el número de modificaciones registradas supera un umbral.
La disposición Split / Sort / Collapse da como resultado que se registren modificaciones de fila diferentes de las esperadas. Esto, a su vez, significa que una actualización de estadísticas puede activarse antes o después de lo que sería el caso de lo contrario.
En el ejemplo anterior, las modificaciones de fila para la columna clave aumentan en 2 (el cambio neto) en lugar de 4 (una para cada fila de la tabla afectada) o 5 (una para cada eliminación / actualización / inserción producida por el colapso).
Además, las columnas no clave que lógicamente no fueron modificadas por la consulta original acumulan modificaciones de fila, que pueden sumar el doble de las filas de la tabla actualizadas (una para cada eliminación y una para cada inserción).
El número de cambios registrados depende del grado de superposición entre los valores de columna de clave antiguos y nuevos (y, por lo tanto, el grado en que se pueden contraer las eliminaciones e inserciones separadas). Restableciendo la tabla entre cada ejecución, las siguientes consultas demuestran el efecto en los contadores de modificación de filas con diferentes superposiciones:
UPDATE dbo.Banana SET pk = pk + 0; -- Full overlap
UPDATE dbo.Banana SET pk = pk + 1;
UPDATE dbo.Banana SET pk = pk + 2;
UPDATE dbo.Banana SET pk = pk + 3;
UPDATE dbo.Banana SET pk = pk + 4; -- No overlap