Fusionner le dépassement de taille de ligne dans SQL Server - "Impossible de créer une ligne de taille .."


8

La table cible à laquelle j'essaye de fusionner les données a environ 660 colonnes. Le code de la fusion:

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 première fois que j'ai exécuté cela (c'est-à-dire lorsque la table est vide), cela a abouti et j'ai inséré une ligne.

La deuxième fois que j'ai exécuté cela, avec le même ensemble de données, une erreur a été renvoyée:
Impossible de créer une ligne de taille 8410 qui est supérieure à la taille de ligne maximale autorisée de 8060.

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.

J'ai donc essayé deux choses (et réussi!):

  • Suppression de la section "WHEN NOT MATCHED" de l'instruction de fusion
  • Exécution d'une instruction de mise à jour avec la même ligne que j'ai essayé de fusionner

Pourquoi la mise à jour à l'aide de la fusion échoue, alors que l'insertion le fait et la mise à jour directe le fait également?

MISE À JOUR:

Géré pour trouver la taille réelle de la ligne - 4978. J'ai créé une nouvelle table qui n'a que cette ligne et trouve la taille de la ligne de cette façon: entrez la description de l'image ici

Et je ne vois toujours pas quelque chose qui dépasse la limite autorisée.

MISE À JOUR (2):

Reproduction complète

Nous avons fait un effort pour que cette reproduction ne nécessite aucun objet auxiliaire supplémentaire et que les données soient (quelque peu) obscurcies.

J'ai essayé cela sur plusieurs serveurs, à partir de la version 2012 et un de 2008, et j'ai pu reproduire entièrement dans chacun d'eux.

Réponses:


10

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' MERGEimplé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 MERGEscé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 ONclause 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 ONclause 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:

Le tri des problèmes

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:

Trace de la pile

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 MATCHEDclause 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 MERGEles 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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.