假设我有一个执行以下操作的 Controller 操作:

  • 检查在特定时间是否有日历槽
  • 检查是否已预订与该插槽
  • 重叠的约会
  • 如果同时满足两个条件,它将在给定时间创建一个新约会

  • 简单的实现存在多个问题:
  • 如果在步骤3之前删除了在1中获取的日历槽,该怎么办?
  • 如果在第2步之后但在第3步之前预定了另一个约会怎么办?

  • 解决这些问题的方法似乎是使用SERIALIZABLE事务隔离级别。问题在于,每个人似乎都认为此事务隔离级别非常危险,因为它可能导致死锁。

    给出以下简单的解决方案:
    public class AController
    {
        // ...
        public async Task Fn(..., CancellationToken cancellationToken)
        {
            var calendarSlotExists = dbContext.Slots.Where(...).AnyAsync(cancellationToken);
            var appointmentsAreOverlapping = dbContext.Appointments.Where(...).AnyAsync(cancellationToken);
            if (calendarSlotExists && !appointmentsAreOverlapping)
                dbContext.Appointments.Add(...);
            dbContext.SaveChangesAsync(cancellationToken);
        }
    }
    

    始终避免并发问题的最佳方法是什么?我应该如何处理最终的死锁?

    最佳答案

    数据库完整性检查是您最好的 friend

    根据您的描述,您的约会基于时段。这使问题变得更加简单,因为您可以在SlotId表上有效地为Appointments定义一个唯一约束。然后,您将需要一个用于Appointments.SlotId引用Slot.Id的外键



    DB会抛出外键冲突异常



    数据库将抛出重复的键异常

    接下来,您需要捕获这两个异常(exception)并将用户重定向回预订页面。再次从数据库重新加载数据,并检查是否有无效条目,通知用户进行修改,然后重试。

    对于死锁部分,它实际上取决于您的表结构。访问数据的方式,对数据进行索引的方式以及数据库的查询计划。没有确切的答案。

    10-08 14:21