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' MERGE
implementazione 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 MERGE
scenari 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 ON
clausola 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 ON
clausola 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:
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:
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 MATCHED
clausola 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 MERGE
compiti 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
.