Pourquoi la deuxième fois que j'ai essayé de fusionner la même ligne qui était déjà insérée, cela a entraîné une erreur. Si cette ligne dépassait la taille de ligne maximale, il ne serait pas possible de l'insérer en premier lieu.
Tout d'abord, merci pour le script de reproduction.
Le problème n'est pas que SQL Server ne peut pas insérer ou mettre à jour une ligne particulière visible par l'utilisateur . Comme vous l'avez noté, une ligne qui a déjà été insérée dans une table ne peut certainement pas être fondamentalement trop grande pour être gérée par SQL Server.
Le problème se produit car l' MERGE
implémentation de SQL Server ajoute des informations calculées (sous forme de colonnes supplémentaires) au cours des étapes intermédiaires du plan d'exécution. Ces informations supplémentaires sont nécessaires pour des raisons techniques, afin de savoir si chaque ligne doit entraîner une insertion, une mise à jour ou une suppression; et également lié à la façon dont SQL Server évite génériquement les violations de clés transitoires lors des modifications des index.
Le moteur de stockage SQL Server requiert que les index soient uniques (en interne, y compris tout uniquificateur caché) à tout moment - au fur et à mesure que chaque ligne est traitée - plutôt qu'au début et à la fin de la transaction complète. Dans des MERGE
scénarios plus complexes , cela nécessite un fractionnement (conversion d'une mise à jour en une suppression et une insertion distinctes), un tri et un repli facultatif (transformation des insertions et des mises à jour adjacentes sur la même clé en mise à jour). Plus d'informations .
En passant, notez que le problème ne se produit pas si la table cible est un segment (supprimez l'index cluster pour le voir). Je ne recommande pas cela comme correctif, je le mentionne simplement pour mettre en évidence le lien entre le maintien de l'unicité de l'index à tout moment (groupé dans le cas présent) et le Split-Sort-Collapse.
Dans les MERGE
requêtes simples , avec des index uniques appropriés et une relation simple entre les lignes source et cible (correspondant généralement à l'aide d'une ON
clause qui présente toutes les colonnes clés), l'optimiseur de requête peut simplifier une grande partie de la logique générique, ce qui se traduit par des plans relativement simples qui ne ne nécessite pas de projet Split-Sort-Collapse ou Segment-Sequence pour vérifier que les lignes cibles ne sont touchées qu'une seule fois.
Dans les MERGE
requêtes complexes , avec une logique plus opaque, l'optimiseur est généralement incapable d'appliquer ces simplifications, exposant beaucoup plus de la logique fondamentalement complexe requise pour un traitement correct (malgré les bogues du produit, et il y en a eu beaucoup ).
Votre requête est certainement qualifiée de complexe. La ON
clause ne correspond pas aux clés d'index (et je comprends pourquoi), et la «table source» est une auto-jointure impliquant une fonction de fenêtre de classement (encore une fois, avec des raisons):
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])
...
Il en résulte de nombreuses colonnes calculées supplémentaires, principalement associées au fractionnement et aux données nécessaires lorsqu'une mise à jour est convertie en une paire d'insertion / mise à jour. Ces colonnes supplémentaires entraînent une ligne intermédiaire dépassant les 8060 octets autorisés lors d'un tri antérieur - celui juste après un filtre:
Notez que le filtre a 1 319 colonnes (expressions et colonnes de base) dans sa liste de sortie. Attacher un débogueur montre la pile d'appels au point où l'exception fatale est levée:
Notez en passant que le problème n'est pas au niveau du spouleur - l'exception y est convertie en un avertissement sur le potentiel d'une ligne trop grande.
Pourquoi la mise à jour à l'aide de la fusion échoue, alors que l'insertion le fait et la mise à jour directe le fait également?
Une mise à jour directe n'a pas la même complexité interne que le MERGE
. Il s'agit d'une opération fondamentalement plus simple qui a tendance à mieux simplifier et à optimiser. La suppression de la NOT MATCHED
clause peut également supprimer suffisamment de complexité de sorte que l'erreur n'est pas générée dans certains cas. Cela ne se produit cependant pas avec la repro.
En fin de compte, mon conseil est d'éviter MERGE
les tâches plus grandes ou plus complexes. Mon expérience est que des insertions séparées / mise à jour / suppression des déclarations ont tendance à mieux optimiser, sont plus simples à comprendre et souvent effectuer mieux dans l' ensemble, par rapport à MERGE
.