.NET執行SQL性能優化一: 針對SQL Server批量執行SQL 語句

本文介紹了幾種如何使用一個SqlCommand執行多條SQL語句的技術。

介紹

使用ADO.NET對SQL Server進行數據存儲經常被忽略的功能之一是它能夠使用單個語句執行多個SQL語句SqlCommand。通常,程序分別執行語句和/或調用存儲過程來執行更大的語句。當然,使用存儲過程是一種首選方法,但是在某些情況下,一次調用執行多個語句是有益的。這可以使用批處理來完成,這基本上意味着一組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語句

第一個變體用於SqlCommand.ExecuteNonQuery對測試表執行兩個單獨的SQL語句。第一個將字段更新somevalue一個,第二個更新字段 。該方法如下所示:

/// <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屬性包含將在此批處理中執行的所有語句。語句用分號分隔。

執行批處理後,行已更新兩次,因此表的內容類似於:

id somevalue

1857
2 76
3735
4549
5270
需要注意的重要一件事是,返回的受影響的行數ExecuteNonQuery爲10。表中有五行,每行更新了兩次,因此更新的總數爲10。因此,即使有批次,也可以檢查無論哪個語句進行更新,都將更新正確的行數。

使用Data Reader執行兩個SELECT語句

下一個測試是執行兩個不同的SELECT語句,並使用一個SqlDataReader類讀取結果。方法是:

/// <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方法前進到下一個結果集。此後,第二組值可以再次循環通過。第二組結果:

76
270

將SqlDataAdapter用於多個SELECT語句

SqlDataReader如果要將結果存儲在中,通常使用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;
}

現在,以這種方式獲取數據非常容易。該代碼僅調用Fill適配器的方法,並將DataSet參數作爲參數傳遞。適配器會自動DataTable在數據集中創建兩個單獨的對象,然後填充它們。在我的測試場景中,第一張表包含三行,第二張表包含兩行。

由於在此示例中,表是即時創建的,因此會自動爲其命名Table1,Table2因此,如果使用名稱來引用表,則將名稱更改爲更具描述性的名稱是明智的。

執行匿名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;
}

現在,在此示例中,使用了與開始創建幾個測試行相同的腳本。如您所見,變量聲明,循環等是包含在批處理中的完全有效的語句。

運行該命令時,會將另外五行添加到表中。還要注意,由於NOCOUNT默認情況下處於關閉狀態,因此該ExecuteNonQuery方法將返回正確數量的行插入到批處理中。如果NOCOUNT設置爲on,則受影響的行數爲-1。

錯誤處理

如果在批處理中執行單個語句時發生錯誤怎麼辦?我使用了一些語法錯誤來對此進行測試。該批處理將作爲一個整體進行分析,因此即使以後的語句包含語法錯誤,該批處理也不會起作用。例如,如果UPDATE批處理中包含錯誤的 語句,則表的狀態不會更改。

如果錯誤不是語法錯誤而是在執行過程中發生,則情況會有所不同。考慮例如當錯誤值出現時檢測到的外鍵錯誤。在這種情況下,先前的語句可能已經更改了數據庫狀態(取決於語句),因此建議像往常一樣使用適當的事務。

結論

雖然批處理的方式不會(也不應該)代替良好的舊存儲過程等,但如果使用得當,它們將很有用。只要調用之間不需要客戶端邏輯,它們就可以用於創建非常動態的操作,而無需進行多次往返。

本文翻譯自文章: Executing multiple SQL statements as one against SQL Server請添加鏈接描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章