通过分页遍历所有记录有一个大约有7650000条记录的表,我想遍历所有记录来做一些处理。我使用实体框架查询并使用服务器端分页的概念
int totalBatches= 7650;
int batchSize =1000;
for (int i = 0; i <= totalBatches+1; i++)
{
int firstId = 0, lastDbId = 0;
Console.WriteLine($"Processing Batch {i.ToString()} of {totalBatches}");
var batch = context.TableName
.Include(x => x.RelatedTable)
.OrderBy(x => x.Id)
.Skip(i).Take(batchSize).ToList();
if (batch.Count() <= 0)
{
Console.WriteLine($"SKIPPING LOOP SINCE NO RECORDS EXISTS");
break;
}
foreach (var entry in batch)
{
//processing each records
}
}
看起来执行完成,没有任何错误,但当我检查处理记录的摘要时,记录上有一些不匹配。我花了大约6个多小时来完成脚本的执行。数据库是Mysql,我使用Pomelo.EntityFrameworkCore.MySql驱动程序作为依赖项
所以想知道分页逻辑是否正确,是否有更好的方法来进行服务器端分页?或者这种方法是否会导致数据丢失?
1条答案
按热度按时间5cnsuln71#
有两种方法是缓慢的。
Skip(a).Take(b)
很慢,因为它实际上迫使DB准备a + b
行来发送,然后忽略a
行,这使得后续查询越来越慢,导致O(n^2)复杂度。不幸的是,为了使这个DB操作是O(n),你需要一种不同的分页方式。这是一个很好的阅读主题:https://learn.microsoft.com/en-us/ef/core/querying/paginationINotifyPropertyChanging
、INotifyPropertyChanged
,并且使您的DBContext不使用快照 *,则适用。1.开始交易
1.获取批处理
1.批量处理实体
1.调用
.SaveChanges()
1.重复3和4。直到你检查完所有的实体
1.结束交易
默认情况下,
DBContext
必须跟踪它提供给您的所有实体(在您的情况下是通过.ToList()
)。不幸的是,DBContext
无法知道您何时完成对任何特定实体的更改,因此它必须跟踪每个实体。因此,这些实体的内部集合只会变得更大,每次调用SaveChanges()时,它都必须检查当前批次中的实体以及所有先前批次中的所有实体的更改,导致检查更改的复杂度为O(n^2),内存复杂度为O(n)。!)
为了解决这个问题,您可以在每个
context.SaveChanges();
之后调用context.ChangeTracker.Clear();
,以有效地告诉上下文您不会对前一批中的实体进行任何更改。作为第三种选择,如果实体处理本身很简单(比如只是向所有行的列添加一些数字),并且您使用的是最近版本的EF,则可以尝试使用批量更新,使整个事情在DB上运行。阅读
ExecuteUpdate
:https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew