Por qué la segunda vez que intenté fusionar la misma fila que ya estaba insertada, se produjo un error. Si esta fila excediera el tamaño máximo de fila, esperaría que no sea posible insertarla en primer lugar.
Primero, gracias por el guión de reproducción.
El problema no es que SQL Server no pueda insertar o actualizar una fila visible visible para el usuario . Como notó, una fila que ya se ha insertado en una tabla ciertamente no puede ser demasiado grande para que SQL Server pueda manejarla.
El problema se produce porque la MERGE
implementación de SQL Server agrega información calculada (como columnas adicionales) durante los pasos intermedios del plan de ejecución. Esta información adicional es necesaria por razones técnicas, para realizar un seguimiento de si cada fila debe resultar en una inserción, actualización o eliminación; y también relacionado con la forma en que SQL Server evita genéricamente violaciones de claves transitorias durante los cambios en los índices.
El motor de almacenamiento de SQL Server requiere que los índices sean únicos (internamente, incluido cualquier uniquifier oculto) en todo momento, a medida que se procesa cada fila, en lugar de al inicio y al final de la transacción completa. En MERGE
escenarios más complejos , esto requiere una división (convertir una actualización en una eliminación e inserción por separado), ordenar y un colapso opcional (convertir las inserciones y actualizaciones adyacentes en la misma clave en una actualización). Más información .
Como comentario adicional, tenga en cuenta que el problema no ocurre si la tabla de destino es un montón (suelte el índice agrupado para ver esto). No estoy recomendando esto como una solución, solo lo menciono para resaltar la conexión entre mantener la unicidad del índice en todo momento (agrupado en el presente caso) y el Split-Sort-Collapse.
En consultas simples MERGE
, con índices únicos adecuados y una relación directa entre las filas de origen y de destino (que generalmente coinciden con una ON
cláusula que presenta todas las columnas clave), el optimizador de consultas puede simplificar gran parte de la lógica genérica, lo que resulta en planes comparativamente simples que lo hacen no requiere un proyecto Split-Sort-Collapse o Segment-Sequence para verificar que las filas de destino solo se toquen una vez.
En consultas complejas MERGE
, con una lógica más opaca, el optimizador generalmente no puede aplicar estas simplificaciones, exponiendo mucho más de la lógica fundamentalmente compleja requerida para el procesamiento correcto (a pesar de los errores del producto, y ha habido muchos ).
Su consulta ciertamente califica como compleja. La ON
cláusula no coincide con las claves de índice (y entiendo por qué), y la 'tabla fuente' es una unión automática que involucra una función de ventana de clasificación (nuevamente, con razones):
MERGE MERGE_REPRO_TARGET AS targetTable
USING
(
SELECT * FROM
(
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY ww,id, tenant
ORDER BY
(
SELECT COUNT(1)
FROM MERGE_REPRO_SOURCE AS targetTable
WHERE
targetTable.[ibi_bulk_id] = sourceTable.[ibi_bulk_id]
AND targetTable.[ibi_row_id] <> sourceTable.[ibi_row_id]
AND
(
(targetTable.[ww] = sourceTable.[ww])
AND (targetTable.[id] = sourceTable.[id])
AND (targetTable.[tenant] = sourceTable.[tenant])
)
AND NOT ((targetTable.[sampletime] <= sourceTable.[sampletime]))
),
sourceTable.ibi_row_id DESC
) AS idx
FROM MERGE_REPRO_SOURCE sourceTable
WHERE [ibi_bulk_id] in (20150803110418887)
) AS bulkData
where idx = 1
) AS sourceTable
ON
(targetTable.[ww] = sourceTable.[ww])
AND (targetTable.[id] = sourceTable.[id])
AND (targetTable.[tenant] = sourceTable.[tenant])
...
Esto da como resultado muchas columnas computadas adicionales, asociadas principalmente con la división y los datos necesarios cuando una actualización se convierte en un par de inserción / actualización. Estas columnas adicionales dan como resultado una fila intermedia que excede los 8060 bytes permitidos en una clasificación anterior, la que se encuentra justo después de un filtro:
Tenga en cuenta que el filtro tiene 1.319 columnas (expresiones y columnas base) en su Lista de salida. Adjuntar un depurador muestra la pila de llamadas en el punto en que se produce la excepción fatal:
Observe de pasada que el problema no está en el Spool: la excepción allí se convierte en una advertencia sobre la posibilidad de que una fila sea demasiado grande.
¿Por qué la actualización mediante combinación no tiene éxito, mientras que la inserción sí y la actualización directa también?
Una actualización directa no tiene la misma complejidad interna que el MERGE
. Es una operación fundamentalmente más simple que tiende a simplificar y optimizar mejor. Eliminar la NOT MATCHED
cláusula también puede eliminar suficiente complejidad como para que el error no se genere en algunos casos. Sin embargo, eso no sucede con la repro.
En última instancia, mi consejo es evitar MERGE
tareas más grandes o más complejas. Mi experiencia es que las declaraciones separadas de inserción / actualización / eliminación tienden a optimizarse mejor, son más fáciles de entender y, a menudo, también tienen un mejor rendimiento general en comparación con MERGE
.