2012-08-30 125 views
7

我正在處理現有的應用程序。這個應用程序從一個巨大的文件中讀取數據,然後在進行一些計算後將數據存儲在另一個表中。非常慢的foreach循環

但是這樣做的循環(見下文)需要很長時間。由於該文件有時包含1,000個記錄,因此整個過程需要數天。

我可以用其他東西替換這個foreach循環嗎?我嘗試使用Parallel.ForEach,它確實有幫助。我對此很陌生,所以感謝您的幫助。

foreach (record someredord Somereport.r) 
{ 
    try 
    { 
     using (var command = new SqlCommand("[procname]", sqlConn)) 
     { 
      command.CommandTimeout = 0; 
      command.CommandType = CommandType.StoredProcedure; 
      command.Parameters.Add(…); 

      IAsyncResult result = command.BeginExecuteReader(); 
      while (!result.IsCompleted) 
      { 
       System.Threading.Thread.Sleep(10); 
      } 
      command.EndExecuteReader(result); 
     } 
    } 
    catch (Exception e) 
    { 
     … 
    } 
} 

審查應答後,我除去異步並用於編輯爲下面的代碼。但是這並沒有改善性能。

using (command = new SqlCommand("[sp]", sqlConn)) 
{ 
    command.CommandTimeout = 0; 
    command.CommandType = CommandType.StoredProcedure; 
    foreach (record someRecord in someReport.) 
    { 
     command.Parameters.Clear(); 
     command.Parameters.Add(....) 
     command.Prepare();        

     using (dr = command.ExecuteReader()) 
     { 
      while (dr.Read()) 
      { 
       if() 
       { 

       } 
       else if() 
       { 

       } 
      } 
     }        
    }       
} 
+7

兩個想法 - 首先,你做的異步錯誤,因此你很可能會爲循環中的許多項目睡覺。其次,您是否可以在整個循環中重用SqlCommand對象,而不是每次都創建/銷燬一個對象? – n8wrl

+3

如果您告訴我們更多關於您想要完成的內容,我們可能會向您展示SQL解決方案,該解決方案的運行速度提高了幾個數量級,並完全避免了整個異步/並行業務。 –

+1

@ user1110790:您發佈的代碼充滿了錯誤(至少仍然有一個),所以我已經清理了一下。我可以虛心地建議,當你在SO上發佈信息時,請確保你的代碼是正確的;否則,你可能會得到許多關注於此的評論,而不是實際的問題。 – stakx

回答

7

而是循環SQL連接的這麼多次,有沒有考慮過從SQL Server中提取出來的整套數據,並通過數據集中處理的數據?

編輯:決定進一步解釋我的意思.. 你可以做以下的,僞代碼如下

  1. 使用SELECT *,並從數據庫中獲取的所有信息,並將其存儲到列表字典
  2. 做你的foreach(在someReport中記錄someRecord)並像往常一樣進行條件匹配。
+5

+1。但是,將數據加載到強類型集合中然後使用Linq而不是使用DataSet可能會更好。 –

+0

我嘗試使用一個數據,但由於某種原因,它進一步放慢了過程。我們也正在記錄每一項操作。你認爲我可以做一個單獨的線程日誌記錄提高性能? – user1110790

+0

@ user1110790 - 根據我的經驗,數據集通常工作起來很慢。這就是爲什麼我建議一個強類型的集合。簡單地在內存中使用IEnumerable集合將會非常快速。如果你正在做很多關鍵的查找,可以通過一個'Dictionary'使它更快。 –

6

第1步:溝通在異步的嘗試。它沒有正確實施,而且你仍然阻止。所以只需執行程序並查看是否有幫助。

第2步:將SqlCommand移到循環的外部並在每次迭代中重新使用它。這樣你就不會爲你的循環中的每一件物品產生和銷燬它。

警告:請確保您重置/清除/刪除您不需要的參數從前面的迭代。我們用可選的參數做了這樣的事情,並且從前一次迭代中「流血」,因爲我們沒有清理我們不需要的參數!

+1

+1第二步非常重要,很多人都忘記了。 –

+0

「步驟2:將SqlCommand移到循環之外......」是主要改進! – Lester

3

你最大的問題是,你遍歷這個:

IAsyncResult result = command.BeginExecuteReader(); 

while (!result.IsCompleted) 
{ 
    System.Threading.Thread.Sleep(10); 
} 

command.EndExecuteReader(result); 

異步模型的整個觀念是調用線程(一個做這個循環)應旋轉了所有的異步在開始使用End方法處理結果之前,使用Begin方法執行任務。如果您在主調用線程中使用Thread.Sleep()來等待異步操作完成(如同您在這裏),那麼您做錯了,最終發生的是每個命令一次一個正在被呼叫,然後在下一次開始之前等待。

相反,嘗試這樣的事:

public void BeginExecutingCommands(Report someReport) 
{ 
    foreach (record someRecord in someReport.r) 
    { 
     var command = new SqlCommand("[procname]", sqlConn); 

     command.CommandTimeout = 0; 
     command.CommandType = CommandType.StoredProcedure; 
     command.Parameters.Add(…); 

     command.BeginExecuteReader(ReaderExecuted, 
      new object[] { command, someReport, someRecord });     
    } 
} 

void ReaderExecuted(IAsyncResult result) 
{ 
    var state = (object[])result.AsyncState; 
    var command = state[0] as SqlCommand; 
    var someReport = state[1] as Report; 
    var someRecord = state[2] as Record; 

    try 
    { 
     using (SqlDataReader reader = command.EndExecuteReader(result)) 
     { 
      // work with reader, command, someReport and someRecord to do what you need. 
     } 
    } 
    catch (Exception ex) 
    { 
     // handle exceptions that occurred during the async operation here 
    } 
} 
+0

我已經從回調方法('ReaderExecuted')中刪除了'public'可訪問性修飾符。這些不應該是公開的,因爲它們不是完整的操作,而只是另一種方法的邏輯「剩餘」。 – stakx

+0

+1用於演示正確使用'Begin ...'/'End ...'異步模式。但是,我不能100%確定這會解決主要問題。我也不確定線程​​池和數據庫能夠處理幾乎同時發生的幾千個請求......? – stakx

+1

線程池將按照它們進入的速度一樣快地安排它們的數目,達到「最小」閾值。然後,當線程數超過該閾值時,它會每秒啓動4個線程,直到達到最大閾值,此時它將保留任何新的請求。最小和最大閾值是可配置的。 – KeithS

0

它似乎執行你的命令SQL看跌期權鎖定所需的資源,這就是強制你使用Async方法(我猜測)的原因。

如果數據庫未處於使用狀態,請嘗試對其進行獨佔訪問。即使在那時,由於數據模型的複雜性,也有一些內部事務考慮諮詢數據庫設計人員。

+0

謝謝。數據庫事實上正在被使用,所以我不能按照你的建議去做。 – user1110790

1

在SQL的另一端寫一個是(一個)磁盤。你很少可以並行寫得更快。事實上,由於索引碎片,並行通常會降低速度。如果您可以在加載之前按主(羣集)鍵對數據進行排序。在大負載甚至禁用其他鍵時,加載數據重建鍵。

不太清楚在異步中做了什麼,但肯定它沒有做你期望的,因爲它正在等待它自己。

try 
{ 
    using (var command = new SqlCommand("[procname]", sqlConn)) 
    { 
     command.CommandTimeout = 0; 
     command.CommandType = CommandType.StoredProcedure; 

     foreach (record someredord Somereport.r) 
     { 
      command.Parameters.Clear() 
      command.Parameters.Add(…); 

      using (var rdr = command.ExecuteReader()) 
      { 
       while (rdr.Read()) 
       { 
        … 
       } 
      } 
     } 
    } 
} 
catch (…) 
{ 
    … 
} 
+0

@stakx將一個rdr.Close()照顧嗎? – Paparazzi

+0

IIRC'rdr.Close()'和'rdr.Dispose()'有相同的效果,但'using'塊比在'finally'子句中包裝'rdr.Close()'容易你必須做的異常安全)。 – stakx

+0

好的,但在每個循環創建一個新的rdr的開銷。如果rdr被重用,那麼什麼異常不會被抓到? – Paparazzi

1

正如我們在評論中討論的那樣,將這些數據存儲在內存中並使用它可能會有更高效的方法。

所以一個簡單的方法是從Entity Framework開始。實體框架將根據您的數據庫模式自動爲您生成類。然後你可以用import a stored procedure來保存你的SELECT語句。我建議將存儲過程導入EF的原因在於,此方法通常比在LINQ中針對EF執行查詢更有效。

然後運行存儲過程和數據存儲在List這樣的...

var data = db.MyStoredProc().ToList();

然後,你可以做你想做與data什麼。或者,正如我所說,如果你做了很多主鍵的查找然後使用ToDictionary()這樣的事情...

var data = db.MyStoredProc().ToDictionary(k => k.MyPrimaryKey);

無論哪種方式,你將與你的data合作內存在這點。