【问题标题】:How to fetch,process and save huge record set in c# efficiently?如何在 C# 中有效地获取、处理和保存巨大的记录集?
【发布时间】:2019-01-12 04:24:35
【问题描述】:

我正在努力实现以下目标:

  • 从 SQL DB 中获取数据。
  • 将数据传递给具有第三方方法的 PerformStuff 方法 MethodforResponse(检查输入并提供响应)

  • 将响应 (xml) 保存回 SQL DB。

下面是示例代码。性能方面它不好,如果数据库中有 1000,000 条记录,它非常慢。

这样做更好吗?任何想法或提示可以使它变得更好。

请帮忙。

using thirdpartylib;
 public class Program
    {

        static void Main(string[] args)
        {
            var response = PerformStuff();
            Save(response);


        }

        public class TestRequest
        {
            public int col1 { get; set; }
            public bool col2 { get; set; }
            public string col3 { get; set; }
            public bool col4 { get; set; }

            public string col5 { get; set; }
            public bool col6 { get; set; }
            public string col7 { get; set; }

        }
        public class TestResponse
        {
            public int col1 { get; set; }
            public string col2 { get; set; }
            public string col3 { get; set; }
            public int col4 { get; set; }

        }
        public TestRequest GetDataId(int id)
        {
            TestRequest testReq = null;
            try
            {
                SqlCommand cmd = DB.GetSqlCommand("proc_name");
                cmd.AddInSqlParam("@Id", SqlDbType.Int, id);
                SqlDataReader dr = new SqlDataReader(DB.GetDataReader(cmd));
                while (dr.Read())
                {
                    testReq = new TestRequest();

                    testReq.col1 = dr.GetInt32("col1");
                    testReq.col2 = dr.GetBoolean("col2");
                    testReq.col3 = dr.GetString("col3");
                    testReq.col4 = dr.GetBoolean("col4");
                    testReq.col5 = dr.GetString("col5");
                    testReq.col6 = dr.GetBoolean("col6");
                    testReq.col7 = dr.GetString("col7");



                }
                dr.Close();
            }

            catch (Exception ex)
            {
                throw;
            }
            return testReq;

        }
        public static TestResponse PerformStuff()
        {
            var response = new TestResponse();
            //give ids in list
            var ids = thirdpartylib.Methodforid()


            foreach (int id in ids)
            {

                var request = GetDataId(id);


                var output = thirdpartylib.MethodforResponse(request);

                foreach (var data in output.Elements())
                {
                    response.col4 = Convert.ToInt32(data.Id().Class());
                    response.col2 = data.Id().Name().ToString();

                }
            }
            //request details
            response.col1 = request.col1;
            response.col2 = request.col2;
            response.col3 = request.col3;

            return response;
        }

        public static void Save(TestResponse response)
        {

            var Sb = new StringBuilder();
            try
            {
                Sb.Append("<ROOT>");
                Sb.Append("<id");
                Sb.Append(" col1='" + response.col1 + "'");
                Sb.Append(" col2='" + response.col2 + "'");
                Sb.Append(" col3='" + response.col3 + "'");
                Sb.Append(" col4='" + response.col4 + "'");

                Sb.Append("></Id>");
                Sb.Append("</ROOT>");
                var cmd = DB.GetSqlCommand("saveproc");
                cmd.AddInSqlParam("@Data", SqlDbType.VarChar, Sb.ToString());
                DB.ExecuteNoQuery(cmd);

            }
            catch (Exception ex)
            {

                throw;
            }
        }

    }

谢谢!

【问题讨论】:

  • 是否需要将 XML 保存到 DB 中?
  • @DarjanBogdan,其实不是。
  • 我建议将记录分成更小的块。存储过程应该只发出一个子集,并为客户端提供某种分页。例如,客户端可以请求特定范围(0 到 500)的记录。在第二个结果集中,该过程可以告诉调用者还剩下多少条记录。
  • 我能想到的唯一加速方法是第三方函数是否会在每次调用时接受多个请求对象。否则,您将陷入顺序操作。您可以尝试生成多个工作线程,以便可以同时处理多个记录。
  • 通过阅读您的示例代码,您似乎阅读了许多记录而只写了一条。您的 PerformStuff 方法被调用一次并仅返回一个 TestResponse 方法。这是真的吗?

标签: c# sql database


【解决方案1】:

我认为您的问题的根源是您逐条获取和插入数据。没有办法优化它。一般来说,您需要改变方法。

您应该想出一个解决方案: 1. 将所有数据在一个命令中获取到数据库。 2. 处理它。 3. 使用BULK INSERT 之类的技术,在一个命令中将其保存回数据库。请注意 BULK INSERT 有一定的局限性,请仔细阅读文档。

【讨论】:

    【解决方案2】:

    您的问题非常广泛,PerformStuff() 方法从根本上来说会很慢,因为它在输出的另一次迭代之前需要O(n) * db_lookup_time。所以,对我来说,你似乎以错误的方式解决这个问题。

    数据库查询语言旨在优化数据遍历。因此,通过 id 进行迭代,然后检查值,会绕过这个产生最慢​​的查找时间可能。

    改为,利用 SQL 强大的查询语言并使用像 where id &lt; 10 and value &gt; 100 这样的子句,因为您最终希望限制需要由 C# 处理的数据集的大小

    所以:

    1. 只需从数据库中读取您需要的最小量数据
    2. 将此数据作为一个单元处理,并行性可能会有所帮助。
    3. 在一个数据库连接中写回修改。

    希望这能让你朝着正确的方向前进。

    【讨论】:

      【解决方案3】:

      根据您的评论,您可以在解决方案中增强多项功能,从内存消耗到 CPU 使用率。

      1. 在数据库级别利用paging。不要一次获取所有记录,以避免在 1+ 百万条记录的情况下出现内存泄漏和/或高内存消耗,而是逐块获取并做任何你需要做的事情。

      2. 由于您不需要将 XML 保存到数据库中,您可以选择将响应保存到文件中。将 XML 保存到文件中使您有机会将stream 数据保存到本地磁盘上。

      3. 不要自己组装 XML,而是使用 XmlSerializer 为您完成这项工作。 XmlSerializer 可以很好地与XmlWriter 一起使用,它最终可以与任何stream 一起使用,包括FileStream。有一个thread关于它,你可以举个例子。

      总之,PerformStuff 方法不仅更快,而且需要更少的资源(内存、CPU),最重要的是,您可以轻松地限制程序的资源消耗(通过更改数据库页面的大小)。

      【讨论】:

        【解决方案4】:

        观察:您的需求看起来与 map/reduce 模式匹配。

        如果thirdpartylib.Methodforid() 返回的ids 集合中的值相当密集,并且proc_name 存储过程后面的表中的行数与ids 集合中的项目数接近,您可以使用单个 SQL 查询(和多行结果集)检索您需要的所有记录,而不是逐个检索它们。这可能看起来像这样:

        public static TestResponse PerformStuff()
        {
            var response = new TestResponse();
        
            var idHash = new HashSet<int> (thirdpartylib.Methodforid());
        
            SqlCommand cmd = DB.GetSqlCommand("proc_name_for_all_ids");
            using (SqlDataReader dr = new SqlDataReader(DB.GetDataReader(cmd)) { 
                while (dr.Read()) {
                    var id = dr.GetInt32("id");
                    if (idHash.Contains(id)) {
                        testReq = new TestRequest();
        
                        testReq.col1 = dr.GetInt32("col1");
                        testReq.col2 = dr.GetBoolean("col2");
                        testReq.col3 = dr.GetString("col3");
                        testReq.col4 = dr.GetBoolean("col4");
                        testReq.col5 = dr.GetString("col5");
                        testReq.col6 = dr.GetBoolean("col6");
                        testReq.col7 = dr.GetString("col7");
        
                        var output = thirdpartylib.MethodforResponse(request);
                        foreach (var data in output.Elements())  {
                            response.col4 = Convert.ToInt32(data.Id().Class());
                            response.col2 = data.Id().Name().ToString();
                        }
                    } /* end if hash.Contains(id) */  
                }  /* end while dr.Read() */
            } /* end using() */
            return response;
        }
        

        为什么会更快?它减少了许多数据库查询,而是流入多行数据进行处理。这将比您的示例更有效。

        为什么它不起作用?

        1. 如果必须按照thirdpartylib.Methodforid() 生成的相同顺序处理id 值,则它将不起作用。
        2. 如果无法检索所有行,即没有可用的proc_name_for_all_ids 存储过程,您将无法流式传输这些行。

        【讨论】:

          猜你喜欢
          • 2011-12-04
          • 2021-12-18
          • 1970-01-01
          • 2011-02-05
          • 2020-11-27
          • 1970-01-01
          • 1970-01-01
          • 2023-03-08
          • 1970-01-01
          相关资源
          最近更新 更多