2016-11-15 107 views
1

我有这样的代码,我在StackOverflow发现:更新数据库运行速度非常慢

public static class EntityFrameworkUtil 
{ 
    public static IEnumerable<T> QueryInChunksOf<T>(this IQueryable<T> queryable, int chunkSize) 
    { 
     return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk => chunk); 
    } 

    public static IEnumerable<T[]> QueryChunksOfSize<T>(this IQueryable<T> queryable, int chunkSize) 
    { 
     int chunkNumber = 0; 
     while (true) 
     { 
      var query = (chunkNumber == 0) 
       ? queryable 
       : queryable.Skip(chunkNumber * chunkSize); 
      var chunk = query.Take(chunkSize).ToArray(); 
      if (chunk.Length == 0) 
       yield break; 
      yield return chunk; 
      chunkNumber++; 
     } 
    } 
} 

我用这个像这样:

int counter = 0; 
    foreach (var chunk in _sdb.Posts.OrderBy(c => c.Id).QueryChunksOfSize(100)) 
     { 
      foreach(var post in chunk) 
      { 
       post.Floor= CorrectFloor(post); 
       post.RealAge= AgeCalculator(post); 
       post.Area= TerasCheck(post); 

       _sdb.Entry(post).State = System.Data.Entity.EntityState.Modified; 
      counter++; 
      } 


     _sdb.SaveChanges(); 
     Console.WriteLine("SaveChanges worked, counter : " + counter); 

    } 

此代码应该更新300000行。代码起初运行速度很快,但现在它运行速度非常缓慢,并且在第30000行。你能告诉我为什么这个代码运行速度很慢,我怎样才能让它更快?谢谢。

+0

我想这就是db规范化问题。但通常情况下,您可以通过调用循环外的保存更改来提高性能。 – Emad

+0

@Emad为整个数据库调用SaveChanges是个好主意吗? – jason

+0

我认为'SaveChanges()'目前处于正确的位置,因为它在每100个项目后都会保存(尽管我会将它提升到1000)。我怀疑减速与更改跟踪有关。我不是EF的专家,但我认为使用'AsNoTracking()'会有所帮助。在第一个foreach中_sdb.Posts.AsNoTracking()。OrderBy(c => c.Id).QueryChunksOfSize(100))'中的var chunk。 –

回答

1

我提出以下建议:

  1. 使用IQueryable<T>而不是IEnumerable<T>。使用IEnumerable<T>将最终导致对数据库执行的许多查询将会降低整体性能。此外,OrderBy子句在这里没有意义,因为您想要更新所有帖子。
  2. 调用SaveChanges一次。你的例子将会提交每100个元素的更改。
  3. 评论您的DataContext配置,以避免惰性加载(包括要加载的特性)和变化的跟踪(你可以叫AsNoTracking()的提议也):

    this.Configuration.LazyLoadingEnabled = false; this.Configuration.AutoDetectChangesEnabled = false; this.Configuration.ProxyCreationEnabled = false;

如果此过程需要很长时间才能执行,请确保在async函数中执行该过程。总结,这样的事情:

int counter = 0; 
foreach (var post in _sdb.Posts.Include("xxx")) 
{ 
    post.Floor = CorrectFloor(post); 
    post.RealAge = AgeCalculator(post); 
    post.Area = TerasCheck(post); 

    _sdb.Entry(post).State = System.Data.Entity.EntityState.Modified; 
    counter++; 
} 

_sdb.SaveChanges(); 
Console.WriteLine("SaveChanges worked, counter : " + counter); 
+0

我认为OrderBy是必要的。否则,您无法保证每个查询都具有相同的顺序(它几乎肯定会这样,但查询计划*可能会更改)。另外,如果id是一个标识列,那么我们知道任何新记录将出现在最后。这可以防止获取100条记录,然后在此之前插入一条新记录。 –