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 MERGE
implementaçã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 MERGE
cená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 ON
clá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 ON
clá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:
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:
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 MATCHED
clá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 MERGE
tarefas 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
.