テーブル値パラメーターはSQLServer2008から存在しており、複数行のデータをSQL Serverに送信するための便利なメカニズムを提供し、単一のパラメーター化された呼び出しとしてまとめられています。その後、任意の行がテーブル変数で使用可能になり、標準のT-SQLコーディングで使用できるようになります。これにより、データを再度分解するための特殊な処理ロジックを作成する必要がなくなります。まさにその定義により、テーブル値パラメーターは、呼び出しが行われているデータベース内に存在する必要があるユーザー定義のテーブルタイプに強く型付けされます。ただし、この記事で説明するように、強く型付けされたものは、期待どおりに厳密に「強く型付けされた」わけではなく、結果としてパフォーマンスに影響を与える可能性があります。
SQL Serverで誤って入力されたテーブル値パラメーターの潜在的なパフォーマンスへの影響を示すために、次の構造を持つユーザー定義のテーブルタイプの例を作成します。
CREATE TYPE dbo.PharmacyData AS TABLE ( Dosage int, Drug varchar(20), FirstName varchar(50), LastName varchar(50), AddressLine1 varchar(250), PhoneNumber varchar(50), CellNumber varchar(50), EmailAddress varchar(100), FillDate datetime );
次に、このユーザー定義のテーブルタイプをSQLServerにデータを渡すための入力パラメーターとして使用する.NETアプリケーションが必要になります。アプリケーションからテーブル値パラメーターを使用するには、通常、DataTableオブジェクトにデータを入力してから、タイプSqlDbType.Structuredのパラメーターの値として渡します。 DataTableは.NETコードで複数の方法で作成できますが、テーブルを作成する一般的な方法は次のようなものです。
System.Data.DataTable DefaultTable = new System.Data.DataTable("@PharmacyData"); DefaultTable.Columns.Add("Dosage", typeof(int)); DefaultTable.Columns.Add("Drug", typeof(string)); DefaultTable.Columns.Add("FirstName", typeof(string)); DefaultTable.Columns.Add("LastName", typeof(string)); DefaultTable.Columns.Add("AddressLine1", typeof(string)); DefaultTable.Columns.Add("PhoneNumber", typeof(string)); DefaultTable.Columns.Add("CellNumber", typeof(string)); DefaultTable.Columns.Add("EmailAddress", typeof(string)); DefaultTable.Columns.Add("Date", typeof(DateTime));
次のように、インライン定義を使用してDataTableを作成することもできます。
System.Data.DataTable DefaultTable = new System.Data.DataTable("@PharmacyData") { Columns = { {"Dosage", typeof(int)}, {"Drug", typeof(string)}, {"FirstName", typeof(string)}, {"LastName", typeof(string)}, {"AddressLine1", typeof(string)}, {"PhoneNumber", typeof(string)}, {"CellNumber", typeof(string)}, {"EmailAddress", typeof(string)}, {"Date", typeof(DateTime)}, }, Locale = CultureInfo.InvariantCulture };
.NETのDataTableオブジェクトのこれらの定義はいずれも、作成されたユーザー定義データ型のテーブル値パラメーターとして使用できますが、さまざまな文字列列のtypeof(string)定義に注意してください。これらはすべて「適切に」型指定されている可能性がありますが、実際には、ユーザー定義のデータ型に実装されているデータ型に対して強く型付けされているわけではありません。次のように、テーブルにランダムデータを入力し、渡したテーブルとまったく同じ行を返す非常に単純なSELECTステートメントのパラメーターとしてSQLServerに渡すことができます。
using (SqlCommand cmd = new SqlCommand("SELECT * FROM @tvp;", connection)) { var pList = new SqlParameter("@tvp", SqlDbType.Structured); pList.TypeName = "dbo.PharmacyData"; pList.Value = DefaultTable; cmd.Parameters.Add(pList); cmd.ExecuteReader().Dispose(); }
次に、デバッグブレークを使用して、以下に示すように、実行中にDefaultTableの定義を検査できます。
文字列列のMaxLengthが-1に設定されていることがわかります。これは、文字列がLOB(ラージオブジェクト)または基本的にMAXデータ型の列としてTDSを介してSQL Serverに渡されていることを意味し、パフォーマンスに悪影響を与える可能性があります。次のように.NETDataTable定義を変更して、ユーザー定義テーブルタイプのスキーマ定義に強く型付けし、デバッグブレークを使用して同じ列のMaxLengthを確認する場合:
System.Data.DataTable SchemaTable = new System.Data.DataTable("@PharmacyData") { Columns = { {new DataColumn() { ColumnName = "Dosage", DataType = typeof(int)} }, {new DataColumn() { ColumnName = "Drug", DataType = typeof(string), MaxLength = 20} }, {new DataColumn() { ColumnName = "FirstName", DataType = typeof(string), MaxLength = 50} }, {new DataColumn() { ColumnName = "LastName", DataType = typeof(string), MaxLength = 50} }, {new DataColumn() { ColumnName = "AddressLine1", DataType = typeof(string), MaxLength = 250} }, {new DataColumn() { ColumnName = "PhoneNumber", DataType = typeof(string), MaxLength = 50} }, {new DataColumn() { ColumnName = "CellNumber", DataType = typeof(string), MaxLength = 50} }, {new DataColumn() { ColumnName = "EmailAddress", DataType = typeof(string), MaxLength = 100} }, {new DataColumn() { ColumnName = "Date", DataType = typeof(DateTime)} }, }, Locale = CultureInfo.InvariantCulture };
これで、列定義の長さが正しくなり、TDSを介してLOBとしてSQLServerに渡されることはありません。
これはあなたが不思議に思うかもしれないパフォーマンスにどのように影響しますか?これは、ネットワークを介してSQL Serverに送信されるTDSバッファーの数に影響し、コマンドの全体的な処理時間にも影響します。
2つのデータテーブルにまったく同じデータセットを使用し、SqlConnectionオブジェクトのRetrieveStatisticsメソッドを利用すると、同じSELECTコマンドの呼び出しのExecutionTimeおよびBuffersSent統計メトリックを取得し、2つの異なるDataTable定義をパラメーターとして使用できます。 SqlConnectionオブジェクトのResetStatisticsメソッドを呼び出すと、テスト間で実行統計をクリアできます。
GetSchemaTable定義は、各文字列列のMaxLengthを正しく指定します。ここで、GetTableは、MaxLength値が-1に設定された文字列型の列を追加するだけで、テーブル内の861行のデータに対して100個の追加のTDSバッファーが送信されます。厳密に型指定されたDataTable定義で送信されるバッファが250であるのに対し、実行時間は111ミリ秒であるのに対し、158ミリ秒です。これは大規模な計画ではそれほど多くないように思われるかもしれませんが、これは1回の呼び出し、1回の実行であり、そのような実行の数千または数百万の時間の経過に伴う累積的な影響は、メリットが合計され始め、顕著な影響を及ぼします。ワークロードのパフォーマンスとスループットについて。
これが実際に違いを生む可能性があるのは、コンピューティングリソースとストレージリソース以上のものにお金を払っているクラウド実装です。 Azure VM、SQLデータベース、またはAWS EC2またはRDSのハードウェアリソースの固定費に加えて、毎月の請求に追加されるクラウドとの間のネットワークトラフィックの追加費用があります。ワイヤを通過するバッファを減らすと、時間の経過とともにソリューションのTCOが低下し、この節約を実装するために必要なコードの変更は比較的簡単です。