EnableRetryOnFailure()是解决数据库死锁的有效方法吗?它会对性能产生负面影响吗?

10

我遇到了死锁问题,出现了以下异常:

System.InvalidOperationException: 出现了一个异常,可能是由于短暂故障引起的。请考虑通过在 'UseSqlServer' 调用中添加 'EnableRetryOnFailure()' 来启用短暂错误恢复。

为了复制问题,我甚至创建了第二个最简单的项目,只有几行代码,但也会出现相同的问题。它非常基础,只需要有两个引用不同实体的实体即可。在我看来,这是一种很常见的情况,EF Core 默认无法处理这种情况,并建议使用 EnableRetryOnFailure()

我的问题是:如果我们有一个大型应用程序,那么每秒钟可能有1000个用户触发重试,具体取决于他们使用会导致此类死锁的操作有多频繁。 这不会影响性能吗?这不会最终阻塞数据库吗?

重新创建此问题的简单代码:

这只是 API,所有代码都在控制器内部,以简化问题。数据库是由 EF Core(Code First 方法)生成的。它有两个控制器,具有接受包含要添加到数据库中的数据的 JSON 文件的端点。每当从文件添加数据时,都会保存有关文件的信息(文件名、日期和时间)。提交一个文件会导致许多带有数据的记录(取决于其中包含的内容)和一个带有文件名、日期和时间的记录(每个数据记录都具有相同的文件引用,因此所有记录都将具有相同的 InputFileId)。

您只需要同时向两个端点发送请求。或只需同时发送例如4个请求到同一个端点。我有2000个条目在 JSON 文件中,所以可能需要更长时间才能更容易地触发死锁。

用户控制器:

[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
    public MyDbContext DbContext { get; }
    public UsersController(MyDbContext dbContext)
    {
        DbContext = dbContext;
    }

    [HttpPost]
    public async Task<IActionResult> Import([FromForm]IFormFile file)
    {
        using var streamReader = new StreamReader(file.OpenReadStream());
        JsonSerializer serializer = new JsonSerializer();

        List<User> users = (List<User>)serializer.Deserialize(streamReader, typeof(List<User>));
        var inputFile = new InputFile
        {
            FileName = file.FileName,
            DateAdded = DateTime.Now
        };

        foreach (var user in users)
        {
            user.InputFile = inputFile;
        }

        await DbContext.AddRangeAsync(users);
        await DbContext.SaveChangesAsync();

        return Ok();
    }
}

产品控制器:

[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    public MyDbContext DbContext { get; }
    public ProductsController(MyDbContext dbContext)
    {
        DbContext = dbContext;
    }

    [HttpPost]
    public async Task<IActionResult> Import([FromForm]IFormFile file)
    {
        using var streamReader = new StreamReader(file.OpenReadStream());
        JsonSerializer serializer = new JsonSerializer();

        List<Product> products = (List<Product>)serializer.Deserialize(streamReader, typeof(List<Product>));
        var inputFile = new InputFile
        {
            FileName = file.FileName,
            DateAdded = DateTime.Now
        };

        foreach (var product in products)
        {
            product.InputFile = inputFile;
        }

        await DbContext.AddRangeAsync(products);
        await DbContext.SaveChangesAsync();

        return Ok();
    }
}

Db上下文:

public class MyDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<User> Users { get; set; }
    public DbSet<InputFile> InputFiles { get; set; }

    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
    {
    }
}

产品:

public class Product
{
    public long Id { get; set; }
    public string SerialNumber { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public long InputFileId { get; set; }
    public InputFile InputFile { get; set; }
}

用户:

public class User
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public long InputFileId { get; set; }
    public InputFile InputFile { get; set; }
}

输入文件:

public class InputFile
{
    public long Id { get; set; }
    public string FileName { get; set; }
    public DateTime DateAdded { get; set; }
}

Startup.cs,ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddDbContext<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
1个回答

9

这样做会不会最终造成数据库阻塞?

默认的EnableRetryOnFailure实现增加了重试之间的等待时间,所以通常可以直接使用,甚至不需要您进行调整。

这样做会影响性能吗?

不会影响性能。

如果我们有一个大型应用程序,每秒钟可能会有1000个用户触发重试,具体取决于他们使用导致死锁的某些操作的频率。

还要确保将READ_COMMITTED_SNAPSHOT设置为ON


更多信息请参见连接恢复。 还要查看SqlServerRetryingExecutionStrategyExecutionStrategy的实现。


2
"READ_COMMITTED_SNAPSHOT set to ON" 是什么意思? - WannabePuppetMaster
2
使用EnableRetryOnFailure选项可以生成重复项吗? - Akmal Salikhov
1
@AkmalSalikhov 对于“INSERT”操作,只要不使用更重的事务级别,在极少数情况下可能是可行的。实际上,对于大多数应用程序来说,除非这是像银行应用程序这样严肃的事情,否则通常并不重要(甚至可能根本不会发生),除非与数据库服务器的连接真的很差。对于那些罕见的应用程序,您需要测试这些情况以确保。如果需要,您始终可以额外努力并在重试时检查先前的“INSERT”是否成功。 - lauxjpn
@AkmalSalikhov 有关重试和事务的更多信息,请参阅官方文档中的连接弹性:执行策略和事务 - lauxjpn
2
@AkmalSalikhov 如需了解如何处理我在第一条评论中提到的特定情况(这是唯一可能导致罕见情况下出现重复条目的情况),请参阅官方文档中同一文章的连接弹性:事务提交失败和幂等问题部分。 - lauxjpn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接