¿Cuál es el número máximo de variables locales que pueden participar en la operación SET?


11

Tengo un procedimiento almacenado que contiene lógica de negocios. En su interior tengo alrededor de 1609 variables (no me pregunten por qué, así es como funciona el motor). Intento con SETuna variable para el valor concatenado de todas las demás variables. Como resultado durante la creación me sale el error:

Mensaje 8631, Nivel 17, Estado 1, Procedimiento XXX, Línea YYY Error interno: se ha alcanzado el límite de la pila del servidor. Busque anidamiento potencialmente profundo en su consulta e intente simplificarlo.

Descubrí que el error se debe a la cantidad de variables que necesito usar en la SEToperación. Puedo realizar la tarea dividiéndola en dos.

Mi pregunta es ¿hay algunas restricciones en esta área? Lo comprobé, pero no encontré ninguno.

Verificamos el error descrito en este KB , pero este no es nuestro caso. No usamos ninguna CASEexpresión dentro de nuestro código. Usamos esa variable temporal para preparar una lista de valores que deben ser reemplazados usando una función CLR. Actualizamos nuestro SQL Server a SP3 CU6 (más reciente), pero aún experimentamos el error.

Respuestas:


16

Msg 8631, Nivel 17, Estado 1, Línea xxx
Error interno: se ha alcanzado el límite de pila del servidor.
Busque anidamiento potencialmente profundo en su consulta e intente simplificarlo.

Este error ocurre con listas de concatenación de asignación larga SETo SELECTvariable debido a la forma en que SQL Server analiza y enlaza este tipo de declaración, como una lista anidada de concatenaciones de dos entradas.

Por ejemplo, SET @V = @W + @X + @Y + @Zestá vinculado a un árbol de la forma:

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 concatenación después de los dos primeros resulta en un nivel adicional de anidamiento en esta representación.

La cantidad de espacio de pila disponible para SQL Server determina el límite final para este anidamiento. Cuando se excede el límite, se genera una excepción internamente, que finalmente da como resultado el mensaje de error que se muestra arriba. A continuación se muestra un ejemplo de la pila de llamadas de proceso cuando se produce el error:

Seguimiento de pila

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

Este es un límite fundamental debido a la forma en que las concatenaciones múltiples se manejan internamente. Afecta SETy SELECTlas declaraciones de asignación variable por igual.

La solución es limitar el número de concatenaciones realizadas en una sola declaración. Esto también suele ser más eficiente, ya que la compilación de árboles de consulta profunda requiere muchos recursos.


5

Inspirado por la respuesta de @Paul , investigué un poco y descubrí que si bien es cierto que el espacio de pila limita el número de concatenaciones, y que el espacio de pila es una función de la memoria disponible y, por lo tanto, varía, los siguientes dos puntos también son ciertos :

  1. hay una manera de agrupar concatenaciones adicionales en una sola declaración, Y
  2. usando este método para ir más allá de la limitación inicial de espacio de pila, se puede encontrar un límite lógico real (que no parece variar)

Primero, adapté el código de prueba de Paul para concatenar cadenas:

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 esta prueba, lo más alto que pude obtener al ejecutar en mi portátil no tan bueno (solo 6 GB de RAM) fue:

  • 3311 (devuelve 3312 caracteres totales) usando SQL Server 2017 Express Edition LocalDB (14.0.3006)
  • 3512 (devuelve 3513 caracteres en total) con SQL Server 2012 Developer Edition SP4 (KB4018073) (11.0.7001)

antes de obtener el error 8631 .

Luego, intenté agrupar las concatenaciones usando paréntesis de modo que la operación concatenara múltiples grupos de concatenaciones. Por ejemplo:

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

Al hacerlo, pude ir mucho más allá de los límites anteriores de las variables 3312 y 3513. El código actualizado es:

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

Los valores máximos (para mí) ahora son usar 42para el primero REPLICATE, usando 43 variables por grupo, y luego usar 762para el segundo REPLICATE, usando 762 grupos de 43 variables cada uno. El grupo inicial está codificado con dos variables.

El resultado ahora muestra que hay 32.768 caracteres en la @Svariable. Si actualizo el grupo inicial para que sea en (@A+@A+@A)lugar de solo (@A+@A), entonces aparece el siguiente error:

Mensaje 8632, Nivel 17, Estado 2, Línea XXXXX
Error interno: se ha alcanzado un límite de servicios de expresión. Busque expresiones potencialmente complejas en su consulta e intente simplificarlas.

Tenga en cuenta que el número de error es diferente al anterior. Ahora es: 8632 . Y, tengo este mismo límite si uso mi instancia de SQL Server 2012 o la instancia de SQL Server 2017.

Probablemente no sea coincidencia que el límite superior aquí, 32.768 , sea la capacidad máxima de SMALLINT( Int16en .NET) IF a partir de 0(el valor máximo es 32.767, pero las matrices en muchos / la mayoría de los lenguajes de programación están basadas en 0).


0

Ahora, esto es simplemente sin memoria, en otras palabras, ¡ya que la operación del procedimiento almacenado realizada en la memoria y los transistores de hardware disponibles o la memoria de página virtual disponible para SQL está llena!

Entonces, básicamente es Stack Overflow en SQL Server.

Ahora, primero intente simplificar el proceso, ya que sabemos que necesita 1609 Variables,

¿Pero necesita todas las variables al mismo tiempo?

Podemos declarar y usar variables donde sea necesario.

Por ejemplo:

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

Pero si intentamos esto en un bucle agregando

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: Esto usará más CPU y tomará un poco más de tiempo en el cálculo.

Ahora esto será lento, pero tiene la ventaja de un menor uso de memoria.

Espero que esto ayude. Publique su consulta para que podamos entender el escenario exacto.


-4

El uso de instrucciones SELECT en lugar de SET puede mejorar el rendimiento y la legibilidad, y puede evitar el error declarado. Entonces en lugar de:

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

Tu puedes hacer:

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

Y establezca los tres valores en una declaración.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.