完了する前にストアドプロシージャから応答を取得する方法


8

終了する前に、ストアドプロシージャから部分的な結果(単純な選択として)を返す必要があります。

それは可能ですか?

はいの場合、それを行う方法は?

そうでない場合、回避策はありますか?

編集:私は手順のいくつかの部分を持っています。最初の部分では、いくつかの文字列を計算します。追加の操作を行うために、手順の後半でそれらを使用します。問題は、呼び出し元が文字列をできるだけ早く必要とすることです。そのため、その文字列を計算してそれを(どういうわけか、たとえばselectから)返し、それから作業を続ける必要があります。発信者は、貴重な文字列をはるかに迅速に取得します。

呼び出し元はWebサービスです。


完全なテーブルロックが発生していないか、明示的なトランザクションが宣言されていないと想定すると、問題なく別のセッションでSELECTを実行できるはずです。
Steve Mangiameli

一般的に、これは私が今見ている唯一の方法ですが、それほど速くなるとは思いません(他の問題もあります)、@ SteveMangiameli
Bogdan Bogdanov

それを2つのSPに分割しますか?最初から2番目の出力を渡します。
パパラッツォ2016年

それほど迅速な解決策ではない、それが私たちがそれを取りやめた理由です、@ Paparazzi
Bogdan Bogdanov

回答:


11

あなたはおそらくオプション付きのRAISERRORコマンドを探していNOWAITます。

発言ごと:

RAISERRORは、PRINTの代わりに使用して、呼び出し元のアプリケーションにメッセージを返すことができます。

これはSELECTステートメントからの結果を返しませんが、メッセージ/文字列をクライアントに返すことができます。選択しているデータのクイックサブセットを返す場合は、FASTクエリヒントを検討することをお勧めします。

クエリが最初のnumber_rowsの高速取得のために最適化されることを指定します。これは負でない整数です。最初のnumber_rowsが返された後、クエリは実行を継続し、完全な結果セットを生成します。

Shannon Severanceによりコメントに追加:

Erland SommarskogによるSQL Serverのエラーおよびトランザクション処理から:

ただし、一部のAPIとツールはそれらの側でバッファリングし、それによっての影響を無効にする可能性があることに注意してくださいWITH NOWAIT

完全なコンテキストについては、ソースの記事を参照してください。


FAST競合状態を悪化させて再現するために、ストアドプロシージャとC#コードの実行を同期する必要があった問題で、この問題を解決しました。結果セットをプログラムで使用する方が、などを使用するよりも簡単RAISERROR()です。私があなたの答えを読み始めたとき、それはで行うことはできないと言っていたように思われたSELECTので、多分それは明確化できましたか?
binki

5

更新: strutzkyの回答(上記)とコメントを参照して、これが期待どおりに動作せず、ここで説明する少なくとも1つの例を確認してください。時間が許せば、理解を深めるために、さらに実験/読む必要があります...

呼び出し元がデータベースと非同期に対話するか、スレッド/マルチプロセスである場合、最初のセッションがまだ実行中に2番目のセッションを開くことができるので、部分的なデータを保持するテーブルを作成し、手順の進行に応じてそれを更新できます。これは、コミットされていない変更を読み取ることができるようにトランザクション分離レベル1が設定された2番目のセッションで読み取ることができます。

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table

1:srutzkyの回答のコメントとその後の更新に従って、監視対象のプロセスがトランザクションにラップされていない場合、分離レベルを設定する必要はありませんが、原因とならないような状況では習慣から外す傾向がありますこれらのケースで不要な場合に害を及ぼす

もちろん、複数のプロセスがこの方法で動作している可能性がある場合(Webサーバーが同時ユーザーを受け入れ、それが当てはまらないことが非常にまれである場合)は、何らかの方法でこのプロセスの進行情報を特定する必要があります。 。おそらく、新しく作成したUUIDをキーとしてプロシージャに渡し、それを進行状況テーブルに追加して、次のように読み取ります。

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table WHERE process = <current_process_uuid>

この方法を使用して、SSMSで長時間実行される手動プロセスを監視しました。私はそれが生産でそれを使用することを検討するのに私があまりに「においがする」かどうか決めることができません...


1
これは1つのオプションですが、現時点では気に入らない。私は他のいくつかのオプションがポップアップすることを願っています。
ボグダンボグダノフ2016年

5

OPはすでに複数の結果セット(MARSではない)の送信を試みており、ストアドプロシージャが完了するのを実際に待ってから、結果セットを返します。その状況を念頭に置いて、いくつかのオプションがあります:

  1. データが128バイトに収まるほど小さい場合は、を使用SET CONTEXT_INFOしてその値を表示できるようにすることができますSELECT [context_info] FROM [sys].[dm_exec_requests] WHERE [session_id] = @SessionID;。ストアドプロシージャを実行してSELECT @@SPID;を介してそれを取得する前に、簡単なクエリを実行する必要があるだけですSqlCommand.ExecuteScalar

    私はこれをテストしましたが、うまくいきます。

  2. @Davidの「進行状況」テーブルにデータを入れるという提案に似ていますが、クリーンアップや同時実行性、プロセス分離の問題を混乱させる必要はありません。

    1. Guidアプリのコード内で新規を作成し、それをパラメーターとしてストアード・プロシージャーに渡します。このGUIDは数回使用されるため、変数に格納します。
    2. ストアドプロシージャで、テーブル名の一部としてそのGUIDを使用して、グローバル一時テーブルを作成しますCREATE TABLE ##MyProcess_{GuidFromApp};。テーブルには、必要なデータ型の列を含めることができます。
    3. データがある場合は、そのグローバル一時テーブルに挿入します。

    4. アプリのコードでは、データを読み出そうと始めるが、ラップSELECTIF EXISTSテーブルがまだ作成されていない場合、それは失敗しないように。

      IF (OBJECT_ID('tempdb..[##MyProcess_{0}]')
          IS NOT NULL)
      BEGIN
        SELECT * FROM [##MyProcess_{0}];
      END;

    を使用すると、Guid変数の値にString.Format()置き換えることができ{0}ます。ifをテストしReader.HasRows、trueの場合は結果を読み取り、それ以外のThread.Sleep()場合は何でも呼び出して再度ポーリングします。

    利点:

    • アプリのコードだけが特定のGuid値を知っているため、このテーブルは他のプロセスから分離されているため、他のプロセスについて心配する必要はありません。別のプロセスには、独自のプライベートグローバル一時テーブルがあります。
    • テーブルなので、すべてが強く型付けされています。
    • これは一時テーブルなので、ストアドプロシージャを実行するセッションが終了すると、テーブルは自動的にクリーンアップされます。
    • これはグローバル一時テーブル ので、
      • 永続テーブルのように、他のセッションからアクセスできます
      • それが作成されたサブプロセス(つまりEXEC/ sp_executesql呼び出し)の終了後も存続します。


    私はこれをテストしましたが、期待どおりに動作します。次のコード例を使用して、自分で試してみることができます。

    1つのクエリタブで次を実行し、ブロックコメントの3行を強調表示して実行します。

    CREATE
    --ALTER
    PROCEDURE #GetSomeInfoBackQuickly
    (
      @MessageTableName NVARCHAR(50) -- might not always be a GUID
    )
    AS
    SET NOCOUNT ON;
    
    DECLARE @SQL NVARCHAR(MAX) = N'CREATE TABLE [##MyProcess_' + @MessageTableName
                 + N'] (Message1 NVARCHAR(50), Message2 NVARCHAR(50), SomeNumber INT);';
    
    -- Do some calculations
    
    EXEC (@SQL);
    
    SET @SQL = N'INSERT INTO [##MyProcess_' + @MessageTableName
    + N'] (Message1, Message2, SomeNumber) VALUES (@Msg1, @Msg2, @SomeNum);';
    
    DECLARE @SomeNumber INT = CRYPT_GEN_RANDOM(2);
    
    EXEC sp_executesql
        @SQL,
        N'@Msg1 NVARCHAR(50), @Msg2 NVARCHAR(50), @SomeNum INT',
        @Msg1 = N'wow',
        @Msg2 = N'yadda yadda yadda',
        @SomeNum = @SomeNumber;
    
    WAITFOR DELAY '00:00:10.000';
    
    SET @SomeNumber = CRYPT_GEN_RANDOM(3);
    EXEC sp_executesql
        @SQL,
        N'@Msg1 NVARCHAR(50), @Msg2 NVARCHAR(50), @SomeNum INT',
        @Msg1 = N'wow',
        @Msg2 = N'yadda yadda yadda',
        @SomeNum = @SomeNumber;
    
    WAITFOR DELAY '00:00:10.000';
    GO
    /*
    DECLARE @TempTableID NVARCHAR(50) = NEWID();
    RAISERROR('%s', 10, 1, @TempTableID) WITH NOWAIT;
    
    EXEC #GetSomeInfoBackQuickly @TempTableID;
    */

    [メッセージ]タブに移動し、印刷されたGUIDをコピーします。次に、別のクエリタブを開いて次を実行し、他のセッションの[メッセージ]タブからコピーしたGUIDを1行目の変数の初期化に配置します。

    DECLARE @TempTableID NVARCHAR(50) = N'GUID-from-other-session';
    
    EXEC (N'SELECT * FROM [##MyProcess_' + @TempTableID + N']');

    叩き続けるF5。最初の10秒間は1つのエントリが表示され、次の10秒間は2つのエントリが表示されます。

  3. SQLCLRを使用して、Webサービスまたはその他の方法でアプリにコールバックできます。

  4. あなたは可能性が多分使用PRINT/ RAISERROR(..., 1, 10) WITH NOWAITバックすぐに文字列を渡すが、これは、次の問題に少しトリッキーによる次のようになります。

    • 「メッセージ」の出力は、VARCHAR(8000)またはに制限されていますNVARCHAR(4000)
    • メッセージは結果と同じ方法では送信されません。それらをキャプチャするには、イベントハンドラを設定する必要があります。その場合は、静的コレクションとして変数を作成して、コードのすべての部分で使用できるメッセージを取得できます。または多分他の方法。メッセージをキャプチャする方法を示す他の回答の例が1つまたは2つあり、後でメッセージを見つけたときにリンクします。
    • デフォルトでは、メッセージはプロセスが完了するまで送信されません。ただし、この動作は、SqlConnection.FireInfoMessageEventOnUserErrorsプロパティをに設定することで変更できますtrue。ドキュメントは述べています:

      FireInfoMessageEventOnUserErrorsをtrueに設定すると、以前は例外として扱われていたエラーがInfoMessageイベントとして処理されるようになりました。すべてのイベントはすぐに発生し、イベントハンドラーによって処理されます。FireInfoMessageEventOnUserErrorsがに設定されているfalse場合、InfoMessageイベントは手順の最後に処理されます。

      ここでの欠点は、ほとんどのSQLエラーでが発生しなくなることSqlExceptionです。この場合、メッセージイベントハンドラーに渡される追加のイベントプロパティをテストする必要があります。これは接続全体に当てはまるため、状況は少し複雑になりますが、管理不可能ではありません。

    • すべてのメッセージは同じレベルで表示され、個別に区別するための個別のフィールドやプロパティはありません。それらが受信される順序は、それらが送信される方法と同じである必要がありますが、それが十分に信頼できるかどうかはわかりません。あとで解析できるタグや何かを含める必要があるかもしれません。そうすれば、少なくともどれがどれであるかを確信することができます。


2
やってみます。文字列を計算した後、それを単純な選択として返し、手順を続行します。問題は、すべてのセットを同時に返すということです(RETURNステートメントの後と思います)。そのため、機能していません。
ボグダンボグダノフ2016年

2
@BogdanBogdanov .NETを使用していますSqlConnectionか?どのくらいの量のデータを戻したいですか?どんなデータ型?PRINTまたはどちらかを試しましたかRAISERROR WITH NOWAIT
ソロモンルツキー2016年

今からやってみます。.NET Webサービスを使用します。
ボグダンボグダノフ2016年

「これはグローバル一時テーブルなので、トランザクション分離レベルについて心配する必要はありません。」それは本当に正しいですか?IIRC一時テーブルは、グローバルテーブルであっても、他のテーブルと同じACID制限を受ける必要があります。どのように動作をテストしたか詳細を教えてください。
David Spillett 2016年

@DavidSpillett私が考えたところで、分離レベルは実際には問題ではなく、あなたの提案に関しても同じことが言えます。テーブルがトランザクション内で作成されない限り。私は答えをサンプルコードで更新しました。
ソロモンRutzky 2016年

0

ストアドプロシージャをバックグラウンド(非同期)で実行する必要がある場合は、Service Brokerを使用する必要があります。セットアップは少し面倒ですが、一度完了すると、ストアドプロシージャ(非ブロッキング)を開始して、進行状況メッセージを必要なだけ(または少しだけ)聞くことができます。

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