SQLパフォーマンス・チューニングの.NET実装:SQL Serverのバッチに対してSQL文を実行

この記事では、SqlCommandオブジェクトは、複数のSQL文を実行するいくつかの手法を使用する方法について説明します。

入門

データストレージにSQL ServerへのADO.NETがしばしば見過ごされている1つの用途は、単一のSqlCommand文を使用して複数のSQL文を実行する能力です。典型的には、プログラムステートメントが実行及び/又はより大きなステートメントを実行するストアドプロシージャを呼び出しています。もちろん、ストアドプロシージャの使用が好ましい方法であるが、いくつかのケースでは、複数のステートメントを実行する単一の呼び出しが有益です。これは基本的に一緒にSQLまたはT-SQL文の集合を意味するバッチを使用して達成することができます。

セットアップ

機能をテストするために、私たちは、データベーステーブルを持っています。


  • テストテーブルを作成します。

  • CREATE TABLE MultiStatementTest (
    id        int not null identity(1,1),
    somevalue int not null
    );

    そして、数行でそれを埋めます。


  • いくつかの行を追加します。

  • DECLARE @counter int = 1
    BEGIN
    WHILE (@counter <= 5) BEGIN
      INSERT INTO MultiStatementTest (somevalue) VALUES (RAND() * 1000);
      SET @counter = @counter + 1;
    END;
    END;

    今データは次のようになります。


  • 初期クエリデータ

  • SELECT * FROM MultiStatementTest;
    IDのsomeValueの


    1 854
    2 73
    3 732
    4 546
    5 267

    テスト手順

    テストプログラムを使用するのは簡単です。ただ、データベーステーブル定義されたテスト正しい接続文字列を作成するために、あなたは、テストの実行を開始することができます。

複数のSQL文を実行します

テストテーブルに二つの別々のSQL文を実行するための第一の変形SqlCommand.ExecuteNonQuery。最初のフィールドは、someValueの第2の更新フィールドを更新します。方法は以下の通りです:

/// <summary>
/// Executes two separate updates against the the connection
/// </summary>
/// <param name="connectionString">Connection string to use</param>
/// <param name="generateError">Should the statement generate an error</param>
/// <returns>True if succesful</returns>
public static bool ExecuteMultipleUpdates(string connectionString, bool generateError = false) {
 System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection();
 System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();
 int rowsAffected;

 connection.ConnectionString = connectionString;
 command.CommandText = @"
     UPDATE MultiStatementTest SET somevalue = somevalue + 1;
     UPDATE MultiStatementTest SET" + (generateError ? "WONTWORK" : "") + 
                   " somevalue = somevalue + 2;";
 command.CommandType = System.Data.CommandType.Text;
 command.Connection = connection;

 try {
    connection.Open();
    rowsAffected = command.ExecuteNonQuery();
 } catch (System.Exception exception) {
    System.Windows.MessageBox.Show(exception.Message, "Error occurred");
    return false;
 } finally {
    command.Dispose();
    connection.Dispose();
 }
 System.Windows.MessageBox.Show(string.Format("{0} rows updated", 
    rowsAffected, "Operation succesful"));

 return true;
}

このように、CommandTextプロパティは、このバッチで実行されるすべての文が含まれています。セミコロンで区切られたステートメント。

バッチを実行した後、行が2回更新されましたので、このようなテーブルのルックスの内容:

IDのsomeValueの

1857年
2 76
3735
4549
5270
注意することが重要なことは、その行の影響を受けた数は、ExecuteNonQuery 10のリターン。表は、各列が2回更新され、5つの行を有しているので、合計数は10の更新です。したがって、さえも声明が更新されたかどうかを確認することができますバッチ、場合、更新は行数を訂正します。

使用データリーダーは、2つのSELECT文を実行します

次のテストは、読み取り結果のSqlDataReaderのクラスを使用して、異なる2つのSELECT文を実行することです。方法は次のとおりです。

/// <summary>
/// Executes two separate select statements against the the connection using data reader
/// </summary>
/// <param name="connectionString">Connection string to use</param>
/// <param name="generateError">Should the statement generate an error</param>
/// <returns>True if succesful</returns>
public static bool ExecuteReader(string connectionString, bool generateError = false) {
 System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection();
 System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();
 System.Data.SqlClient.SqlDataReader dataReader;
 System.Text.StringBuilder stringBuilder;
 bool loopResult = true;

 connection.ConnectionString = connectionString;
 command = new System.Data.SqlClient.SqlCommand();
 command.CommandText = @"
    SELECT somevalue FROM MultiStatementTest WHERE somevalue%2 = 1;
    SELECT somevalue FROM MultiStatementTest " + (generateError ? "WONTWORK" : "WHERE") + 
               " somevalue%2 = 0;";
 command.CommandType = System.Data.CommandType.Text;
 command.Connection = connection;

 try {
    connection.Open();
    dataReader = command.ExecuteReader();
    while (loopResult) {
       stringBuilder = new System.Text.StringBuilder();
       while (dataReader.Read()) {
          stringBuilder.AppendLine(dataReader.GetInt32(0).ToString());
       }
       System.Windows.MessageBox.Show(stringBuilder.ToString(), "Data from the result set");
       loopResult = dataReader.NextResult();
    }
 } catch (System.Exception exception) {
    System.Windows.MessageBox.Show(exception.Message, "Error occurred");
    return false;
 } finally {
    command.Dispose();
    connection.Dispose();
 }

 return true;
}

アイデアは、二つの文の間にセミコロンで区切って、同じバッチです。この例では、結果セットは、奇数または偶数の数に応じて、二列に分割されます。ExecuteReaderのが呼び出されると、最初の結果セットは、自動的に利用可能です。方法および行を結果を表示します:

857
735
549
次の結果を得るためには、リーダーは次の結果セットにNextResultメソッド進むを使用するように指示されなければなりません。その後、値の第2設定することができるサイクルを経て再び。結果の第二セット:

76
270

複数のSELECT文のためのSqlDataAdapter

あなたはSqlDataReaderの中で結果を保存したい場合は、通常はDataSetを使用しています。次のテストのために、のは、DataSetを埋めるためにSqlDataAdapterオブジェクトを使用してみましょう。コードは以下の通りであります:

/// <summary>
/// Executes two separate select statements against the the connection
/// </summary>
/// <param name="connectionString">Connection string to use</param>
/// <param name="generateError">Should the statement generate an error</param>
/// <returns>True if succesful</returns>
public static bool ExecuteMultipleSelects(string connectionString, bool generateError = false) {
 System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection();
 System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();
 System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter();
 System.Data.DataSet dataset = new System.Data.DataSet();

 connection.ConnectionString = connectionString;
 command = new System.Data.SqlClient.SqlCommand();
 command.CommandText = @"
     SELECT * FROM MultiStatementTest WHERE somevalue%2 = 1;
     SELECT " + (generateError ? "WONTWORK" : "*") + 
       " FROM MultiStatementTest WHERE somevalue%2 = 0;";
 command.CommandType = System.Data.CommandType.Text;
 command.Connection = connection;

 try {
    connection.Open();
    adapter.SelectCommand = command;
    adapter.Fill(dataset);
 } catch (System.Exception exception) {
    System.Windows.MessageBox.Show(exception.Message, "Error occurred");
    return false;
 } finally {
    command.Dispose();
    connection.Dispose();
 }
 System.Windows.MessageBox.Show(string.Format(
    "Dataset contains {0} tables, {1} rows in table 1 and {2} rows in table 2", 
    dataset.Tables.Count, 
    dataset.Tables[0].Rows.Count, 
    dataset.Tables[1].Rows.Count, 
    "Operation succesful"));

 return true;
}

この方法でデータを取得するために今、非常に簡単。コードの呼び出しは、唯一のパラメータとしてアダプタ方法とデータセットのパラメータを入力します。DataTableのアダプタは、自動的にデータセット内の2つの別々のオブジェクトを作成し、それらを埋めます。私のテストシナリオでは、最初のテーブルには3行が含まれ、2番目の表は、2つの行が含まれています。

そのため、この例では、テーブルは、それが自動的に、そのため表1、表2に命名されますが、テーブルを参照するために名前を使用する場合、それはより説明が賢明である何かに名前を変更しますので、その場で作成されます。

匿名T-SQLブロックを実行

ストレージプロセスは非常に良好であるが、時にはT-SQLコードは、本質的に非常に動的であり得ます。この場合には、ストアドプロシージャを作成することは困難です。バッチはまた、T-SQL文の束を実行するために使用することができます。この方法では、データベースはオブジェクトが、元のバッチ実行の実装を命名されていないのと同じ方法、例えば、SQL Server Management Studioのから実行することができます。

次のようにテストコードがあります:

/// <summary>
/// Executes an anonymous T-SQL batch against the the connection
/// </summary>
/// <param name="connectionString">Connection string to use</param>
/// <param name="generateError">Should the statement generate an error</param>
/// <returns>True if succesful</returns>
public static bool ExecuteAnonymousTSql(string connectionString, bool generateError = false) {
 System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection();
 System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();
 int rowsAffected;

 connection.ConnectionString = connectionString;
 command.CommandText = @"
    DECLARE @counter int = 1
    BEGIN
     WHILE (@counter <= 5) BEGIN
     INSERT INTO MultiStatementTest (somevalue) VALUES (RAND() * 100000);
     SET @counter = @counter + 1;
     " + (generateError ? "WONTWORK" : "") + @"
     END;
    END;";
 command.CommandType = System.Data.CommandType.Text;
 command.Connection = connection;

 try {
    connection.Open();
    rowsAffected = command.ExecuteNonQuery();
 } catch (System.Exception exception) {
    System.Windows.MessageBox.Show(exception.Message, "Error occurred");
    return false;
 } finally {
    command.Dispose();
    connection.Dispose();
 }
 System.Windows.MessageBox.Show(string.Format("{0} rows inserted", 
    rowsAffected, 
    "Operation succesful"));

 return true;
}

さて、この例では、我々はいくつかのテストラインを作成して開始するために同じスクリプトを使用します。あなたが見ることができるように、ループなどの変数宣言は、バッチで完全に有効な文が含まれています。

このコマンドを実行すると5つの要素は、さらに、テーブルに追加されます。注また、閉状態NOCOUNTデフォルトであるため、この方法はバッチに正しい数は、ExecuteNonQuery行を返すこと。NOCOUNTセット上にいる場合は、行数が影響を受ける-1。

エラー処理

どのように行うために、バッチ内の単一のステートメントの実行時にエラーが発生した場合は?私はこれをテストするために、いくつかの文法の誤りを使用しました。バッチは、バッチが動作しません、文は構文エラーが含まれていた後でも、全体として分析されます。UPDATE文のバッチはエラーが含まれている場合たとえば、ステータステーブルは変更されません。

エラーが構文エラーではありませんが、実装プロセスにした場合、状況は異なるものになります。エラーが発生した場合、例えば、誤り検出値への外部キーを考えます。この場合、前のステートメントは(ステートメントに応じて)データベースの状態を変更した可能性があり、それは、適切なトランザクションを使用して、いつものように、それをお勧めします。

結論

バッチモードでは(といけない)など、古き良きストアドプロシージャを、置き換えることはありませんが、適切に使用されている場合、彼らは有用であろうしながら。長いクライアントが呼び出しの間のロジックを必要としないよう、彼らは複数の旅行を必要とせず、非常にダイナミックな操作を作成するために使用することができます。

記事からこの翻訳:一人に対してSQL Serverのアドオン記述リンクなど、複数のSQL文を実行

おすすめ

転載: blog.51cto.com/yuanzhitang/2482948