可以参与SET操作的最大局部变量数是多少?


11

我有一个包含业务逻辑的存储过程。在它里面,我有大约1609个变量(不要问我为什么,这就是引擎的工作原理)。我尝试将SET一个变量转换为所有其他变量的串联值。结果是在创建过程中出现错误:

消息8631,级别17,状态1,过程XXX,行YYY内部错误:已达到服务器堆栈限制。请在查询中寻找潜在的深层嵌套,并尝试简化它。

我发现该错误是由于我需要在SET操作中使用的变量数量引起的。我可以将作业一分为二来执行。

我的问题是这方面有限制吗?我检查了一下,但是没有找到。

我们检查了此KB中描述的错误,但这不是我们的情况。我们CASE在代码内不使用任何表达式。我们使用该临时变量准备必须使用CLR函数替换的值的列表。我们将SQL Server更新为SP3 CU6(最新),但是仍然遇到该错误。

Answers:


16

消息8631,级别17,状态1,行xxx
内部错误:已达到服务器堆栈限制。
请在查询中寻找潜在的深层嵌套,并尝试简化它。

由于SQL Server解析和绑定这种类型的语句的方式-作为两个输入串联的嵌套列表,长SETSELECT可变分配串联列表会发生此错误。

例如,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);

由于内部处理多个串联的方式,因此这是一个基本限制。它影响SETSELECT同样变量赋值语句。

解决方法是限制在单个语句中执行的串联数量。由于编译深度查询树是资源密集型的,因此这通常也会更有效。


5

@Paul回答启发,我进行了一些研究,发现虽然堆栈空间确实限制了连接数,并且堆栈空间是可用内存的函数,因此确实有所变化,但以下两点也是正确的:

  1. 有一种方法可以将其他串联填充为单个语句,并且
  2. 使用此方法超出初始堆栈空间限制,可以找到实际的逻辑限制(似乎没有变化)

首先,我修改了Paul的测试代码以连接字符串:

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 GB的RAM)上运行时,我可以获得的最高分数是:

  • 使用SQL Server 2017 Express Edition LocalDB(14.0.3006)的3311(返回3312个总字符)
  • 使用SQL Server 2012 Developer Edition SP4的3512(返回3513个总字符)(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实例,我都有相同的限制。

这可能不是巧合的是,这里的上限- 32,768 -是最大容量SMALLINTInt16.NET中)如果启动时0(最大值为32,767,但在许多/大多数编程语言中数组是从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

注意:这将使用更多的CPU,并花费更多的时间进行计算。

现在这将很慢,但是具有更少的内存使用量的优势。

希望对您有所帮助,请发表您的查询,以便我们了解确切的情况。


-4

使用SELECT语句而不是SET可以提高性能和可读性,并且可以使您摆脱所陈述的错误。所以代替:

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

你可以做:

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

并在一条语句中设置所有三个值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.