Яка максимальна кількість локальних змінних, які можуть брати участь в операції SET?


11

У мене зберігається процедура, яка містить бізнес-логіку. Всередині мене є близько 1609 змінних (не питайте мене, чому саме так працює двигун). Я намагаюся до SETзмінної до зв'язаного значення всіх інших змінних. В результаті під час створення я отримую помилку:

Повідомлення 8631, Рівень 17, Стан 1, Процедура XXX, Рядок РРР Внутрішня помилка: Досягнуто ліміт стека сервера. Будь-ласка, знайдіть у своєму запиті потенційно глибоке гніздування та спробуйте його спростити.

Я зрозумів, що помилка пов'язана з кількістю змінних, які мені потрібно використовувати в SETоперації. Я можу виконати завдання, розділивши його на два.

Моє запитання: чи є якісь обмеження в цій галузі? Я перевірив, але жодного не знайшов.

Ми перевірили помилку, описану в цьому КБ , але це не наш випадок. Ми не використовуємо жодних CASEвиразів у нашому коді. Ми використовуємо цю тимчасову змінну для підготовки списку значень, які потрібно замінити за допомогою функції CLR. Ми оновили наш SQL Server до SP3 CU6 (остання оновлена), але ми все ще відчуваємо помилку.

Відповіді:


16

Повідомлення 8631, Рівень 17, Стан 1, Рядок xxx
Внутрішня помилка: Досягнуто ліміту стеку сервера.
Будь-ласка, знайдіть у своєму запиті потенційно глибоке гніздування та спробуйте його спростити.

Ця помилка виникає з довгими SETабо SELECTзмінними списками конкатенації присвоєнь через те, як SQL Server розбирає та зв'язує цей тип оператора - як вкладений список конкатенацій з двома входами.

Наприклад, SET @V = @W + @X + @Y + @Zприв’язується до дерева форми:

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

Кожен елемент конкатенації після перших двох приводить до додаткового рівня вкладення в цьому поданні.

Обсяг простору стеку, доступний SQL Server, визначає граничну межу цього гніздування. При перевищенні межі внутрішньо збільшується виняток, що в кінцевому підсумку призводить до повідомлення про помилку, показаного вище. Приклад стека виклику процесу при видаленні помилки показаний нижче:

Стек стека

Репро

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

Це є основним обмеженням через те, як внутрішньо обробляються кілька конкатенацій. Він впливає SETі змінює SELECTоператори присвоєння однаково.

Вирішення проблеми полягає в обмеженні кількості конкатекацій, виконаних в одному заяві. Це, як правило, буде більш ефективним, оскільки складання дерев з глибокими запитами вимагає значних ресурсів.


5

Натхненний @Paul «s відповідь , я зробив деякі дослідження і виявив , що в той час як вірно , що стек простір дійсно обмежує кількість зчеплень, і що стек простір є функцією доступною пам'яті і , таким чином , змінюється, наступні два пункти також вірно :

  1. є спосіб збити додаткові конкатенації в одне твердження, І
  2. використовуючи цей метод, щоб вийти за рамки початкового обмеження простору стека, можна знайти фактичний логічний межа (який, як видається, не змінюється)

По-перше, я адаптував тестовий код Павла для об'єднання рядків:

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

За допомогою цього тесту найвищий показник, який я міг отримати під час роботи на своєму не дуже великому ноутбуці (всього 6 Гб оперативної пам’яті), був:

  • 3311 (повертає 3312 загальних знаків) за допомогою SQL Server 2017 Express Edition LocalDB (14.0.3006)
  • 3512 (повертає 3513 загальних знаків) за допомогою SQL Server 2012 Developer Edition SP4 (KB4018073) (11.0.7001)

перш ніж отримати помилку 8631 .

Далі я спробував згрупувати конкатенації за допомогою дужок таким чином, що операція об'єднала б кілька груп конкатенацій. Наприклад:

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

При цьому мені вдалося вийти за рамки попередніх меж змінних 3312 та 3513. Оновлений код:

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

Максимальні значення (для мене) тепер мають використовувати 42для першого REPLICATE, таким чином, використовуючи 43 змінні на групу, а потім використовувати 762для другої REPLICATE, таким чином, використовуючи 762 групи по 43 змінних у кожній. Початкова група жорстко закодована з двома змінними.

Вихідні дані показують, що в @Sзмінній є 32 768 символів . Якщо я оновлюю початкову групу (@A+@A+@A)замість просто (@A+@A), я отримую таку помилку:

Повідомлення 8632, рівень 17, стан 2, рядок XXXXX
Внутрішня помилка: Досягнуто обмеження служб вираження. Будь-ласка, знайдіть у запиті потенційно складні вирази та спробуйте їх спростити.

Зауважте, що номер помилки інший, ніж раніше. Зараз це: 8632 . І у мене є такий самий ліміт, чи використовую мій екземпляр SQL Server 2012 або екземпляр SQL Server 2017.

Це, ймовірно , не випадково , що верхня межа тут - 32768 - це максимальна ємність з SMALLINT( Int16в .NET) IF , починаючи з 0(максимальне значення 32767 , а масиви в багатьох / більшості мов програмування від 0).


0

Іншими словами, це просто Без пам'яті, оскільки операція збереженої процедури, виконаної в пам'яті, та наявних апаратних транзисторів або Віртуальної пам'яті сторінки, доступних для SQL, є повною!

Тому в основному переповнення стека в SQL Server.

Тепер спершу спробуйте спростити процес, оскільки ми знаємо, що вам потрібно 1609 змінних,

Але чи потрібні вам всі змінні одночасно?

Ми можемо декларувати та використовувати змінні там, де це потрібно.

Наприклад:

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

Але якщо ми спробуємо це в циклі, додавши

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

Примітка. Це використовуватиме більше процесора та забирає трохи більше часу на обчислення.

Тепер це буде повільно, але мати перевагу менше використання пам'яті.

Сподіваюся, це допоможе. Будь ласка, опублікуйте свій запит, щоб ми могли зрозуміти точний сценарій.


-4

Використання операторів SELECT замість SET може покращити продуктивність та читабельність та може оточити вказану помилку. Тож замість:

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

Ви можете зробити:

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

І встановіть усі три значення в одному операторі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.