将新记录插入特定表时出现死锁



我有一个后台任务(由IHostedService启动(,每7天运行一次,该任务将数据复制到同一个表中,只更改PK以生成伪数据(用于演示(。

问题是,当它试图为第二个表保存新数据(大约2k条新记录(时,实体框架从未完成SaveChangesAsync,该过程被阻止,并开始消耗整个可用RAM。

注意:第一个表有时会复制超过2万条新记录。

这是我目前的代码,我正在使用实体框架核心5.0.4。NET Core 3.1:

using (var context = _context.CreateNewInstance())
{
var existingStudents = context.Students.Where(s => s.UniversityId == _destUniversity.Id);
var sourceStudents = context.Students.Where(s => s.UniversityId == _sourceUniversity.Id)
.Select(s => new Student()
{
//...properties
});
var newStudents = sourceStudents.Where(s => !existingStudents.Any(es => es.DiffKey == s.DiffKey)).ToArray();
if (newStudents.Length == 0)
{
return;
}
await context.Students.AddRangeAsync(newStudents);
await context.SaveChangesAsync(_cancellationToken.Token);
}

我已经试过了:

  1. 禁用";"自动检测变化">
  2. 批量插入:这只适用于第一批
  3. 使用IEnumerable而不是IQueryable
  4. 任务中所有表的单个上下文
  5. 每个表的新上下文实例
  6. 比较数据库/表的配置,并将其复制到本地

我有2台服务器(1台本地服务器,1台远程服务器(和3个数据库(1个本地数据库,2个远程数据库(,代码适用于三个数据库中的两个数据库(一个本地数据库和一个远程数据库。

我用这段代码在SQL:上获得了任务的spid

int psid;
using (var command = contextTmp.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "select @@spid as id";
contextTmp.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
result.Read();
psid = result.GetInt16("id");
}
contextTmp.Database.CloseConnection();
}

使用上一段代码中的spid,我监视了SQL Server端的请求,以检查其状态:

select session_id, 
status, 
command,
blocking_session_id,
wait_type, 
wait_time,
last_wait_type,
wait_resource
from sys.dm_exec_requests 
where session_id = @id

结果:

命令>等待时间选择ASYNC_NETWORK_IO
session_id状态blocking_session_id等待时间
84已挂起0

Entity Framework不擅长批量插入东西。如果实体有一个自动生成的主键,那么在每次插入后,EF都会查询数据库以获取条目的id。这意味着,对于20K条记录中的每一条,都必须等待数据库的往返时间。这就是为什么当你调用SaveChanges()时,一切似乎都停止了——它仍然在运行,但需要很长时间。有几种方法可以解决这个问题:

  • 更改要由客户端生成的id
  • 使用像EntityFrameworkPlus这样的扩展库来执行大容量插入
  • 使用SqlBulkCopy而不是实体框架
  • 使用存储过程

理想情况下,您的查询不需要将数据传输到客户端,只需插入FROM即可。我建议使用第三方扩展linq2db。EntityFrameworkCore(免责声明,我是创建者之一(

然后您的查询将几乎立即在服务器端执行:

using (var context = _context.CreateNewInstance())
{
var existingStudents = context.Students.Where(s => s.UniversityId == _destUniversity.Id);
var sourceStudents = context.Students.Where(s => s.UniversityId == _sourceUniversity.Id)
.Select(s => new Student()
{
//...properties
});
var newStudents = sourceStudents.Where(s => !existingStudents.Any(es => es.DiffKey == s.DiffKey));
await newStudents.InsertAsync(context.Students.ToLinqToDBTable(), x => x, _cancellationToken.Token);
}

最新更新