Mesclar tamanho de linha excedente no SQL Server - "Não é possível criar uma linha de tamanho .."

8

A tabela de destino para a qual estou tentando mesclar os dados tem ~ 660 colunas. O código para a mesclagem:

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 (...)

A primeira vez que executei isso (ou seja, quando a tabela está vazia), resultou em sucesso e inseri uma linha.

Na segunda vez em que eu executei isso, com o mesmo conjunto de dados, um erro foi retornado:
Não é possível criar uma linha de tamanho 8410 que seja maior que o tamanho máximo permitido da linha de 8060.

Por que a segunda vez que tentei mesclar a mesma linha que já estava inserida, isso resultou em um erro. Se essa linha exceder o tamanho máximo da linha, seria de esperar que não fosse possível inseri-la em primeiro lugar.

Então, tentei duas coisas (e consegui!):

  • Removendo a seção "WHEN NOT MATCHED" da instrução merge
  • Executando uma instrução de atualização com a mesma linha que tentei mesclar

Por que a atualização usando mesclagem não é bem-sucedida, enquanto a inserção faz e a atualização direta também faz?

ATUALIZAR:

Consegui encontrar o tamanho real da linha - 4978. Criei uma nova tabela que possui apenas essa linha e localize o tamanho da linha desta maneira: insira a descrição da imagem aqui

E ainda não vejo algo que exceda o limite permitido.

ATUALIZAÇÃO (2):

Reprodução completa

Fez um esforço para que essa reprodução não exigisse nenhum objeto auxiliar adicional e que os dados fossem (um pouco) ofuscados.

Tentei isso em vários servidores, da versão 2012 e um de 2008, e foi capaz de se reproduzir totalmente em todos eles.

Yosi Dahari
fonte

Respostas:

10

Por que a segunda vez que tentei mesclar a mesma linha que já estava inserida, isso resultou em um erro. Se essa linha exceder o tamanho máximo da linha, seria de esperar que não fosse possível inseri-la em primeiro lugar.

Primeiro, obrigado pelo script de reprodução.

O problema não é que o SQL Server não possa inserir ou atualizar uma linha específica visível ao usuário . Como você observou, uma linha que já foi inserida em uma tabela certamente não pode ser fundamentalmente muito grande para o SQL Server manipular.

O problema ocorre porque a MERGEimplementação do SQL Server adiciona informações calculadas (como colunas extras) durante etapas intermediárias no plano de execução. Essas informações extras são necessárias por motivos técnicos, para acompanhar se cada linha deve resultar em uma inserção, atualização ou exclusão; e também relacionado à maneira como o SQL Server evita genericamente violações transitórias de chave durante alterações nos índices.

O Mecanismo de armazenamento do SQL Server exige que os índices sejam exclusivos (internamente, incluindo qualquer uniquificador oculto) o tempo todo - à medida que cada linha é processada - em vez de no início e no final da transação completa. Em MERGEcenários mais complexos , isso requer uma divisão (conversão de uma atualização em uma exclusão e inserção separada), classificação e um recolhimento opcional (transformando inserções e atualizações adjacentes na mesma chave em uma atualização). Mais informações .

Como um aparte, observe que o problema não ocorre se a tabela de destino for um heap (descarte o índice em cluster para ver isso). Não estou recomendando isso como uma correção, apenas mencionando-o para destacar a conexão entre manter a exclusividade do índice o tempo todo (agrupado no presente caso) e o Split-Sort-Collapse.

Em consultas simples MERGE , com índices exclusivos adequados e um relacionamento direto entre as linhas de origem e de destino (normalmente correspondendo usando uma ONcláusula que apresenta todas as colunas-chave), o otimizador de consulta pode simplificar grande parte da lógica genérica, resultando em planos comparativamente simples. não exige um projeto Split-Sort-Collapse ou Segment-Sequence para verificar se as linhas de destino são tocadas apenas uma vez.

Em consultas complexas MERGE , com uma lógica mais opaca, o otimizador geralmente não pode aplicar essas simplificações, expondo muito mais a lógica fundamentalmente complexa necessária para o processamento correto (apesar dos erros do produto, e existem muitos ).

Sua consulta certamente se qualifica como complexa. A ONcláusula não corresponde às chaves de índice (e eu entendo o porquê), e a 'tabela de origem' é uma junção automática que envolve uma função da janela de classificação (novamente, com motivos):

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])
...

Isso resulta em muitas colunas computadas extras, principalmente associadas à divisão e aos dados necessários quando uma atualização é convertida em um par de inserção / atualização. Essas colunas extras resultam em uma linha intermediária que excede os 8060 bytes permitidos em uma Classificação anterior - a logo após um Filtro:

A classificação do problema

Observe que o filtro possui 1.319 colunas (expressões e colunas base) em sua lista de saída. Anexar um depurador mostra a pilha de chamadas no ponto em que a exceção fatal é gerada:

Rastreamento de pilha

Observe que o problema não está no spool - a exceção é convertida em um aviso sobre o potencial de uma linha ser muito grande.

Por que a atualização usando mesclagem não é bem-sucedida, enquanto a inserção faz e a atualização direta também faz?

Uma atualização direta não tem a mesma complexidade interna que a MERGE. É uma operação fundamentalmente mais simples que tende a simplificar e otimizar melhor. A remoção da NOT MATCHEDcláusula também pode remover a complexidade suficiente para que o erro não seja gerado em alguns casos. Isso não acontece com a reprodução, no entanto.

Por fim, meu conselho é evitar MERGEtarefas maiores ou mais complexas. Minha experiência é que instruções de inserção / atualização / exclusão separadas tendem a otimizar melhor, são mais simples de entender e também geralmente apresentam melhor desempenho geral, em comparação com MERGE.

Paul White 9
fonte