2017-09-14 96 views
1

我是一個多線程新手和一個SQL新手,所以請原諒任何菜鳥的錯誤。多線程SQL select語句

我想異步執行很多SQL查詢。查詢都是來自同一個數據庫中同一個表的select語句。我可以同步運行它們,一切正常,但測試一個小子集導致我相信同步運行所有查詢將花費大約150個小時,這太長了。因此,我試圖找出如何並行運行它們。

我試圖在run a method multiple times simultaneously in c#的答案後對代碼建模,但是我的代碼沒有正確執行(這是錯誤的,儘管我不知道具體如何,代碼只是說錯誤發生)。

這裏是我有什麼(什麼我實際上做了一個更小和更簡單的版本):

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<string> EmployeeIDs = File.ReadAllLines(/* Filepath */); 
     List<Tuple<string, string>> NamesByID = new List<Tuple<string, string>>(); 

     //What I do not want to do (because it takes too long) ... 
     using (SqlConnection conn = new SqlConnection(/* connection string */)) 
     { 
      foreach (string id in EmployeeIDs) 
      { 
       using (SqlCommand cmd = new SqlCommand("SELECT FirstName FROM Employees WITH (NOLOCK) WHERE EmployeeID = " + id, conn)) 
       { 
        try 
        { 
         conn.Open(); 
         NamesByID.Add(new Tuple<string, string> (id, cmd.ExecuteScalar().ToString())); 
        } 
        finally 
        { 
         conn.Close(); 
        } 
       } 
      } 
     } 


     //What I do want to do (but it errors) ... 
     var tasks = EmployeeIDs.Select(id => Task<Tuple<string, string>>.Factory.StartNew(() => RunQuery(id))).ToArray(); 
     Task.WaitAll(tasks); 
     NamesByID = tasks.Select(task => task.Result).ToList(); 
    } 

    private static Tuple<string, string> RunQuery(string id) 
    { 
     using (SqlConnection conn = new SqlConnection(/* connection string */)) 
     { 
      using (SqlCommand cmd = new SqlCommand("SELECT FirstName FROM Employees WITH (NOLOCK) WHERE EmployeeID = " + id, conn)) 
      { 
       try 
       { 
        conn.Open(); 
        return new Tuple<string, string> (id, cmd.ExecuteScalar().ToString()); 
       } 
       finally 
       { 
        conn.Close(); 
       } 
      } 
     } 
    } 
} 

注:我不在乎這究竟是如何多線程(任務,parallel.foreach,BackgroundWorker的等)。這將被用來精確地運行一次〜30,000次select查詢,所以我只需要它運行一次(我希望〜8小時=一個工作日,但我會拿我能得到的)一次。它不一定非常漂亮。

預先感謝您!

+1

是選擇所有不是一個選項,然後處理它們的客戶端?運行這麼多個人查詢似乎是一種非常低效的方式。表中有多少條記錄? – Tone

+0

你有'30,000'查詢運行,是否因爲有'30,000'員工? – Squirrel

+0

@Tone我不確定選擇全部是否可行。我能看到的複雜性是在真實版本中有一個子查詢。 (在我的例子中,真正的版本有一個子查詢來得到什麼是「id」)因此,真正的查詢看起來像「從表中選擇FristName,其中Employee ID =(從otherTable中選擇top 1 EmployeeID,其中variable = value)所有?(就像我說的,SQL新手。)第二點,每個表(查詢和子查詢)有成千上萬的行,查詢表有200列。 – BrianH

回答

1

你想做一個查詢來獲得所有的ID /名稱組合,然後把它們放到一個字典(快速訪問)。這將消除運行30,000個查詢的非常緩慢的過程,並降低代碼的複雜性。

我可以讓你更具體的東西,如果你貼實際的SQL查詢(如果你需要,你可以更改列名和表名),但是這應該是接近:

;WITH CompTransCTE AS (
    SELECT CompanyID, MIN(TransactionID) AS TransactionID 
    FROM CompanyTransactions 
    WHERE CompanyID IN (/*Comma seperated list of values*/) 
    GROUP BY CompanyID 
) 
SELECT CT.CompanyID, T.DollarAmount, T.TransactionID 
FROM Transactions AS T 
INNER JOIN CompTransCTE AS CT ON CT.TransactionID = T.TransactionID; 
+0

實際查詢(名稱改變,因爲說實話,我不知道如果我被允許發佈或沒有,所以我會犯錯的一面謹慎)是「SELECT DollarAmount FROM Transactions WHERE TransactionID =(SELECT TOP 1 TransactionID FROM CompanyTransactions WHERE CompanyID = @CompanyID ORDER BY TransactionID ASC)並且我的文件具有所有輸入到查詢中的CompanyID – BrianH

+0

@BrianH我已更新查詢,如果您有問題,請告知我。幾個小時,但我會檢查什麼時候再回來 – Trisped

+0

明天我將不得不回覆你,因爲我明天將在工作中測試它,我會讓你知道的,我非常感謝你的幫助。我有1個後續問題:我有30,000個ID,SQL是否會處理一個包含30,000個條目的逗號分隔列表?如果是的話,它會真的更快嗎?還是更關心正確的做事方式與減少時間? – BrianH

3

這是完全錯誤的。你應該建立一個查詢來選擇你需要的所有FirstNames。如果您需要將一堆ID傳遞給服務器,那沒問題,只需使用table valued parameter(又名TVP),昏迷分隔的值列表確實不能很好地擴展。如果查詢正確寫入並且索引表,那應該是相當快的。 100k行桌是一張小桌子。然後

的查詢可能看起來像這樣

SELECT DollarAmount, comp.CompanyID 
FROM Transactions 
JOIN (SELECT MIN(TransactionID) as minTransactionID, CompanyID 
     FROM CompanyTransactions  
     GROUP BY CompanyID 
    ) AS comp 
ON Transactions.TransactionID = comp.minTransactionID 
JOIN @IDList ON id = comp.CompanyID 

您可以使用IN代替JOIN如果TVP的ID不是唯一的。

Btw。你知道NOLOCK是什麼意思嗎?如果您是數據庫的唯一用戶並且使用它單線程或不修改任何數據,那麼您是安全的。除此之外,它意味着你還好有很小的機率:

  • 一些記錄可能會丟失在結果
  • 有在結果
  • 有結果中的行重複記錄,即從來沒有承諾永不被接受爲有效數據
  • 如果使用VARCHAR(最大值),你可能會得到一個從未被存儲
+0

你能舉一個我怎麼做的例子嗎? (SQL新手)我不確定你的意思是「表值參數」。我可以將它作爲逗號分隔列表發送(我知道這是什麼),但在逗號列表中傳遞30,000個ID似乎......過度。謝謝。對不起,我缺乏知識。 – BrianH

+0

我添加了解釋鏈接。 TVP有一件事要知道,你必須在服務器上創建表類型。但它真的回報,它實際上是關於兩行代碼。您可以從存儲過程開始掌握原理,但TVP不限於此,您可以在任何其他查詢中使用此參數。 –

0

沒有在數據庫中創建一個用戶自定義表類型的文本,你可以使用SqlBulkCopy將ID加載到臨時表中,並在查詢中引用該ID。

using System; 
using System.Collections.Generic; 
using System.Data; 
using System.Data.SqlClient; 
using System.Linq; 

namespace ConsoleApp11 
{ 
    class Program 
    { 

     static void Main(string[] args) 
     { 
      //var EmployeeIDs = File.ReadAllLines(""/* Filepath */); 
      var EmployeeIDs = Enumerable.Range(1, 30 * 1000).ToList(); 
      var dt = new DataTable(); 
      dt.Columns.Add("id", typeof(int)); 

      dt.BeginLoadData(); 
      foreach (var id in EmployeeIDs) 
      { 
       var row = dt.NewRow(); 
       row[0] = id; 
       dt.Rows.Add(row); 
      } 
      dt.EndLoadData(); 

      using (SqlConnection conn = new SqlConnection("server=.;database=tempdb;integrated security=true")) 
      { 
       conn.Open(); 

       var cmdCreateTemptable = new SqlCommand("create table #ids(id int primary key)",conn); 
       cmdCreateTemptable.ExecuteNonQuery(); 

       //var cmdCreateEmpable = new SqlCommand("create table Employees(EmployeeId int primary key, FirstName varchar(2000))", conn); 
       //cmdCreateEmpable.ExecuteNonQuery(); 


       var bc = new SqlBulkCopy(conn); 
       bc.DestinationTableName = "#ids"; 
       bc.ColumnMappings.Add("id", "id"); 
       bc.WriteToServer(dt); 

       var names = new List<string>(); 
       var cmd = new SqlCommand("SELECT FirstName, EmployeeId FROM Employees WHERE EmployeeID in (select id from #ids)", conn); 
       using (var rdr = cmd.ExecuteReader()) 
       { 
        var firstName = rdr.GetString(0); 
        var id = rdr.GetInt32(1); 
        names.Add(firstName); 
       } 

       Console.WriteLine("Hit any key to continue"); 
       Console.ReadKey(); 
      } 



     } 


    } 
}