2014-01-07 22 views
12

我看了很多問題,但很明顯我的SO-fu沒有達到任務,所以在這裏。我試圖有效地使用預先準備好的語句,而不僅僅是指參數化單個語句,而是編譯一個重複使用多次的語句。我的問題在於參數和重用以及如何正確實施。如何在C#.NET(SQL Server)中正確有效地重用預準備語句?

一般來說,我遵循這一程序(人爲的例子):

SqlConnection db = new SqlConnection(...); 
SqlCommand s = new SqlCommand("select * from foo where [email protected]", db); 
s.Parameters.Add("@a", SqlDbType.VarChar, 8); 
s.Prepare(); 
... 
s.Parameters["@a"] = "bozo"; 
s.Execute(); 

超,工程。但是,我不想在每次運行此查詢時都執行所有這些步驟(或後四個步驟)。這似乎是抵消了準備好的陳述的整個想法。在我看來,我只需要改變參數並重新執行,但問題是如何做到這一點?

我試過s.Parameters.Clear(),但是這實際上消除了參數本身,而不僅僅是價值,所以我基本上是需要重新Add的參數,並再次重新Prepare,這似乎打破整個點爲好。不用了,謝謝。

在這一點上,我剩下迭代通過s.Parameters並將它們全部設置爲null或其他值。 這是正確的嗎?不幸的是,在我目前的項目中,我有大約15個參數的查詢需要執行〜每次運行10,000次。我可以將這個迭代轉換成一種方法,但是想知道是否有更好的方法來做到這一點(沒有存儲過程)。

我目前的解決方法是擴展方法SqlParameterCollection.Nullify,它將所有參數設置爲null,這對我的情況來說很好。我只是在執行後運行它。


我發現了一些幾乎相同,但(恕我直言)沒有答案的問題:

Prepared statements and the built-in connection pool in .NET

SQLite/C# Connection Pooling and Prepared Statement Confusion(!塞爾是如此接近接聽)

最佳答案我能找到的是( 1)以上常識和(2)本頁:

http://msdn.microsoft.com/en-us/magazine/cc163799.aspx

+0

奇怪的是,我在編輯框中的第一個鏈接之後有一個換行符,但它沒有使它進入呈現的輸出...固定。 – Josh

+0

把它放在一個方法上,傳遞一個SqlParameters集合給它並重用其餘的。 :) –

+0

請閱讀http://msdn.microsoft.com/en-us/magazine/ee236412.aspx,並確保準確定義參數的大小。這應該有助於提高性能。 (當然除了storedprocedure) – Steve

回答

9

當重新使用一個準備好的SqlCommand時,你所需要做的就是將參數值設置爲新的?使用後不需要清除它們。對於我自己,我還沒有看到在過去的10年裏產生的數據庫管理系統,這些數據庫系統從編寫語句中獲得了明顯的好處(我想如果數據庫服務器處於CPU的極限,它可能不會,但這不是典型)。你確定Preparing是必要的嗎?

運行相同的命令「每次運行約10,000次」對我來說有點臭,除非你是從外部源上傳的。在這種情況下,批量加載可能有所幫助?每場比賽都在做什麼?

乾杯 -

+0

它基本上是一個來自各種來源(SQL,LDAP,SOAP等)的週期性外部負載,通過處理將傳入數據轉換爲新結構。不幸的是,它比列地圖或選擇/插入更復雜。我已經看到其他地方的其他答案,通過準備發誓,並且在網上閱讀了很多東西之後,我不相信大多數人會正確地使用準備好的語句(爲此目的)。清除它們只是一個預防措施,問題是如果這個(僅重置參數)是重新使用該命令來保留編譯語句的正確方法。 – Josh

+1

謝謝。你只需要將參數重新設置爲新的值(我剛剛測試過,它的工作原理)。你不需要將它們設置爲null或任何東西。所以是的,保持連接打開,保持SqlCommands周圍,並根據需要重置參數值。 –

+0

感謝您注意保持連接打開。我最終創建了一個靜態DB類並使用它「註冊」了命令,這樣我只需要一次連接就可以重複使用這些命令。幸運的是,它是一個單線程應用程序。我也做到了,所以我必須在執行註冊命令時提交參數,並確保它們都存在,因此沒有以前的值(它們在我發現的執行之間逗留)。 – Josh

4

要添加西蒙的回答,prior to Sql 2005Command.Prepare()會提高即席查詢的查詢計劃緩存(存儲過程一般會被編譯)。但是,在more recent Sql Versions中,如果您的查詢是參數化的,那麼也可以對參數化的即席查詢進行緩存,從而減少對Prepare()的需求。

下面是保留了SqlParameters集合改變這些參數的值而變化,以防止參數的重複創建的僅有的值的一個例子(即,節省參數對象創建和集合):

using (var sqlConnection = new SqlConnection("connstring")) 
{ 
    sqlConnection.Open(); 
    using (var sqlCommand = new SqlCommand 
     { 
      Connection = sqlConnection, 
      CommandText = "dbo.MyProc", 
      CommandType = CommandType.StoredProcedure, 
     }) 
    { 
     // Once-off setup per connection 
     // This parameter doesn't vary so is set just once 
     sqlCommand.Parameters.Add("ConstantParam0", SqlDbType.Int).Value = 1234; 
     // These parameters are defined once but set multiple times 
     sqlCommand.Parameters.Add(new SqlParameter("VarParam1", SqlDbType.VarChar)); 
     sqlCommand.Parameters.Add(new SqlParameter("VarParam2", SqlDbType.DateTime)); 

     // Tight loop - performance critical 
     foreach(var item in itemsToExec) 
     { 
     // No need to set ConstantParam0 
     // Reuses variable parameters, by just mutating values 
     sqlParameters["VarParam1"].Value = item.Param1Value; // Or sqlParameters[1].Value 
     sqlParameters["VarParam2"].Value = item.Param2Date; // Or sqlParameters[2].Value 
     sqlCommand.ExecuteNonQuery(); 
     } 
    } 
} 

注:

  • 如果插入了大量的行,和併發與數據庫的其他居民是非常重要的,如果一個ACID事務邊界不importan噸,你可能會考慮批處理和提交更新,使得一次只能少於5000個行鎖,以防止表鎖升級。
  • 根據您的proc實際正在做什麼工作,可能有機會並行化循環,例如,與TPL。顯然連接和命令不是線程安全的每個任務將需要自己的連接和可重用命令 - localInit overload of Parallel.ForEach is ideal for this