web-dev-qa-db-ja.com

TVPを読み取り専用にする必要がある理由、および他のタイプのパラメーターを読み取り専用にできない理由

このブログ によると、関数またはストアドプロシージャのパラメーターは、OUTPUTパラメーターでない場合、本質的に値渡しであり、本質的に、より安全なバージョンの受け渡しとして扱われます。 OUTPUTパラメータの場合は-reference。

最初に、TVPをREADONLYと宣言することの目的は、TVPをOUTPUTパラメータとして使用できないことを開発者に明確に知らせることであると考えましたが、非TVPをREADONLYとして宣言することはできません。たとえば、次は失敗します。

create procedure [dbo].[test]
@a int readonly
as
    select @a

メッセージ346、レベル15、状態1、手順テスト
パラメーター "@a"はテーブル値パラメーターではないため、READONLYとして宣言できません。

  1. TVPで 統計情報は保存されません なので、DML操作を防止する根拠は何ですか?
  2. 何らかの理由でTVPをOUTPUTパラメータにしたくないことに関連していますか?
19
Erik

説明は次の組み合わせに関連しているようです:a)この質問で言及されていないリンクされたブログの詳細、b)パラメータが常に受け渡される方法に適合しているTVPの実用、c)と性質テーブル変数の。

  1. リンクされたブログの投稿に含まれている詳細の欠落は、ストアドプロシージャと関数で変数がどのように渡され、どのように渡されるかです(「パラメーターがOUTPUTパラメーターである場合、参照の受け渡しのより安全なバージョン」の質問の句に関連しています)。 :

    TSQLは、コピーイン/コピーアウトセマンティクスを使用して、ストアドプロシージャと関数にパラメーターを渡します。

    ...ストアドプロシージャの実行が(エラーにヒットすることなく)終了すると、コピーアウトが行われ、渡されたパラメータがストアドプロシージャで加えられた変更で更新されます。

    このアプローチの真の利点は、エラーの場合です。ストアドプロシージャの実行中にエラーが発生した場合、パラメーターに加えられた変更は呼び出し元に反映されません。

    OUTPUTキーワードが存在しない場合、コピーアウトは行われません。

    一番下の行:
    ストアドプロシージャへのパラメータは、エラーが発生した場合、ストアドプロシージャの部分的な実行を反映することはありません。

    このパズルのパート1は、パラメーターが常に「値によって」渡されることです。そして、現在の値が実際に送り返されるのは、パラメーターがOUTPUTおよびとしてマークされている場合のみです。 OUTPUT値が「参照によって」本当に渡された場合、その変数のメモリ内の場所へのポインターは、渡されたものであり、値自体ではありません。また、ポインタ(メモリアドレスなど)を渡した場合、ストアドプロシージャの次の行でエラーが発生して実行が中止された場合でも、行われた変更はすぐに反映されます。

    パート1を要約すると、変数値は常にコピーされます。それらはメモリアドレスによって参照されません。

  2. パート1を念頭に置いて、渡される変数が非常に大きい場合、常に変数値をコピーするポリシーはリソースの問題につながる可能性があります。 blob型(VARCHAR(MAX)NVARCHAR(MAX)VARBINARY(MAX)XML、および使用すべきでない型がどのように処理されるかを確認するためのテストは行っていませんもはや:TEXTNTEXT、およびIMAGE)ですが、渡されるデータのテーブルが非常に大きくなる可能性があると言っても安全です。 TVP機能を開発している人にとって、クールな新機能が健全な数のシステムを破壊することを防ぐための真の「参照渡し」機能を望むことは理にかなっています(つまり、よりスケーラブルなアプローチが必要です)。 ドキュメント でわかるように、これは彼らが行ったことです。

    Transact-SQLは、テーブル値パラメーターを参照によってルーチンに渡し、入力データのコピーを作成しないようにします。

    また、このメモリ管理の問題は、SQL Server 2005で導入されたSQLCLR API(TVPはSQL Server 2008で導入された)にあるため、新しい概念ではありませんでした。 NVARCHARおよびVARBINARYデータをSQLCLRコード(つまり、SQLCLRアセンブリ内の.NETメソッドの入力パラメーター)に渡す場合、次のいずれかを使用して「値による」アプローチを選択できます。それぞれSqlStringまたはSqlBinary、またはSqlCharsまたはSqlBytesをそれぞれ使用して「参照」アプローチを使用できます。 SqlCharsおよびSqlBytesタイプを使用すると、データを.NET CLRに完全にストリーミングできるため、200 MB全体(最大2 GB)をコピーするのではなく、大きな値の小さなチャンクをプルできます。 、右)値。

    パート2を要約すると、TVPはその性質上、「常に値をコピーする」モデル内にとどまると、大量のメモリを消費する(したがって、パフォーマンスを低下させる)傾向があります。したがって、TVPは真の「参照渡し」を行います。

  3. 最後の部分は、第2部が重要である理由です。TVPをコピーして変更するのではなく、本当に「参照によって」渡すだけの理由です。そして、それはパート1の基礎である設計目標によって答えられます。正常に完了しないストアドプロシージャは、OUTPUTとしてマークされているかどうかにかかわらず、どのような方法でも入力パラメーターを変更するべきではありません。 。 DML操作を許可すると、TVPの値が呼び出しコンテキストに存在するため、すぐに影響があります(参照渡しは、渡されたもののコピーではなく、渡されたものを変更することになるため)。

    さて、この時点で誰かがおそらくモニターに話しかけているでしょう。「まあ、オートマジック機能を組み込んで、TVPパラメータに加えられた変更をストアドプロシージャに渡した場合は、ロールバックします。問題は解決しました。」そんなに早くない。これがテーブル変数の性質の出番です。テーブル変数に加えられた変更はトランザクションに拘束されません!したがって、変更をロールバックする方法はありません。そして実際、これはロールバックが必要な場合にトランザクション内で生成された情報を保存するために使用されるトリックです:-)。

    パート3をまとめると、ストアドプロシージャが異常終了する原因となるエラーが発生した場合に、テーブル変数で行われた変更を「元に戻す」ことはできません。そしてこれは、部分的な実行を反映しないパラメーターを持つという設計目標に違反します(パート1)。

Ergo:READONLYキーワードは、TVPのDML操作を防ぐために必要です。これらのキーワードは、実際に「参照」によって渡されるテーブル変数であるため、したがって、ストアドプロシージャでエラーが発生した場合でも、変更をすぐに反映し、それを防ぐ方法は他にありません。

さらに、他のデータ型のパラメーターは、渡されたもののコピーであるため、READONLYを使用できません。そのため、まだ保護されていないものは保護されません。それと、他のデータ型のパラメーターが機能する方法は読み取りと書き込みを意図したものでした。そのため、読み取り専用の概念を含むようにAPIを変更することは、おそらくさらに多くの作業になるでしょう。

19
Solomon Rutzky

Martin Smithによる質問へのコメントから生成されたコミュニティWiki回答

このためのアクティブな接続項目(Erland Sommarskogによって送信された)があります。

SPが相互に呼び出すときにテーブルパラメータを読み取り専用にする必要があるという制限

これまでのところマイクロソフトによる唯一の反応は次のとおりです(強調を追加):

これについてのフィードバックをありがとう。多くのお客様から同様のフィードバックをいただいております。 テーブル値パラメーターの読み取り/書き込みを許可するには、SQLエンジン側とクライアントプロトコルでかなりの作業が必要になります。時間/リソースの制約のためその他の優先事項と同様に、SQL Server 2008リリースの一部としてこの作業を行うことはできません。ただし、この問題を調査し、次のリリースのSQL Serverの一部として対処するためにレーダーでこれをしっかりと持っています。ここでフィードバックを歓迎し、歓迎します。

スリーニ・アチャリヤ
上級プログラムマネージャー
SQL Server Relational Engine

5
Paul White 9