Unisci overflow dimensione riga in SQL Server - "Impossibile creare una riga di dimensioni .."


8

La tabella di destinazione in cui sto cercando di unire i dati ha ~ 660 colonne. Il codice per l'unione:

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 prima volta che l'ho eseguito (ovvero quando la tabella è vuota) ha avuto esito positivo e ho inserito una riga.

La seconda volta che ho eseguito questo, con lo stesso set di dati, è stato restituito un errore:
Impossibile creare una riga della dimensione 8410 che è maggiore della dimensione massima consentita della riga di 8060.

Perché la seconda volta che ho provato a unire la stessa riga che era già stata inserita, si è verificato un errore. Se questa riga superasse la dimensione massima della riga, si aspetterebbe che non sia possibile inserirla in primo luogo.

Quindi ho provato due cose (e ci sono riuscito!):

  • Rimozione della sezione "WHEN NOT MATCHED" dall'istruzione di unione
  • Eseguendo una dichiarazione di aggiornamento con la stessa riga ho provato a unire

Perché l'aggiornamento tramite merge non riesce, mentre insert fa e anche l'aggiornamento diretto lo fa?

AGGIORNARE:

Gestito per trovare la dimensione effettiva della riga - 4978. Ho creato una nuova tabella che ha solo questa riga e trovo la dimensione della riga in questo modo: inserisci qui la descrizione dell'immagine

E ancora non vedo qualcosa che superi il limite consentito.

UPDATE (2):

Riproduzione completa

Ha fatto uno sforzo per far sì che questa riproduzione non richiedesse ulteriori oggetti ausiliari e che i dati fossero (in qualche modo) offuscati.

Ho provato questo su diversi server, dalla versione 2012 e uno dal 2008, ed è stato in grado di riprodursi completamente in tutti.

Risposte:


10

Perché la seconda volta che ho provato a unire la stessa riga che era già stata inserita, si è verificato un errore. Se questa riga superasse la dimensione massima della riga, si aspetterebbe che non sia possibile inserirla in primo luogo.

Innanzitutto, grazie per la sceneggiatura della riproduzione.

Il problema non è che SQL Server non può inserire o aggiornare una particolare riga visibile dall'utente . Come hai notato, una riga che è già stata inserita in una tabella non può essere sostanzialmente troppo grande per essere gestita da SQL Server.

Il problema si verifica perché l' MERGEimplementazione di SQL Server aggiunge informazioni calcolate (come colonne aggiuntive) durante passaggi intermedi nel piano di esecuzione. Queste informazioni aggiuntive sono necessarie per motivi tecnici, per tenere traccia dell'eventuale necessità di inserire, aggiornare o eliminare ciascuna riga; e anche in relazione al modo in cui SQL Server evita genericamente violazioni chiave transitorie durante le modifiche agli indici.

Il motore di archiviazione di SQL Server richiede che gli indici siano univoci (internamente, incluso un qualsiasi unificatore univoco) in ogni momento, man mano che viene elaborata ogni riga, anziché all'inizio e alla fine della transazione completa. In MERGEscenari più complessi , ciò richiede una suddivisione (conversione di un aggiornamento in eliminazione e inserimento separati), ordinamento e un collasso opzionale (trasformazione di inserti e aggiornamenti adiacenti sulla stessa chiave in un aggiornamento). Maggiori informazioni .

Per inciso, si noti che il problema non si verifica se la tabella di destinazione è un heap (rilasciare l'indice cluster per vedere questo). Non sto raccomandando questo come una correzione, solo menzionandolo per evidenziare la connessione tra il mantenimento dell'unicità dell'indice in ogni momento (raggruppato nel caso presente) e il Split-Sort-Collapse.

Nelle query semplici MERGE , con indici univoci adeguati e una relazione semplice tra le righe di origine e di destinazione (in genere la corrispondenza utilizzando una ONclausola che include tutte le colonne chiave), Query Optimizer può semplificare gran parte della logica generica, dando luogo a piani relativamente semplici che lo fanno non richiede un progetto Split-Sort-Collapse o Segment-Sequence per verificare che le righe di destinazione vengano toccate una sola volta.

Nelle query complesse MERGE , con una logica più opaca, l'ottimizzatore di solito non è in grado di applicare queste semplificazioni, esponendo molto più della logica fondamentalmente complessa richiesta per una corretta elaborazione (nonostante i bug del prodotto, e ce ne sono stati molti ).

La tua query è sicuramente complessa. La ONclausola non corrisponde alle chiavi dell'indice (e capisco perché), e la 'tabella di origine' è un self-join che coinvolge una funzione della finestra di classificazione (di nuovo, con motivi):

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

Ciò si traduce in molte colonne calcolate aggiuntive, associate principalmente alla divisione e ai dati necessari quando un aggiornamento viene convertito in una coppia di inserimento / aggiornamento. Queste colonne extra comportano una riga intermedia che supera gli 8060 byte consentiti in un ordinamento precedente, quello subito dopo un filtro:

L'ordinamento del problema

Si noti che il Filtro ha 1.319 colonne (espressioni e colonne di base) nel suo Elenco di output. Allegare un debugger mostra lo stack di chiamate nel punto in cui viene sollevata l'eccezione fatale:

Stack Trace

Si noti che il problema non riguarda lo spool: l'eccezione viene convertita in un avviso sul potenziale che una riga sia troppo grande.

Perché l'aggiornamento tramite merge non riesce, mentre insert fa e anche l'aggiornamento diretto lo fa?

Un aggiornamento diretto non ha la stessa complessità interna di MERGE. È un'operazione fondamentalmente più semplice che tende a semplificare e ottimizzare meglio. La rimozione della NOT MATCHEDclausola può anche rimuovere abbastanza della complessità in modo che l'errore non venga generato in alcuni casi. Ciò non accade con la riproduzione, tuttavia.

In definitiva, il mio consiglio è di evitare MERGEcompiti più grandi o più complessi. La mia esperienza è che le istruzioni separate di inserimento / aggiornamento / eliminazione tendono a ottimizzare meglio, sono più semplici da comprendere e spesso offrono prestazioni complessivamente migliori rispetto a MERGE.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.