Überlauf der Zeilengröße in SQL Server zusammenführen - "Es kann keine Zeilengröße erstellt werden."

8

Die Zieltabelle, in die ich die Daten zusammenführen möchte, enthält ~ 660 Spalten. Der Code für die Zusammenführung:

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

Das erste Mal, als ich dies ausführte (dh wenn die Tabelle leer ist), führte dies zum Erfolg und fügte eine Zeile ein.

Beim zweiten Ausführen mit demselben Datensatz wurde ein Fehler zurückgegeben: Es
kann keine Zeile mit der Größe 8410 erstellt werden, die größer als die zulässige maximale Zeilengröße von 8060 ist.

Warum ich beim zweiten Versuch, dieselbe Zeile einzufügen, die bereits eingefügt wurde, zu einem Fehler geführt hat. Wenn diese Zeile die maximale Zeilengröße überschreitet, ist zu erwarten, dass sie überhaupt nicht eingefügt werden kann.

Also habe ich zwei Dinge ausprobiert (und es ist mir gelungen!):

  • Entfernen des Abschnitts "WENN NICHT PASSEND" aus der Zusammenführungsanweisung
  • Ausführen einer Update-Anweisung mit derselben Zeile, die ich zusammenführen wollte

Warum ist die Aktualisierung mithilfe der Zusammenführung nicht erfolgreich, während die Einfügung und die direkte Aktualisierung ebenfalls erfolgreich ist?

AKTUALISIEREN:

Es ist gelungen, die tatsächliche Zeilengröße zu ermitteln - 4978. Ich habe eine neue Tabelle erstellt, die nur diese Zeile enthält, und die Zeilengröße folgendermaßen ermittelt: Geben Sie hier die Bildbeschreibung ein

Und ich sehe immer noch nichts, was die zulässige Grenze überschreitet.

UPDATE (2):

Vollständige Reproduktion

Es wurde versucht, dass für diese Reproduktion keine zusätzlichen Hilfsobjekte erforderlich sind und dass die Daten (etwas) verschleiert werden.

Versuchte dies auf mehreren Servern ab Version 2012 und einem ab 2008 und konnte in allen vollständig reproduzieren.

Yosi Dahari
quelle

Antworten:

10

Warum ich beim zweiten Versuch, dieselbe Zeile einzufügen, die bereits eingefügt wurde, zu einem Fehler geführt hat. Wenn diese Zeile die maximale Zeilengröße überschreitet, ist zu erwarten, dass sie überhaupt nicht eingefügt werden kann.

Zunächst vielen Dank für das Reproduktionsskript.

Das Problem ist nicht, dass SQL Server eine bestimmte vom Benutzer sichtbare Zeile nicht einfügen oder aktualisieren kann . Wie Sie bereits bemerkt haben, kann eine Zeile, die bereits in eine Tabelle eingefügt wurde, für SQL Server nicht grundsätzlich zu groß sein.

Das Problem tritt auf, weil die SQL Server- MERGEImplementierung berechnete Informationen (als zusätzliche Spalten) während Zwischenschritten im Ausführungsplan hinzufügt. Diese zusätzlichen Informationen werden aus technischen Gründen benötigt, um zu verfolgen, ob jede Zeile zum Einfügen, Aktualisieren oder Löschen führen soll. und auch im Zusammenhang mit der Art und Weise, wie SQL Server vorübergehende Schlüsselverletzungen bei Änderungen an Indizes generell vermeidet.

Die SQL Server Storage Engine erfordert, dass Indizes zu jeder Zeit - intern, einschließlich jeder versteckten Eindeutigkeit) eindeutig sind - während jede Zeile verarbeitet wird - und nicht zu Beginn und am Ende der vollständigen Transaktion. In komplexeren MERGESzenarien erfordert dies eine Aufteilung (Konvertieren eines Updates in ein separates Löschen und Einfügen), Sortieren und ein optionales Reduzieren (Verwandeln benachbarter Einfügungen und Aktualisierungen auf demselben Schlüssel in ein Update). Weitere Informationen .

Beachten Sie außerdem, dass das Problem nicht auftritt, wenn die Zieltabelle ein Heap ist (löschen Sie den Clustered-Index, um dies anzuzeigen). Ich empfehle dies nicht als Fix, sondern erwähne es nur, um den Zusammenhang zwischen der jederzeitigen Beibehaltung der Index-Eindeutigkeit (im vorliegenden Fall gruppiert) und dem Split-Sort-Collapse hervorzuheben.

Bei einfachen MERGE Abfragen mit geeigneten eindeutigen Indizes und einer einfachen Beziehung zwischen Quell- und Zielzeilen (die normalerweise mit einer ONKlausel übereinstimmen , die alle Schlüsselspalten enthält) kann der Abfrageoptimierer einen Großteil der generischen Logik vereinfachen, was zu vergleichsweise einfachen Plänen führt Sie benötigen kein Split-Sort-Collapse- oder Segment-Sequence-Projekt, um zu überprüfen, ob die Zielzeilen nur einmal berührt werden.

Bei komplexen MERGE Abfragen mit undurchsichtigerer Logik kann der Optimierer diese Vereinfachungen normalerweise nicht anwenden, wodurch viel mehr der grundlegend komplexen Logik verfügbar gemacht wird, die für die korrekte Verarbeitung erforderlich ist (trotz Produktfehlern, und es gab viele ).

Ihre Anfrage gilt sicherlich als komplex. Die ONKlausel stimmt nicht mit den Indexschlüsseln überein (und ich verstehe warum), und die 'Quellentabelle' ist eine Selbstverknüpfung, die eine Funktion des Ranking-Fensters beinhaltet (wiederum mit Gründen):

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

Dies führt zu vielen zusätzlichen berechneten Spalten, die hauptsächlich dem Split und den Daten zugeordnet sind, die benötigt werden, wenn ein Update in ein Einfüge- / Update-Paar konvertiert wird. Diese zusätzlichen Spalten führen dazu, dass eine Zwischenzeile die zulässigen 8060 Bytes bei einer früheren Sortierung überschreitet - die unmittelbar nach einem Filter:

Das Problem Sortieren

Beachten Sie, dass der Filter 1.319 Spalten (Ausdrücke und Basisspalten) in seiner Ausgabeliste enthält. Das Anhängen eines Debuggers zeigt den Aufrufstapel an dem Punkt an, an dem die schwerwiegende Ausnahme ausgelöst wird:

Stapelverfolgung

Hinweis beiläufig , dass das Problem nicht an der Spool - die Ausnahme ist auf eine Warnung über das umgewandelte Potenzial für eine Reihe zu groß zu sein.

Warum ist die Aktualisierung mithilfe der Zusammenführung nicht erfolgreich, während die Einfügung und die direkte Aktualisierung ebenfalls erfolgreich ist?

Ein direktes Update hat nicht die gleiche interne Komplexität wie das MERGE. Es ist eine grundlegend einfachere Operation, die dazu neigt, besser zu vereinfachen und zu optimieren. Durch das Entfernen der NOT MATCHEDKlausel wird möglicherweise auch genug Komplexität entfernt, sodass der Fehler in einigen Fällen nicht generiert wird. Das passiert beim Repro allerdings nicht.

Letztendlich ist mein Rat, MERGEgrößere oder komplexere Aufgaben zu vermeiden . Ich habe die Erfahrung gemacht, dass separate Anweisungen zum Einfügen / Aktualisieren / Löschen tendenziell besser optimieren, einfacher zu verstehen sind und im Vergleich zu Anweisungen häufig insgesamt eine bessere Leistung erbringen MERGE.

Paul White 9
quelle