SET操作に参加できるローカル変数の最大数はいくつですか?


11

ビジネスロジックを含むストアドプロシージャがあります。その中には約1609の変数があります(理由は聞かないでください、これがエンジンのしくみです)。SET変数を他のすべての変数の連結値にしようとしています。結果として、作成中にエラーが発生します。

メッセージ8631、レベル17、状態1、手順XXX、行YYY内部エラー:サーバーのスタック制限に達しました。クエリで潜在的に深い入れ子を探し、それを単純化してみてください。

エラーは、SET操作で使用する必要がある変数の数が原因であることがわかりました。2つに分けて割り当てができます。

私の質問は、この領域にいくつかの制限があるのですか?チェックしましたが何も見つかりませんでした。

このKBに記載されているエラーを確認しましたが、これは私たちのケースではありません。CASEコード内では式を使用しません。その一時変数を使用して、CLR関数を使用して置き換える必要がある値のリストを準備します。SQL ServerをSP3 CU6(最新)に更新しましたが、まだエラーが発生しています。

回答:


16

メッセージ8631、レベル17、状態1、行xxx
内部エラー:サーバーのスタック制限に達しました。
クエリで潜在的に深い入れ子を探し、それを単純化してみてください。

このエラーは、SQL Serverがこのタイプのステートメントを解析およびバインドする方法(2入力連結のネストされたリストとして)が原因で、長いSETまたはSELECT変数の割り当て連結リストで発生します。

たとえば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 

最初の2つの後の各連結要素は、この表現で追加の入れ子のレベルになります。

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ます。

回避策は、1つのステートメントで実行される連結の数を制限することです。深いクエリツリーのコンパイルはリソースを大量に消費するため、これは通常より効率的です。


5

@Paul回答に触発されて、私はいくつかの調査を行い、スタックスペースは連結の数を制限することは事実であり、そのスタックスペースは利用可能なメモリの関数であり、したがって変化することがわかりましたが、次の2つの点も当てはまります。 :

  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を使用する3311(合計3312文字を返す)(14.0.3006)
  • SQL Server 2012 Developer Edition SP4(KB4018073)を使用した3512(合計3513文字を返す)(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し、次に2番目のREPLICATEに使用して、それぞれ43の変数の762グループを使用します。最初のグループは2つの変数でハードコードされています。

出力には、@S変数に32,768文字があることが示されます。初期グループを(@A+@A+@A)だけ(@A+@A)ではなくに更新すると、次のエラーが発生します。

メッセージ8632、レベル17、状態2、行XXXXX
内部エラー:式サービスの制限に達しました。クエリで潜在的に複雑な式を探し、それらを簡略化してみてください。

エラー番号が以前とは異なることに注意してください。現在は8632です。また、SQL Server 2012インスタンスとSQL Server 2017インスタンスのどちらを使用しても、同じ制限があります。

ここでの上限(32,768)が(.NETで)IF の最大容量(最大値は32,767ですが、多くの/ほとんどのプログラミング言語の配列は0ベース)であることはおそらく偶然ではありません。SMALLINTInt160


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

SETの代わりにSELECTステートメントを使用すると、パフォーマンスと読みやすさが向上し、上記のエラーを回避できる場合があります。したがって、代わりに:

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

できるよ:

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

そして、3つの値すべてを1つのステートメントで設定します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.