通过分页遍历所有记录 有一张表大约有 7650000 条记录,我想遍历所有记录 做一些处理。我正在使用 Entity Framework Query 并使用服务器端分页的概念 查询看起来像
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 驱动作为依赖
所以想知道分页逻辑是否正确 有没有更好的方法来进行服务器端分页?还是这种方法会导致数据丢失?
这种方法有两种变慢的方式。
Skip(a).Take(b)
慢是因为它实际上强制 DB 准备 a + b
行发送,然后忽略 a
行,这使得后续查询越来越慢,导致 O(n^2) 复杂度。不幸的是,要使此 DB 操作成为 O(n),您需要一种不同的分页方式。这是关于该主题的非常好的读物:https://learn.microsoft.com/en-us/ef/core/querying/pagination
这是假设您使用的是检测更改的默认方式,如果您没有为您的实体实施
INotifyPropertyChanging
,INotifyPropertyChanged
并且使您的 DBContext 不使用快照.
这还假设您的代码执行此操作:
.SaveChanges()
默认情况下,
DBContext
必须跟踪它提供给您的所有实体(在您的情况下通过.ToList()
)。 DBContext
不幸的是无法知道您何时完成对任何特定实体的更改,因此它必须跟踪 every single entity.
因此,这些实体的内部集合只会越来越大,每次调用 SaveChanges() 时,它都必须检查当前批次中的实体以及所有先前批次中的所有实体 ,导致检查更改的 O(n^2) 复杂度以及 O(n) 内存复杂度 (!!)
为了解决这个问题,您可以在每个
context.ChangeTracker.Clear();
之后调用 context.SaveChanges();
以有效地告诉上下文您不会对前一批中的实体进行任何更改。
作为第三种选择,如果实体处理本身很简单(比如只在所有行的列中添加一些数字)并且您使用的是最新版本的 EF,您可以尝试使用批量更新来使整个事情运行数据库。在此处阅读有关
ExecuteUpdate
的信息:https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew