将新记录插入特定表时出现死锁
Posted
技术标签:
【中文标题】将新记录插入特定表时出现死锁【英文标题】:Deadlock when inserting new records to a specific table 【发布时间】:2021-06-25 19:55:59 【问题描述】:我有一个每 7 天运行一次的后台任务(由 IHostedService
启动),该任务将数据复制到同一个表中,仅更改 PK 以生成虚拟数据(用于演示)。
问题在于,当它尝试为第二个表保存新数据(大约 2k 条新记录)时,实体框架永远不会完成 SaveChangesAsync
,此过程被阻止并开始消耗整个可用 RAM。
注意:第一个表有时会复制超过 2 万条新记录。
这是我当前的代码,我正在使用 Entity Framework Core 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);
我已经尝试了所有这些:
-
Disabling "Auto Detect Changes"
Batch insert:这只适用于第一批。
Use
IEnumerable
instead of IQueryable
任务中所有表的单一上下文
每个表的新上下文实例
比较数据库/表的配置并复制到本地
我有 2 个服务器(1 个本地,1 个远程)和 3 个数据库(1 个本地,2 个远程),代码适用于三个数据库中的两个(1 个本地和 1 个远程)。
我使用这段代码在 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
结果:
session_id | status | command | blocking_session_id | wait_type | wait_time | last_wait_type | wait_resource |
---|---|---|---|---|---|---|---|
84 | suspended | SELECT | 0 | ASYNC_NETWORK_IO | 35 | ASYNC_NETWORK_IO |
我已经阅读了有关此问题的博客,但我还不明白,我想我错过了一些东西。我没有主意了。
其他来源:
DBA stackexchange Understanding how SQL Server executes a query SQL Server ASYNC Network IO Wait Type Database Connection Hazards with Entity Framework更新(1):
在我们从 .NET Core 2.2 更新到 3.1 之前,所有功能都运行良好。升级后我们开始看到奇怪的行为。
有什么建议吗?
【问题讨论】:
仅供参考 - 这不是死锁。直接挡住。通过回滚一个进程,死锁将很快得到解决。 好点,谢谢。那么,它是什么? 正如我所说,这是直接阻塞。 哦,好吧,这对我来说是新的,让我读一些关于它的东西 我认为关键在于批量工作,第一次。任何尝试都可能执行单个存储的命令?该方法可能在执行后没有正确“关闭”,问题是实体框架仍然认为该表正在处理,从而阻止另一个异步进程进行更改。尝试在没有异步的情况下进行存储,看看这是否不能解决您的问题,然后您就可以确定是否可以。 【参考方案1】:Entity Framework 不擅长批量插入东西。如果一个实体有一个自动生成的主键,那么在每次插入之后,EF 都会查询数据库以获取条目的 id。这意味着对于每条 20K 记录,您必须等待数据库的往返时间。这就是为什么当您致电 SaveChanges()
时一切似乎都停止工作的原因 - 它仍在运行,但需要很长时间。有几种方法可以解决这个问题:
【讨论】:
我把表格的顺序改了,现在第二张是第一张,这样做是为了避免插入20k条记录,但是问题依旧 我不确定你的意思。您还在将newStudents
添加到Students
表中吗?如果是这样,您要添加多少条记录?这是您原始问题中的核心问题,因此如果情况发生了变化,您需要使用更多详细信息更新问题
哦,对不起,让我解释一下,我正在为许多表插入数据,学生是第二个,第一个表有时会插入超过 20k 但并非总是如此(这样我没有问题),所以我改变了表格的顺序,现在我将newStudents
添加到Students
,然后再尝试将其他内容插入其他表格。
CDC 是问题的一部分,如果我禁用它,一切正常,但它是必需的,所以我更改了要在客户端生成的 Id(和其他属性)并解决了现在的问题。谢谢,伙计。【参考方案2】:
理想情况下,您的查询不需要将数据传输到客户端,只需 INSERT 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);
【讨论】:
请注意,只有在sourceStudents
不可翻译为SQL 函数的投影中不使用它才会起作用。以上是关于将新记录插入特定表时出现死锁的主要内容,如果未能解决你的问题,请参考以下文章
将记录插入 phpmyadmin 时出现 Asynctask 错误