Quel est le nombre maximum de variables locales pouvant participer à l'opération SET?

11

J'ai une procédure stockée qui contient la logique métier. À l'intérieur, j'ai environ 1609 variables (ne me demandez pas pourquoi, c'est ainsi que fonctionne le moteur). J'essaie SETune variable à la valeur concaténée de toutes les autres variables. Par conséquent, lors de la création, j'obtiens l'erreur:

Msg 8631, niveau 17, état 1, procédure XXX, ligne YYY Erreur interne: la limite de pile du serveur a été atteinte. Veuillez rechercher une imbrication potentiellement profonde dans votre requête et essayez de la simplifier.

J'ai compris que l'erreur est due au nombre de variables que je dois utiliser dans l' SETopération. Je peux effectuer la tâche en la divisant en deux.

Ma question est la suivante: y a-t-il des restrictions dans ce domaine? J'ai vérifié, mais je n'en ai pas trouvé.

Nous avons vérifié l'erreur décrite dans ce KB , mais ce n'est pas notre cas. Nous n'utilisons aucune CASEexpression dans notre code. Nous utilisons cette variable temporaire pour préparer une liste de valeurs qui doivent être remplacées à l'aide d'une fonction CLR. Nous avons mis à jour notre serveur SQL vers SP3 CU6 (le plus récent), mais nous rencontrons toujours l'erreur.

Bogdan Bogdanov
la source

Réponses:

16

Msg 8631, niveau 17, état 1, ligne xxx
Erreur interne: la limite de pile du serveur a été atteinte.
Veuillez rechercher une imbrication potentiellement profonde dans votre requête et essayez de la simplifier.

Cette erreur se produit avec des listes de concaténation d'affectation longues SETou SELECTvariables en raison de la façon dont SQL Server analyse et lie ce type d'instruction - en tant que liste imbriquée de concaténations à deux entrées.

Par exemple, SET @V = @W + @X + @Y + @Zest lié dans un arbre de la forme:

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

Chaque élément de concaténation après les deux premiers entraîne un niveau supplémentaire d'imbrication dans cette représentation.

La quantité d'espace de pile disponible pour SQL Server détermine la limite ultime de cette imbrication. Lorsque la limite est dépassée, une exception est levée en interne, ce qui entraîne finalement le message d'erreur illustré ci-dessus. Un exemple de pile d'appels de processus lorsque l'erreur est levée est illustré ci-dessous:

Trace de la pile

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

Il s'agit d'une limite fondamentale en raison de la façon dont plusieurs concaténations sont traitées en interne. Il affecte également SETet SELECTles instructions d'affectation variable.

La solution consiste à limiter le nombre de concaténations effectuées dans une seule instruction. Cela sera également généralement plus efficace, car la compilation d'arbres de requête profonds nécessite beaucoup de ressources.

Paul White 9
la source
5

Inspiré par @ Paul de réponse , je l' ai fait quelques recherches et a constaté que si il est vrai que l' espace de pile limite le nombre de concaténations, et que l' espace de pile est fonction de la mémoire disponible et varie donc, les deux points suivants sont également vrai :

  1. il existe un moyen de regrouper des concaténations supplémentaires en une seule instruction, ET
  2. en utilisant cette méthode pour aller au-delà de la limitation d'espace de pile initiale, une limite logique réelle (qui ne semble pas varier) peut être trouvée

Tout d'abord, j'ai adapté le code de test de Paul pour concaténer des chaînes:

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

Avec ce test, le plus haut niveau que j'ai pu obtenir lors de l'exécution sur mon ordinateur portable pas si génial (seulement 6 Go de RAM) était:

  • 3311 (renvoie 3312 caractères au total) à l'aide de SQL Server 2017 Express Edition LocalDB (14.0.3006)
  • 3512 (renvoie 3513 caractères au total) à l'aide de SQL Server 2012 Developer Edition SP4 (KB4018073) (11.0.7001)

avant d'obtenir l'erreur 8631 .

Ensuite, j'ai essayé de regrouper les concaténations en utilisant des parenthèses de sorte que l'opération concatène plusieurs groupes de concaténations. Par exemple:

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

Ce faisant, j'ai pu aller bien au-delà des limites précédentes de 3312 et 3513 variables. Le code mis à jour est:

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

Les valeurs maximales (pour moi) sont maintenant à utiliser 42pour la première REPLICATE, utilisant ainsi 43 variables par groupe, puis à utiliser 762pour la seconde REPLICATE, utilisant ainsi 762 groupes de 43 variables chacun. Le groupe initial est codé en dur avec deux variables.

La sortie montre maintenant qu'il y a 32 768 caractères dans la @Svariable. Si je mets à jour le groupe initial au (@A+@A+@A)lieu de simplement (@A+@A), j'obtiens l'erreur suivante:

Msg 8632, niveau 17, état 2, ligne XXXXX
Erreur interne: une limite de services d'expression a été atteinte. Veuillez rechercher des expressions potentiellement complexes dans votre requête et essayez de les simplifier.

Notez que le numéro d'erreur est différent qu'auparavant. Il est maintenant: 8632 . ET, j'ai cette même limite si j'utilise mon instance SQL Server 2012 ou l'instance SQL Server 2017.

Ce n'est probablement pas une coïncidence si la limite supérieure ici - 32 768 - est la capacité maximale de SMALLINT( Int16dans .NET) IF à partir de 0(la valeur maximale est 32 767 mais les tableaux dans de nombreux / la plupart des langages de programmation sont basés sur 0).

Solomon Rutzky
la source
0

En d'autres termes, il s'agit simplement de mémoire insuffisante, car le fonctionnement de la procédure stockée effectuée en mémoire et les transistors matériels disponibles ou la mémoire de page virtuelle disponible pour SQL est plein!

Donc, c'est essentiellement Stack Overflow dans SQL Server.

Maintenant, essayez d'abord de simplifier le processus, car nous savons que vous avez besoin de 1609 variables,

Mais avez-vous besoin de toutes les variables en même temps?

Nous pouvons déclarer et utiliser des variables si nécessaire.

Par exemple:

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

Mais si nous essayons cela en boucle en ajoutant

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

Remarque: cela utilisera plus de CPU et prendra un peu plus de temps dans le calcul.

Maintenant, ce sera lent, mais vous aurez moins d’utilisation de la mémoire.

J'espère que cela vous aide, veuillez poster votre requête afin que nous puissions comprendre le scénario exact.

MarmiK
la source
-4

L'utilisation d'instructions SELECT au lieu de SET peut améliorer les performances et la lisibilité, et peut vous aider à contourner l'erreur indiquée. Donc au lieu de:

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

Tu peux faire:

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

Et définissez les trois valeurs dans une seule instruction.

Matthew Sontum
la source