Qual é o número máximo de variáveis ​​locais que podem participar da operação SET?

11

Eu tenho um procedimento armazenado que contém lógica de negócios. Dentro dele, tenho cerca de 1609 variáveis ​​(não me pergunte por que, é assim que o mecanismo funciona). Eu tento SETuma variável com o valor concatenado de todas as outras variáveis. Como resultado, durante a criação, recebo o erro:

Msg 8631, Nível 17, Estado 1, Procedimento XXX, Linha AAAA Erro interno: o limite da pilha do servidor foi atingido. Procure um aninhamento potencialmente profundo em sua consulta e tente simplificá-lo.

Eu descobri que o erro é devido ao número de variáveis ​​que eu preciso usar na SEToperação. Eu posso executar a tarefa dividindo-a em duas.

Minha pergunta é: existem algumas restrições nessa área? Eu verifiquei, mas não encontrei nenhum.

Verificamos o erro descrito neste KB , mas este não é o nosso caso. Não usamos nenhuma CASEexpressão dentro do nosso código. Usamos essa variável temporária para preparar uma lista de valores que precisam ser substituídos usando uma função CLR. Atualizamos nosso SQL Server para o SP3 CU6 (mais recente), mas ainda temos o erro.

Bogdan Bogdanov
fonte

Respostas:

16

Msg 8631, Nível 17, Estado 1, Linha xxx
Erro interno: o limite da pilha do servidor foi atingido.
Procure um aninhamento potencialmente profundo em sua consulta e tente simplificá-lo.

Esse erro ocorre nas listas de concatenação de atribuições longas SETou SELECTvariáveis, devido à maneira como o SQL Server analisa e vincula esse tipo de instrução - como uma lista aninhada de concatenações de duas entradas.

Por exemplo, SET @V = @W + @X + @Y + @Zestá vinculado a uma árvore do formulário:

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

Cada elemento de concatenação após os dois primeiros resulta em um nível extra de aninhamento nessa representação.

A quantidade de espaço de pilha disponível para o SQL Server determina o limite máximo para esse aninhamento. Quando o limite é excedido, uma exceção é gerada internamente, o que eventualmente resulta na mensagem de erro mostrada acima. Um exemplo de pilha de chamada de processo quando o erro é gerado é mostrado abaixo:

Rastreio de pilha

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);

Esse é um limite fundamental devido à maneira como várias concatenações são tratadas internamente. Afeta SETe SELECTinstruções de atribuição variável igualmente.

A solução alternativa é limitar o número de concatenações executadas em uma única instrução. Isso também costuma ser mais eficiente, pois a compilação de árvores de consultas profundas consome muitos recursos.

Paul White 9
fonte
5

Inspirado por @ Paul 's resposta , eu fiz algumas pesquisas e descobrimos que, embora seja verdade que o espaço de pilha faz limite o número de encadeamentos, e que o espaço de pilha é uma função de memória disponível e, portanto, varia, os dois pontos seguintes também são verdadeiras :

  1. existe uma maneira de incluir concatenações adicionais em uma única declaração E
  2. Usando esse método para ir além da limitação inicial do espaço de pilha, pode ser encontrado um limite lógico real (que não parece variar)

Primeiro, adaptei o código de teste de Paul para concatenar seqüências de caracteres:

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);

Com este teste, o máximo que eu consegui ao executar no meu laptop não tão bom (apenas 6 GB de RAM) foi:

  • 3311 (retorna 3312 caracteres totais) usando o SQL Server 2017 Express Edition LocalDB (14.0.3006)
  • 3512 (retorna 3513 caracteres totais) usando o SQL Server 2012 Developer Edition SP4 (KB4018073) (11.0.7001)

antes de obter o erro 8631 .

Em seguida, tentei agrupar as concatenações usando parênteses, para que a operação concatenasse vários grupos de concatenações. Por exemplo:

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

Fazendo isso, fui capaz de ir muito além dos limites anteriores das variáveis ​​3312 e 3513. O código atualizado é:

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);

Os valores máximos (para mim) agora devem ser usados 42para o primeiro REPLICATE, usando 43 variáveis ​​por grupo e, em seguida, usando 762o segundo REPLICATE, usando 762 grupos de 43 variáveis ​​cada. O grupo inicial é codificado com duas variáveis.

A saída agora mostra que existem 32.768 caracteres na @Svariável. Se eu atualizar o grupo inicial em (@A+@A+@A)vez de apenas (@A+@A), recebo o seguinte erro:

Msg 8632, Nível 17, Estado 2, Linha XXXXX
Erro interno: um limite de serviços de expressão foi atingido. Por favor, procure expressões potencialmente complexas em sua consulta e tente simplificá-las.

Observe que o número do erro é diferente do que antes. Agora é 8632 . E, eu tenho esse mesmo limite, usando minha instância do SQL Server 2012 ou a instância do SQL Server 2017.

Provavelmente não é coincidência que o limite superior aqui - 32.768 - seja a capacidade máxima de SMALLINT( Int16no .NET) IF iniciando em 0(o valor máximo é 32.767, mas as matrizes na maioria das linguagens de programação são baseadas em 0).

Solomon Rutzky
fonte
0

Agora, isso é simplesmente falta de memória em outras palavras, como a operação do procedimento armazenado feito na memória e os transistores de hardware disponíveis ou a Memória de Página Virtual disponível para SQL está cheia!

Portanto, é basicamente Stack Overflow no SQL Server.

Agora, primeiro tente simplificar o processo, pois sabemos que você precisa de 1609 variáveis,

Mas você precisa de todas as variáveis ​​ao mesmo tempo?

Podemos declarar e usar variáveis ​​quando necessário.

Por exemplo:

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

Mas se tentarmos isso em um loop adicionando

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: Isso usará mais CPU e levará um pouco mais de tempo no cálculo.

Agora isso será lento, mas terá a vantagem de menos uso de memória.

Espero que isso ajude. Por favor, poste sua consulta para que possamos entender o cenário exato.

MarmiK
fonte
-4

O uso de instruções SELECT em vez de SETs pode melhorar o desempenho e a legibilidade, além de contornar o erro declarado. Então, em vez de:

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

Você pode fazer:

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

E defina todos os três valores em uma instrução.

Matthew Sontum
fonte