web-dev-qa-db-ja.com

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

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

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

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

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

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

11
Bogdan Bogdanov

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

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

例えば、 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が使用できるスタックスペースの量によって、この入れ子の最終的な制限が決まります。制限を超えると、内部で例外が発生し、最終的に上記のエラーメッセージが表示されます。エラーがスローされたときのプロセスコールスタックの例を以下に示します。

Stack trace

再現

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変数割り当てステートメントに等しく影響します。

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

16
Paul White 9

@ Paulanswer に触発され、私はいくつかの調査を行ったところ、スタックスペースは連結の数を制限することは事実ですがスタックスペースは使用可能なメモリの関数であるため変化するため、次の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);

(私にとっての)最大値は、最初のREPLICATE42を使用し、グループごとに43個の変数を使用し、次に2番目のREPLICATE762を使用して、それぞれ43個の変数の762グループを使用します。最初のグループは2つの変数でハードコードされています。

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

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

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

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

5
Solomon Rutzky

つまり、メモリで実行されたストアドプロシージャの操作と、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を使用し、計算に少し時間がかかります。

これは遅いですが、メモリ使用量が少ないという利点があります。

これがお役に立てば幸いです。正確なシナリオを理解できるように、クエリを投稿してください。

0
MarmiK