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

Bog*_*nov 11 sql-server t-sql database-internals sql-server-2012 errors

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

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

我发现错误是由于我需要在SET操作中使用的变量数量。我可以通过将其一分为二来执行任务。

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

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

Pau*_*ite 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 
Run Code Online (Sandbox Code Playgroud)

前两个之后的每个串联元素都会在此表示中产生额外的嵌套级别。

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);
Run Code Online (Sandbox Code Playgroud)

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

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


Sol*_*zky 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);
Run Code Online (Sandbox Code Playgroud)

通过这个测试,我在不太好的笔记本电脑(只有 6 GB 的 RAM)上运行时可以获得的最高值是:

  • 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);
Run Code Online (Sandbox Code Playgroud)

这样做我能够远远超出 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);
Run Code Online (Sandbox Code Playgroud)

最大值(对我而言)现在42用于第一个REPLICATE,因此每组使用 43 个变量,然后762用于第二个REPLICATE,因此使用 762 个组,每组 43 个变量。初始组使用两个变量进行硬编码。

现在输出显示@S变量中有 32,768 个字符。如果我将初始组更新为 is(@A+@A+@A)而不是 just (@A+@A),则会出现以下错误:

消息 8632,级别 17,状态 2,行 XXXXX
内部错误:已达到表达式服务限制。请在您的查询中寻找潜在的复杂表达式,并尝试简化它们。

请注意,错误编号与以前不同。现在是:8632。而且,无论我使用 SQL Server 2012 实例还是 SQL Server 2017 实例,我都有同样的限制。

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