非常缓慢的foreach循环

时间:2012-08-30 17:17:01

标签: c# .net ado.net

我正在处理现有的应用程序。此应用程序从一个巨大的文件中读取数据,然后在进行一些计算后,将数据存储在另一个表中。

但这样做的循环(见下文)需要很长时间。由于该文件有时包含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)
    {
        …
    }
}

在查看答案后,我删除了Async并使用了编辑过的代码,如下所示。但这并没有改善表现。

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 ()
                {

                }
            }
        }                             
    }                        
}

6 个答案:

答案 0 :(得分:8)

不是多次循环sql连接,而是考虑从sql server中提取整个数据集并通过数据集处理数据?

编辑:决定进一步解释我的意思.. 您可以执行以下伪代码,如下所示

  1. 使用select *并从数据库中获取所有信息并将其存储到类或词典列表中。
  2. 做你的foreach(在someReport中记录someRecord)并像往常一样进行条件匹配。

答案 1 :(得分:6)

第1步:在异步时抛弃尝试。它没有正确实现,你无论如何都要阻止。所以只需执行该程序,看看是否有帮助。

步骤2:将SqlCommand移出循环之外,并在每次迭代时重复使用它。这样你就不会为循环中的每个项目产生和销毁它的成本。

警告:确保重置/清除/删除上一次迭代中不需要的参数。我们使用可选参数执行了类似的操作,并且在上一次迭代中使用了'bleed-thru',因为我们没有清理我们不需要的参数!

答案 2 :(得分: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
    }
}

答案 3 :(得分:1)

在SQL的另一端写入是一个(一个)磁盘。你很少能并行写得更快。事实上,并行通常会因索引碎片而减慢速度。如果您可以在加载之前按主(群集)密钥对数据进行排序。在很大的负载甚至禁用其他密钥,加载数据重建密钥。

不确定在asynch中正在做什么,但是肯定它没有按照你自己的预期进行操作。

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 (…)
{
    …
}

答案 4 :(得分: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

答案 5 :(得分:0)

似乎执行你的SQL命令会锁定一些必需的资源,这就是强制你使用Async方法的原因(我猜)。

如果数据库未在使用中,请尝试独占访问它。即便如此,由于数据模型的复杂性,还有一些内部事务会考虑咨询数据库设计人员。