Combinar desbordamiento de tamaño de fila en SQL Server: "No se puede crear una fila de tamaño ..."

8

La tabla de destino a la que estoy tratando de fusionar los datos tiene ~ 660 columnas. El código para la fusión:

MERGE TBL_BM_HSD_SUBJECT_AN_1 AS targetTable
USING
        (                
SELECT * 
FROM TBL_BM_HSD_SUBJECT_AN_1_STAGING
WHERE [ibi_bulk_id] in (20150520141627106) and  id in(101659113)
    ) AS sourceTable 
ON (...)
WHEN MATCHED AND ((targetTable.[sampletime] <= sourceTable.[sampletime]))
        THEN UPDATE SET ...
WHEN NOT MATCHED 
        THEN INSERT (...)
    VALUES (...)

La primera vez que ejecuté esto (es decir, cuando la tabla está vacía) resultó exitoso e insertó una fila.

La segunda vez que ejecuté esto, con el mismo conjunto de datos, se devolvió un error:
no se puede crear una fila de tamaño 8410 que sea mayor que el tamaño de fila máximo permitido de 8060.

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.

Así que probé dos cosas (¡y lo logré!):

  • Eliminando la sección "CUANDO NO SE COINCIDE" de la declaración de fusión
  • Ejecutando una declaración de actualización con la misma fila que intenté fusionar

¿Por qué la actualización mediante combinación no tiene éxito, mientras que la inserción sí y la actualización directa también?

ACTUALIZAR:

Logré encontrar el tamaño de fila real - 4978. He creado una nueva tabla que solo tiene esta fila, y encuentro el tamaño de fila de esta manera: ingrese la descripción de la imagen aquí

Y todavía no veo algo que exceda el límite permitido.

ACTUALIZACIÓN (2):

Reproducción completa

Se hizo un esfuerzo para que esta reproducción no requiera ningún objeto auxiliar adicional, y que los datos se ofusquen (algo).

Probé esto en varios servidores, desde la versión 2012, y uno desde 2008, y pude reproducir completamente en todos ellos.

Yosi Dahari
fuente

Respuestas:

10

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 MERGEimplementació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 MERGEescenarios 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 ONclá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 ONclá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:

El problema ordenado

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:

Seguimiento de pila

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 MATCHEDclá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 MERGEtareas 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.

Paul White 9
fuente