Deadlock on deleting separate entities in parallel
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
Hi all,
I'm running into a deadlock when I'm deleting separate entities in parallel. From my understanding, this should work without any issues.
This might be related to #15180 or #14371 but they have already been fixed, and the changes should be in the version I'm using (6.0.6)
While I'm struggling to understand exactly what's causing the issue (my knowledge of SQL locks is pretty bad 😅), it appears to be related to the cascading deletes that are configured automatically. Disabling the cascading deletes seems to help, however, I can't always disable the cascading deletes since I have owned entities where I can't configure the delete behavior.
Please let me know if you need any more information.
### Code
```C#
using Microsoft.EntityFrameworkCore;
namespace DeadlockInvestigation;
public class Blog
{
public Guid Id { get; init; }
public ICollection Posts { get; } = new List();
public ICollection Users { get; } = new List();
}
public class Post
{
public Guid Id { get; init; }
public Guid BlogId { get; init; }
}
public class User
{
public Guid Id { get; init; }
public Guid BlogId { get; init; }
}
public class DeadlockContext : DbContext
{
public DbSet Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
"Data Source=.;Initial Catalog=DeadlockDatabase;Integrated Security=True");
}
}
public class Program
{
static async Task Main()
{
await using (var db = new DeadlockContext())
{
await db.Database.EnsureDeletedAsync();
await db.Database.EnsureCreatedAsync();
}
for (int i = 0; i < 5; i++)
{
var createdBlogs = new List();
await using (var db = new DeadlockContext())
{
var blogWIthPost = new Blog();
blogWIthPost.Posts.Add(new Post());
db.Blogs.Add(blogWIthPost);
var blogWithUser = new Blog();
blogWithUser.Users.Add(new User());
db.Blogs.Add(blogWithUser);
await db.SaveChangesAsync();
createdBlogs.Add(blogWIthPost.Id);
createdBlogs.Add(blogWithUser.Id);
}
await Parallel.ForEachAsync(createdBlogs, async (blog, _) =>
{
await using (var db = new DeadlockContext())
{
var dbBlog = await db.Blogs
.Include(b => b.Posts)
.Include(b => b.Users)
.SingleAsync(b => b.Id == blog);
db.Blogs.Remove(dbBlog);
await db.SaveChangesAsync();
}
});
Console.WriteLine($"Iteration {i} was successful");
}
Console.WriteLine("All iterations were successful");
}
}
```
### Stack trace
```
Unhandled exception. System.InvalidOperationException: An exception has been raised that is likely due to a transient failure. Consider enabling transient error resiliency by adding 'EnableRetryOnFailure' to the 'UseSqlServer' cal
l.
---> Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): Transaction (Process ID 60) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
at Microsoft.Data.SqlClient.SqlCommand.<>c.b__188_0(Task`1 result)
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
ClientConnectionId:9d47ea57-6524-4452-9f44-4077c91a4d34
Error Number:1205,State:53,Class:13
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at DeadlockInvestigation.Program.<>c.<b__0_0>d.MoveNext() in C:\Users\yashanf\Code\DeadlockInvestigation\DeadlockInvestigation\Program.cs:line 82
--- End of stack trace from previous location ---
at DeadlockInvestigation.Program.<>c.<b__0_0>d.MoveNext() in C:\Users\yashanf\Code\DeadlockInvestigation\DeadlockInvestigation\Program.cs:line 83
--- End of stack trace from previous location ---
at System.Threading.Tasks.Parallel.<>c__50`1.<b__50_0>d.MoveNext()
--- End of stack trace from previous location ---
at DeadlockInvestigation.Program.Main() in C:\Users\yashanf\Code\DeadlockInvestigation\DeadlockInvestigation\Program.cs:line 72
at DeadlockInvestigation.Program.()
```
### Deadlock report

```xml
unknown
unknown
(@p1 uniqueidentifier)SET NOCOUNT ON;
DELETE FROM [Blogs]
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;
unknown
unknown
(@p1 uniqueidentifier)SET NOCOUNT ON;
DELETE FROM [Blogs]
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;
```
### Provider and version information
Database provider: `Microsoft.EntityFrameworkCore.SqlServer` - 6.0.6
Target framework: .NET 6.0
Operating system: Windows 10 Pro x64 (19044)
SQL Server: Developer (64-bit) 15.0.2080.9
IDE: JetBrains Rider 2021.3.2
Contributor guide
Assessment
This issue has not been assessed yet.