Какое максимальное количество локальных переменных может участвовать в операции SET?

11

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

Сообщение 8631, уровень 17, состояние 1, процедура XXX, строка YYY Внутренняя ошибка: достигнут предел стека сервера. Пожалуйста, поищите потенциально глубокое вложение в вашем запросе и постарайтесь упростить его.

Я выяснил, что ошибка связана с количеством переменных, которые мне нужно использовать в 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операторы присваивания переменных одинаково.

Обходной путь должен ограничить число конкатенаций, выполняемых в одном операторе. Это также обычно будет более эффективным, поскольку компиляция глубоких деревьев запросов требует больших ресурсов.

Пол Уайт 9
источник
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

Примечание: это потребует больше ресурсов процессора и займет немного больше времени при расчете.

Теперь это будет медленно, но имеет преимущество в меньшем использовании памяти.

Я надеюсь, что это поможет. Пожалуйста, оставьте свой запрос, чтобы мы могли понять точный сценарий.

MarmiK
источник
-4

Использование операторов SELECT вместо SET может улучшить производительность и удобочитаемость, а также может помочь избежать указанной ошибки. Так что вместо:

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

Ты можешь сделать:

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

И установить все три значения в одном утверждении.

Мэтью Сонтум
источник