C# 从 SQL Server 将 200k 记录加载到字典中的最快方法

问题描述 投票:0回答:3

我需要从 SQL Server 检索 200k 条记录。 SQL 在一次操作中返回所有数据。每行代表一个客户。一个客户有多个与其关联的位置,每个位置都有多个与其关联的许可证。性能是这里最重要的因素,我需要知道我的方法是否最快,或者是否有更快的方法将所有这些 SQL 记录加载到 C# 容器中。我使用字典并使用相应的列索引从 SQL 数据读取器分配记录。代码示例如下。现在,.NET 需要 12 分钟来处理 269,000 行。有没有更快的方法来做到这一点?

编辑- 感谢您提供的所有绝妙建议。我将详细说明更多细节,因为我简化了我在这里向您展示的内容,让您了解我如何处理嵌套列表。

大约有4000条代理商记录,每条记录必须每天发送给供应商。每个代理记录都有一个地址、联系人和位置列表。在位置的情况下,每个位置都可以有该位置的地址列表、该位置的联系人、该位置的许可证和该位置的人员。

原开发者发送SQL查询得到所有客户id号的列表(record key)。然后他执行一个 for 循环并发出一个带有特定客户 ID 的单独 sql 请求以获取地址列表,然后发出一个不同的 sql 请求以获取联系人列表,等等。一旦他将一个位置添加到主要代理列表中,他就会进行 7 个单独的 SQL 调用以获取该位置的所有列表。这个过程在运行时需要将近 5 个小时。我的老板把它交给我,希望我在 15 分钟内让它运行起来。

我的第一种方法是向 SQL 服务器发送请求,一次获取所有代理和所有子列表,然后将整个结果集发送回代码。这样做是为了消除所有的 sql 调用。 然后我想将所有这些数据加载到代理记录及其嵌套

列表。现在我的选择中有 100 个字段和 26 个左连接。其中一些连接连接到同一个表,但对不同的记录使用不同的 id。例如,地址表的连接将地址 ID 与代理 ID 相匹配。然后还有另一个连接到地址表,但对于位置列表中的地址,位置 ID 与地址 ID 匹配。

所以现在你知道了完整的故事并且你已经看到了我的代码。我将采纳您提供的一些建议,看看我得到了什么样的改进。一旦我有了更快的东西,我会把它贴在这里,这样你就可以看到我做了什么。如果您有任何进一步的建议,我很乐意听到。

public class LicenseSF
{
    public string LicenseId { get; set; }
}


public class LocationSF
{
    public int LocIdSeq { get; set; }
    public List<LicenseSF> Licenses { get; set; }
}   
 
public class AgentRecord
{
    public int CusIdSeq { get; set; }
    public List<LocationSF> Locations { get; set; } 

}


Dictionary<int, AgentRecord> agencyDict = new Dictionary<int, AgentRecord>();

  using (var con = new SqlConnection(connectionString))
  {
      using (var cmd = new SqlCommand(sql, con))
      {
          con.Open();
          SqlDataReader reader = await cmd.ExecuteReaderAsync();
          if (reader.HasRows)
          {
              while (reader.Read())
              {
                  int cusId = !reader.IsDBNull(0) ? reader.GetInt32(0) : -1;                                              
                  int LocIdSeq  = !reader.IsDBNull(1) ? reader.GetInt32(1) : -1;
                  int LicenseId   = !reader.IsDBNull(2) ? reader.GetInt32(2) : -1;
                  string CUS_Name = !reader.IsDBNull(3) ? reader.GetString(3):string.Empty;   
                 
                  //first, add the new agent if it doesn't already exist in the dictionary
                  AgentRecord agentSF;

                  agencyDict.TryGetValue(cusId, out agentSF);
                  if (agentSF == null)
                  {
                      agentSF = new AgentRecord 
                      {
                          CusIdSeq = cusId
                      }
                      agencyDict[cusId] = agentSF;
                  }

                  //if agent has a location add it to the list for this agent
                  if (!Convert.IsDBNull(LocIdSeq) && LOC_CUS_ID_SEQ_FK > 0)
                  {
                      if (agentSF.Locations == null)
                          agentSF.Locations = new List<LocationSF>();

                      LocationSF locSF = agentSF.Locations.FirstOrDefault(z => z.LocIdSeq== cusId.ToString());

                      if (locSF == null)
                      {
                            locSF = new LocationSF
                            {
                                LocIdSeq= LocIdSeq
                            };
                      }
                      
                      //A location can have multiple licenses. Add them to this specific location

                      if (!Convert.IsDBNull(LicenseId ) && LicenseId > 0)
                      {
                          if(locSF.Licenses == null)
                              locSF.Licenses = new List<LicenseSF>();

                          LicenseSF licSF = agentSF.Licenses.FirstOrDefault(z => z.LicenseId == LocIdSeq.ToString());

                          if (licSF == null)
                          {
                              licSF = new LicenseSF
                              {
                                  LicenseId= LicenseId
                              };
                          }

                      }
                      
                      agentSF.Locations.Add(locSF);

                  }
              }
c# sql-server asp.net-core
3个回答
0
投票

您可以提高很多效率。

  • 首先,将列表转换为字典,并在类中对其进行初始化。
  • 如果你知道你得到了多少数据,请调整你的字典大小。
  • async
    当您能够专用一个线程时,代码通常会稍微慢一些。它的响应速度更快,并且在您每秒运行所有这些次数的情况下表现更好,因此取决于用例。
  • Convert.IsDBNull
    是错误的,它应该只是比较
    -1
    。或者使用可为空的
    int?
    .
  • 读者需要
    using
    .
  • reader.HasRows
    是不必要的。
using var con = new SqlConnection(connectionString);
using var cmd = new SqlCommand(sql, con);
con.Open();
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
    int cusId = !reader.IsDBNull(0) ? reader.GetInt32(0) : -1;                                              
    int LocIdSeq  = !reader.IsDBNull(1) ? reader.GetInt32(1) : -1;
    int LicenseId   = !reader.IsDBNull(2) ? reader.GetInt32(2) : -1;
    string CUS_Name = !reader.IsDBNull(3) ? reader.GetString(3):string.Empty;   
                 
    //first, add the new agent if it doesn't already exist in the dictionary
    if(!agencyDict.TryGetValue(cusId, out var agentSF))
    {
        agentSF = new AgentRecord 
        {
            CusIdSeq = cusId
        }
        agencyDict[cusId] = agentSF;
    }

    //if agent has a location add it to the list for this agent
    if (LocIdSeq != -1 && LOC_CUS_ID_SEQ_FK > 0)
    {
        if(!agentSF.Locations.TryGetValue(cusId, out var locSF)
        {
            locSF = new LocationSF
            {
                LocIdSeq = LocIdSeq
            };
            agentSF.Locations.Add(cusId, locSF);
        }

                      
        //A location can have multiple licenses. Add them to this specific location

        if (LicenseId > 0)
        {
            if(!agentSF.Licenses.TryGetValue(LocIdSeq, out var licSF)
            {
                if (licSF == null)
                {
                    licSF = new LicenseSF
                    {
                        LicenseId= LicenseId
                    };
                }
            }
                      
            agentSF.Locations.Add(locSF);

       }
   }
}

最后,鉴于您显然正在使用三向连接,请考虑三个单独的

select
语句,这可能会提高性能。您可以使用第一个的字典结果来喂养第二个,等等。


0
投票

这个过程在运行时需要将近5个小时。我老板给我的 并希望我在 15 分钟内让它运行起来。

现在 .NET 处理 269,000 行需要 12 分钟。

你的工作已经完成了,因为你的老板给了你一个绩效预算(15 分钟),而你在预算之内很舒服。

如果您让我们知道您的新性能预算是多少,我们可以为您提供更快的代码。这是因为 10 分钟预算的答案与 1 分钟预算的答案大不相同。


0
投票

很久以来我不得不在 C# 和 SQL Server 之间移动大量数据,所以它可能已经改变了。

通常使用 SQL Server 中的一种数据导出工具将数据写入文件然后在 C# 中读取文件会更快。如果您不关心记录的锁定等以防止更新,那么将数据从每个表导出到一个单独的文件然后在 SQL Server 外部处理“连接”也可能更快。

© www.soinside.com 2019 - 2024. All rights reserved.