Qual è il numero massimo di variabili locali che possono partecipare all'operazione SET?


11

Ho una procedura memorizzata che contiene la logica aziendale. Al suo interno ho circa 1609 variabili (non chiedermi perché, ecco come funziona il motore). Provo a SETuna variabile al valore concatenato di tutte le altre variabili. Di conseguenza durante la creazione ottengo l'errore:

Messaggio 8631, livello 17, stato 1, procedura XXX, riga AAA Errore interno: limite dello stack del server raggiunto. Cerca un annidamento potenzialmente profondo nella query e prova a semplificarlo.

Ho capito che l'errore è dovuto al numero di variabili che devo usare SETnell'operazione. Posso eseguire il compito suddividendolo in due.

La mia domanda è: ci sono alcune restrizioni in questo settore? Ho controllato, ma non ne ho trovato nessuno.

Abbiamo verificato l'errore descritto in questo KB , ma questo non è il nostro caso. Non utilizziamo CASEespressioni all'interno del nostro codice. Usiamo quella variabile temporanea per preparare un elenco di valori che devono essere sostituiti usando una funzione CLR. Abbiamo aggiornato il nostro SQL Server a SP3 CU6 (gli ultimi aggiornati), ma continuiamo a riscontrare l'errore.

Risposte:


16

Messaggio 8631, livello 17, stato 1, riga xxx
Errore interno: limite dello stack del server raggiunto.
Cerca un annidamento potenzialmente profondo nella query e prova a semplificarlo.

Questo errore si verifica con elenchi di concatenazioni di assegnazioni lunghe SETo SELECTvariabili a causa del modo in cui SQL Server analizza e associa questo tipo di istruzione, come un elenco nidificato di concatenazioni a due input.

Ad esempio, SET @V = @W + @X + @Y + @Zè associato a un albero del modulo:

ScaOp_Arithmetic x_aopAdd
    ScaOp_Arithmetic x_aopAdd
        ScaOp_Arithmetic x_aopAdd
            ScaOp_Identifier @W 
            ScaOp_Identifier @X 
        ScaOp_Identifier @Y 
    ScaOp_Identifier @Z 

Ogni elemento di concatenazione dopo i primi due determina un ulteriore livello di nidificazione in questa rappresentazione.

La quantità di spazio dello stack disponibile per SQL Server determina il limite ultimo per questo annidamento. Quando viene superato il limite, viene sollevata internamente un'eccezione, che alla fine porta al messaggio di errore mostrato sopra. Di seguito è mostrato un esempio di stack di chiamate di processo quando viene generato l'errore:

Traccia dello stack

Repro

DECLARE @SQL varchar(max);

SET @SQL = '
    DECLARE @S integer, @A integer = 1; 
    SET @S = @A'; -- Change to SELECT if you like

SET @SQL += REPLICATE(CONVERT(varchar(max), ' + @A'), 3410) +';'; -- Change the number 3410

-- SET @S = @A + @A + @A...
EXECUTE (@SQL);

Questo è un limite fondamentale dovuto al modo in cui più concatenazioni vengono gestite internamente. Colpisce SETe SELECTassegna in modo uniforme le istruzioni di assegnazione.

La soluzione alternativa consiste nel limitare il numero di concatenazioni eseguite in una singola istruzione. Questo sarà anche in genere più efficiente, poiché la compilazione di alberi di query approfonditi richiede molte risorse.


5

Ispirato da @ Paolo 's risposta , ho fatto qualche ricerca e ha scoperto che, se è vero che lo spazio di stack limita il numero di concatenazioni, e che lo spazio dello stack è una funzione della memoria disponibile e quindi varia, i seguenti due punti sono anche vero :

  1. c'è un modo per stipare concatenazioni aggiuntive in una singola istruzione, AND
  2. usando questo metodo per andare oltre la limitazione iniziale dello spazio dello stack, è possibile trovare un limite logico effettivo (che non sembra variare)

Innanzitutto, ho adattato il codice di test di Paul per concatenare le stringhe:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'
    DECLARE @S VARCHAR(MAX), @A VARCHAR(MAX) = ''a''; 
    SET @S = @A';

SET @SQL += REPLICATE(CONVERT(NVARCHAR(MAX), N' + @A'), 3312) + N';';

-- SET @S = @A + @A + @A...
SET @SQL += N'SELECT DATALENGTH(@S) AS [Chars In @S];';
EXECUTE (@SQL);

Con questo test, il massimo che ho potuto ottenere quando sono in esecuzione sul mio laptop non così eccezionale (solo 6 GB di RAM) è stato:

  • 3311 (restituisce 3312 caratteri totali) utilizzando SQL Server 2017 Express Edition LocalDB (14.0.3006)
  • 3512 (restituisce 3513 caratteri totali) utilizzando SQL Server 2012 Developer Edition SP4 (KB4018073) (11.0.7001)

prima di ottenere l'errore 8631 .

Successivamente, ho provato a raggruppare le concatenazioni utilizzando la parentesi in modo tale che l'operazione avrebbe concatenato più gruppi di concatenazioni. Per esempio:

SET @S = (@A + @A + @A + @A) + (@A + @A + @A + @A) + (@A + @A + @A + @A);

In questo modo sono stato in grado di andare ben oltre i limiti precedenti delle variabili 3312 e 3513. Il codice aggiornato è:

DECLARE @SQL VARCHAR(MAX), @Chunk VARCHAR(MAX);

SET @SQL = '
    DECLARE @S VARCHAR(MAX), @A VARCHAR(MAX) = ''a''; 
    SET @S = (@A+@A)';

SET @Chunk = ' + (@A' + REPLICATE(CONVERT(VARCHAR(MAX), '+@A'), 42) + ')';

SET @SQL += REPLICATE(CONVERT(VARCHAR(MAX), @Chunk), 762) + ';';

SET @SQL += 'SELECT DATALENGTH(@S) AS [Chars In @S];';

-- PRINT @SQL; -- for debug

-- SET @S = (@A+@A) + (@A + @A...) + ...
EXECUTE (@SQL);

I valori massimi (per me) ora sono da usare 42per il primo REPLICATE, usando quindi 43 variabili per gruppo, e quindi usando 762per il secondo REPLICATE, usando quindi 762 gruppi di 43 variabili ciascuno. Il gruppo iniziale è codificato con due variabili.

L'output ora mostra che ci sono 32.768 caratteri nella @Svariabile. Se aggiorno il gruppo iniziale in modo che sia (@A+@A+@A)anziché solo (@A+@A), visualizzo il seguente errore:

Messaggio 8632, livello 17, stato 2, riga XXXXX
Errore interno: è stato raggiunto un limite di servizi di espressione. Cerca espressioni potenzialmente complesse nella tua query e prova a semplificarle.

Si noti che il numero di errore è diverso da prima. Ora è: 8632 . E, ho lo stesso limite se utilizzo la mia istanza di SQL Server 2012 o l'istanza di SQL Server 2017.

Probabilmente non è un caso che il limite superiore qui - 32.768 - sia la capacità massima di SMALLINT( Int16in .NET) IF a partire da 0(il valore massimo è 32.767 ma gli array in molti / la maggior parte dei linguaggi di programmazione sono basati su 0).


0

Ora, questo è semplicemente Memoria insufficiente, in altre parole, poiché l'operazione della procedura memorizzata viene eseguita in memoria e i transistor hardware disponibili o la memoria della pagina virtuale disponibile per SQL sono pieni!

Quindi è sostanzialmente Stack Overflow in SQL Server.

Ora, prima prova a semplificare il processo, poiché sappiamo che hai bisogno di 1609 variabili,

Ma hai bisogno di tutte le variabili contemporaneamente?

Possiamo dichiarare e usare le variabili dove necessario.

Per esempio:

Declare @var1 int, @Var2 int @Var3 int, .... , @var1000 int; -- Here assume Thousand Variables are declared

Declare @Tot Int;
SET @Tot = 0;
if(True)
Begin
    SET @TOT = @TOT+ VAR1 + VAR2 + .... + VAR1000; -- This might fail; 
End

Ma se proviamo questo in un ciclo aggiungendo

Declare @Tot Int;
SET @Tot = 0;
DECLARE @i int, @Count int;
SET @i = 1;
SET @Count = 1609;
WHILE (@i <= @Count)
BEGIN
   DECLARE @SQL NVARCHAR(128);
   SET @SQL = 'SET @TOT = @TOT+ VAR'+ cast(@i as nvarchar);
   EXEC (@SQL);
   SET @i = @i + 1;
END

Nota: questo utilizzerà più CPU e richiederà un po 'più di tempo nel calcolo.

Ora questo sarà lento, ma ha il vantaggio di usare meno memoria.

Spero che questo aiuti, per favore pubblica la tua domanda in modo che possiamo capire lo scenario esatto.


-4

L'uso delle istruzioni SELECT anziché SET può migliorare le prestazioni e la leggibilità e può aggirare l'errore dichiarato. Quindi invece di:

SET @a = 1
SET @b = 2
SET @c = @e + 2*@d

Tu puoi fare:

SELECT @a = 1, @b = 2, @c = @e + 2 * @d

E imposta tutti e tre i valori in un'unica istruzione.

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.