【问题标题】:Console Application slowing down over time控制台应用程序随时间变慢
【发布时间】:2017-02-10 09:57:19
【问题描述】:

我正在尝试导入大约 900K 行数据并将其映射到我的新数据模型。 我的问题是我为此导入功能构建的控制台应用程序会随着时间的推移而变慢。

我已经监控了 SQL 查询,它们都表现良好(

  • 计数:100 |平均毫秒数:36
  • 计数:200 |平均毫秒数:67
  • 计数:300 |平均毫秒数:106
  • 计数:400 |平均毫秒数:145
  • 计数:500 |平均毫秒数:183
  • 计数:600 |平均毫秒数:222
  • 计数:700 |平均毫秒数:258
  • 计数:800 |平均毫秒数:299
  • 计数:900 |平均毫秒数:344
  • 计数:1000 |平均毫秒数:376

使用 1K 行的新块重新启动应用程序时,时间相似。

导入数据格式如下:

public class ImportData
{
    public int Id { get; set; }
    public int TaxpayerId { get; set; }
    public string CustomerName { get; set; }
    public string Email { get; set; }
    public string PhoneNumber { get;set; }
}

我的数据模型的简化示例如下所示:

public class Channel
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Permission
{
    public Guid Id { get; set; }
    public Channel Channel { get; set; }
    public string Recipient { get; set; }
}

public class Taxpayer
{
    public Guid Id { get; set; }
    public int TaxpayerId { get; set; }
    public string Name { get; set; }
    public List<Permission> Permissions { get; set; }        
}

我的导入方法如下:

public void Import()
{
    Stopwatch stopwatch = new Stopwatch();

    //Get import data
    List<ImportData> importDataList = _dal.GetImportData();

    stopwatch.Start();

    for (int i = 0; i < importDataList.Count; i++)
    {
        ImportData importData = importDataList[i];

        Taxpayer taxpayer = new Taxpayer()
        {
            Name = importData.CustomerName,
            TaxpayerId = importData.TaxpayerId,
            Permissions = new List<Permission>()
        };
        //Does not call SaveChanges on the context
        CreateTaxpayer(taxpayer, false); 

        //Create permissions
        if (!string.IsNullOrWhiteSpace(importData.Email))
        {
            //Does not call SaveChanges on the context
            CreatePermission(_channelIdEmail, importData.Email, taxpayer, PermissionLogType.PermissionRequestAccepted);
        }
        if (!string.IsNullOrWhiteSpace(importData.PhoneNumber))
        {
            //Does not call SaveChanges on the context
            CreatePermission(_channelIdPhoneCall, importData.PhoneNumber, taxpayer, PermissionLogType.PermissionRequestAccepted);
            //Does not call SaveChanges on the context
            CreatePermission(_channelIdSms, importData.PhoneNumber, taxpayer, PermissionLogType.PermissionRequestAccepted);
        }

        if ((i + 1) % 100 == 0)
        {
            Console.WriteLine("Count: " + (i + 1) + " | Avg ms: " + stopwatch.ElapsedMilliseconds / 100);
            stopwatch.Restart();
        }
    }
    _dal.SaveChanges();
}

我尝试了以下方法:

  • 减少对 SaveChanges 的调用次数(最后只调用一次)
  • 实现多线程(运气不好) - 它似乎与实体框架并不一致

我的想法已经不多了。你们对解决这个性能问题有什么建议吗?

【问题讨论】:

  • 请创建一个minimal reproducible example。您显示的代码没有明显的问题。我怀疑您在某处执行 Linq(对实体或对象)查询,当您将项目添加到集合时,该查询会逐渐变慢。
  • 不要使用 ORM。使用 SqlBulkCopy。 ORM 不适用于批量操作,就像使用镊子移动一卡车的岩石一样。您最终会跟踪 所有 记录,为 每个 插入发送单独的 INSERT 请求。您可以禁用更改跟踪并添加一个扩展,将批处理添加到 EF,以便一起发送多个请求,但使用 EF 而不是 SqlBulkCopy,您仍然一无所获。
  • 您需要为每次迭代处理您的 _dal 对象(或者更好的是,为每次迭代使用一个新对象) - 还可以通过在 app.config 中使用 gcServer 来提高性能 - 但最好的方法是不要使用EF,但 SqlBulkCopy
  • 对上下文对象的更改、跟踪和批处理只会带来百分比收益。 SqlBulkCopy 将导致数量级的改进。不仅BULK INSERT 在行流上的速度要快得多,而且它还使用最少的日志记录。事务日志不会记录每一个 INSERT 语句。它只记录足够的数据以在需要时回滚。这会导致 LOT 更少的磁盘 IO、日志使用和碎片

标签: asp.net sql-server performance entity-framework console-application


【解决方案1】:

您为什么不使用 BulkCopy,此代码需要针对您的特定表和列进行修改,但希望您能明白:

using (var bulkCopy = new SqlBulkCopy(_DbContext.Database.Connection.ConnectionString, SqlBulkCopyOptions.TableLock))
            {
               bulkCopy.BulkCopyTimeout = 1200; // 20 minutes
                bulkCopy.BatchSize = 10000;
                bulkCopy.DestinationTableName = "TaxPayer";

                var table = new DataTable();
                var props = TypeDescriptor.GetProperties(typeof(TaxPayer))                                     
                    //Dirty hack to make sure we only have system data types                                      
                    //i.e. filter out the relationships/collections
                    .Cast<PropertyDescriptor>()
                    .Where(propertyInfo => propertyInfo.PropertyType.Namespace.Equals("System"))
                    .ToArray();
                foreach (var propertyInfo in props) 
                { 
                    bulkCopy.ColumnMappings.Add(propertyInfo.Name, propertyInfo.Name); 
                    table.Columns.Add(propertyInfo.Name, Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType); 
                }

                // need to amend next line to account for the correct number of columns
                var values = new object[props.Length + 1];
                foreach (var item in importDataList) 
                { 
                    for (var i = 0; i < values.Length - 1; i++) 
                    { 
                        ///TODO: Decide which columns need including
                        values[i] = props[i].GetValue(item);
                    }
                    table.Rows.Add(values);
                }

                bulkCopy.WriteToServer(table);
            }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-25
    • 2010-11-04
    相关资源
    最近更新 更多