使用C#并发事务更新数量问题

3
我开发了一个在线购买我的产品的应用程序。我在商店里有一种产品“雨伞”,有100个。我已经开发了一个在线购买我的产品的应用程序。但是,当同时购买时会出现问题。
如果同时发生两次交易,AvailableQty将会更新不正确。假设有两笔交易同时发生,购买数量为100和50。理想情况下,第一笔交易(购买数量为100)应该成功,因为我们有100个库存可用。但第二笔交易应该返回错误,因为库存不足以处理,因为第一笔交易的余额为0(100-100)。但是以上情况下,这两笔交易都成功了,余额现在显示为-50。
当存在两个单独的事务时,它会正常工作。但是当这两个事务同时发生时就会出现问题。这个问题的原因是,在并发交易时,检查可用性的条件同时命中,此时条件得到满足,因为DB表还没有更新到最新的数量。
我该如何纠正这个问题?
public bool UpdateStock(int productId, int purchaseQty)
{
    using(var db = new MyEntities())
    {
       var stock = db.Products.Find(productId);

       if (stock.AvailableQty >= purchaseQty) // Condition to check the availablity
       {
            stock.AvailableQty = stock.AvailableQty - purchaseQty;
            db.SaveChanges();
            return true;
        }
        else
        {
            return false;
        }
    }
}

3
查找乐观并发和Entity Framework。 - TheGeneral
@TheGeneral 或悲观并发(锁定行)。 - Cihan Yakar
你有两个广泛的选择。a) 乐观并发,根据@TheGeneral的评论(https://learn.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application#add-an-optimistic-concurrency-property-to-the-department-entity)。b)使用直接SQL,如`UPDATE TABLE SET AvailableQty = AvailableQty - @purchaseQty WHERE AvailableQty >= @purchaseQty`,然后检查更新后的行数。 - mjwills
1个回答

2

这是一个典型的线程并发问题,可以通过多种方式解决,其中之一是使用简单的lock语句:

public class StockService
{
    private readonly object _availableQtyLock = new object();

    public bool UpdateStock(int productId, int purchaseQty)
    {
        using (var db = new MyEntities())
        { 
            lock (_availableQtyLock)
            {
                var stock = db.Products.Find(productId);
                if (stock.AvailableQty >= purchaseQty) // Condition to check the availablity
                {
                    stock.AvailableQty = stock.AvailableQty - purchaseQty;
                    db.SaveChanges();
                    return true;
                }
                return false;
            }
        }
    }
}

只有一个线程可以获得对_availableQtyLock的独占锁,这意味着其他线程必须等待第一个线程释放该对象上的锁。请注意,这是处理并发性最简单(也可能最慢)的方法,还有其他方法可以进行线程同步,例如MonitorSemaphore、快速SlimLock等等...由于很难确定哪种方法最适合您的需求,您需要进行适当的性能/压力测试,但我的建议是从最简单的方法开始。注意:正如其他人在评论中提到的那样,并发问题也可以在数据库层面上解决,这确实更合适,但如果您不想/不能引入任何数据库更改,则可以选择这种方式。

2
嗯,这似乎不太具有可扩展性。 - TheGeneral
我完全同意,但在这种情况下可扩展性不是必需的,对吧? - Darjan Bogdan
如果我们有100支股票,同时有两笔交易分别为50和30股,那么我应该看到的余额是20股吗? - user9657450
1
希望这个 StockService 没有多个实例。或者一个 Web Garden。或者一个 Web Farm。当数据库内置并发处理时,这似乎是一个奇怪的解决方案。 - mjwills
@mjwills 这就是为什么分布式锁存在的原因,无论如何我同意,但是当你不知道整个问题的范围时,很难给出适当的解决方案,不是吗? :) - Darjan Bogdan
显示剩余2条评论

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