2017-09-14 94 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(); 
      } 



     } 


    } 
}