解析和插入批量数据。如何保持业绩和做好关系?

Posted

技术标签:

【中文标题】解析和插入批量数据。如何保持业绩和做好关系?【英文标题】:Parsing and inserting bulk data. How to keep performance and do relations? 【发布时间】:2015-01-21 06:31:38 【问题描述】:

数据

我有一个包含大约 300,000 个假期的集合。每个假期都有几个类别、国家、城市、活动和其他子对象。此数据需要插入到 mysql / SQL Server 数据库中。每次运行解析器程序时,我都能够截断整个数据库并开始清理。

我的尝试

我尝试过使用实体框架,这也是我的偏好所在。为了保持实体框架的性能,我创建了一个结构,其中 300 个项目从假期集合中取出,由实体框架解析和插入,然后处理它的上下文。使用这种方法,程序在几分钟内完成。如果我用集合中的所有 300k 假期(以及它的子对象)填充上下文,这只是几个小时的问题。

int total = vacationsObjects.Count;          

for (int i = 0; i < total; i += Math.Min(300, (total - i)))

  var set = vacationsObjects.Skip(i).Take(300);
  int enumerator = 0;

  using (var database = InitializeContext())
  
   foreach (VacationModel vacationData in set)
   
      enumerator++;;

      Vacations vacation = new Vacations
      
         ProductId = vacationData.ExternalId,
         Name = vacationData.Name,
         Description = vacationData.Description,
         Price = vacationData.Price,
         Url = vacationData.Url,
      ;

      foreach (string category in vacationData.Categories)
      
         var existingCategory = database.Categories.Local.FirstOrDefault(c => c.CategoryName == categor);

         if (existingCategory != null)
            vacation.Categories.Add(existingCategory);
         else
         
            vacation.Categories.Add(new Category
            
              CategoryName = category
            );
         
      

      database.Vacations.Add(vacation);
   

   database.SaveChanges();
  

这种方法的缺点(可能是交易破坏者)是弄清楚关系。正如您在添加类别时看到的那样,我检查它是否已经在本地上下文中创建,然后使用它。但是,如果它已添加到之前的 300 组中呢?我不想为每个假期多次查询数据库以检查一个实体是否已经存在于其中。

可能的解决方案

我可以在内存中保留一本字典,其中包含已添加的类别。我需要弄清楚如何将这些类别附加到适当的假期(反之亦然)并将它们插入到数据库中,包括它们各自的关系。

可能的替代方案

分离上下文和事务 -

纯理论,我不知道我在这里是否有意义。也许我可以让 EF 的上下文跟踪所有对象,并手动控制插入部分。我已经搞砸了,试图使用手动事务范围无济于事。

存储过程 -

我可以编写一个存储过程来处理和插入我的数据。我不是这种替代方案的忠实拥护者,因为我想保持在 MySQL 和 SQL Server 之间切换的灵活性。另外,我不知道从哪里开始。

中间 CSV 文件 -

我可以将解析后的数据导出到一个或多个 CSV 文件中,并使用 MySQL 的 INFLINE 等导入工具,而不是将解析后的数据直接插入 RDMBS。

替代数据库系统

可以选择 Azure 表存储、MongoDB 或 RavenDB 等数据库。但是,由于与我的技能和工具的兼容性,我更愿意坚持使用传统的 RDMBS。

我已经研究了这个问题几个星期了。似乎找到适合的解决方案的最佳方法是简单地尝试不同的可能性并观察结果。我希望我能从你的个人经历中得到一些指导或提示。

【问题讨论】:

我只能从 MSSQL 方面给你我的观点,因为两年以来我没有在 MySQL 上从事 ETL 任务。 MSSQL 中的批量加载功能非常高效。我每天加载数百万行。使用 SPOC 批量插入或使用 SSIS(我建议插入数据),您可以轻松完成工作。如果您可以每天截断阶段表,则可以插入数据而不进行规范化。在第二步中,您可以标准化您的属性,例如类别或城市。在最后一步中,完成维度表的更新后,您可以将暂存数据处理成事实表。 【参考方案1】:

如果单独插入每条记录,整个操作会花费很多时间。瓶颈是客户端和服务器之间的 SQL 查询。每个查询都需要时间,因此请尽量避免使用多个查询。对于大量数据,在本地处理它们会好得多。最好的解决方案是使用特殊的导入工具。在 MySQL 中可以使用LOAD DATA,在 MSSQL 中可以使用BULK INSERT。要导入您的数据,您需要一个.css 文件。

要正确处理外部键,您必须在插入之前手动填充表。如果目标表为空,您可以简单地使用预定义的主键和外部键创建 .css 文件。否则,您可以从服务器导入现有记录,用您的数据更新它们,然后将它们导出回来。

【讨论】:

如果我每 300 个左右的对象保存和处理上下文,我可以将查询量保持在可接受的水平。当然,这只有在我找到一种以编程方式处理我的关系的方法时才可行。我将研究 LOAD DATA 和 BULK INSERT。与我发现的类似方法(中间 CSV 文件)相比,您知道它有什么明显的优势吗?【参考方案2】:

时间

由于您只能负担得起INSERTs,一个建议是尝试Entity Framework Bulk Insert extension。我用它最多可以保存 200K 条记录,而且效果很好。只需包含在您的项目中并编写如下内容:

context.BulkInsert(listOfEntities);

这应该解决(或大大改进 EF 版本)您的问题的时间维度

数据完整性

将所有内容保存在一个事务中听起来并不合理(我预计 300K 父记录会生成至少 3M 总记录),所以我会尝试以下方法:

1) 使用批量插入插入实体。

2) 调用存储过程检查数据完整性

如果插入的时间比较长,失败的几率比较大,可以加载已经加载的内容,让进程跳过已经加载的内容:

1) 为一批假期记录及其所有子记录进行较小的批量插入。确保它在事务中运行。一个 BULK INSERT 以原子方式运行(无需事务),用于多个 it seems tricky。

2) 如果流程失败,您的数据库中有完整的假期数据(没有部分导入的假期)

3) 重新执行该过程,但加载现有的假期记录(仅限父母)。使用 EF,更快的方法是使用 AsNoTracking 来节省跟踪开销(这对于大型列表非常有用)

var existingVacations = context.Vacation.Select(v => v.VacationSourceIdentifier).AsNoTracking();

【讨论】:

【参考方案3】:

正如 Alexei 所建议的,如果您的模型受此库支持,EntityFramework.BulkInsert 是一个非常好的解决方案。

您还可以使用Entity Framework Extensions(专业版),它允许使用 BulkSaveChanges 和批量操作(插入、更新、删除和合并)。

它支持你的两个提供商:MySQL 和 SQL Server

// Upgrade SaveChanges performance with BulkSaveChanges
var context = new CustomerContext();
// ... context code ...

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(operation => operation.BatchSize = 1000);

// Use direct bulk operation
context.BulkInsert(customers);

免责声明:我是项目的所有者Entity Framework Extensions

【讨论】:

以上是关于解析和插入批量数据。如何保持业绩和做好关系?的主要内容,如果未能解决你的问题,请参考以下文章

Sqlalchemy - 当数据存在关系时,如何正确地将数据批量插入数据库

企业安全之如何在保证业务的同时做好安全

如何挂在外部的配置文件到docker中的nginx

JDBC 批量插入:直接 insert values 数据和预编译的区别分析

mongodb数据库批量插入海量数据时为啥有少部分数据丢失

数据批量插入MSSQL