ASP.NET:在业务逻辑中具有竞争条件的问题

时间:2010-04-27 11:52:30

标签: c# asp.net mutex race-condition

我有以下问题:

我们的系统只有在发布时才允许购买X次产品。购买时,中央采购算法会检查存在多少订单,如果低于X则继续购买。

在伪C#代码中:

public class OrderMethods
{

    public static Purchase(Product product, Client client)
    {

        int purchases = /* count order records of this product */;

        if(purchases>=MAX_ORDERS) throw PurchaseException();

        /* perform purchase by inserting order record in database */

     }

}

问题在于,有时当对某种产品有很高的需求时,很多请求会同时发生,并且会有超过MAX_ORDERS的注册。这种情况大约每年发生一次:(。

解决此问题的最佳解决方案是什么?我正在使用ASP.NET / C#,Ling2SQL和MSSQL。我们有1000>每天订单。订单按照请求顺序处理非常重要。

我到目前为止所提出的解决方案:

  • 一个全球互斥?

  • 每个产品的一个互斥锁存储在哈希表中,其访问函数如下:

    private Mutex GetPurchaseMutex(Guid productId)
    {
    
    if (mutexTbl[productId] == null)
        {
            mutexTbl[productId] = new Mutex();
        }
        return (Mutex)mutexTbl[productId];
    }
    

其中mutexTbl是Hashtable。在这里,我还没弄清楚如何以一种很好的方式丢弃旧的互斥锁。

  • 在Order表上使用T-SQL INSERT Trigger检查有多少订单:

    创建TRIGGER Triggers_OrderInsertTrigger ON订单 插入后 如 IF / *检查是否有许多订单* / BEGIN RAISERROR('太多订单',16,1); ROLLBACK交易; 返回 END;

但我不太喜欢这两种解决方案。你怎么解决这个问题?

3 个答案:

答案 0 :(得分:4)

我会说当这个逻辑可以用事务保护时,将这个逻辑移到数据库层。

检查已下达的要约数量,以及是否可以在同一交易中下新订单。在此期间,新请求将暂停其事务(查询已下订单的数量),直到第一个完成。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

BEGIN TRANSACTION

DECLARE @OrderCount int
SELECT @OrderCount = COUNT (ID) FROM [Order] WHERE ProductID = '234323'

IF (@OrderCount < @Threshold)
BEGIN
    INSERT INTO [Order] (ID, ProductID, ...)
    VALUES (NEWID(), '234323', ...)

    COMMIT TRANSACTION
    RETURN 0
END
ELSE
    ROLLBACK TRANSACTION
    RETURN 1
END

答案 1 :(得分:3)

这个问题听起来很像我描述的“仅有两个未发货的订单”问题here。 (警告,文章相当长,但信息量很大)。但我认为您的选择基本上是:

  • 将完整的业务逻辑(包括验证)放入数据库中(就像Developer Art所写)。
  • 使用可序列化的交易,并在您的业务层(包括.Count()
  • 中使用C#编写此逻辑
  • 什么都不做,让它每年失败一次,然后清理乱七八糟。

虽然你可能会发现最后一个选项很有趣,但这实际上是一个严肃的选择。修复这种并发问题很难,可能到处都是。它可能会迫使你做出重大的架构改变。此外,COUNT(*)解决方案的有效性取决于该特定表中的索引。在数据库中添加索引(出于性能原因)可能会意外地改变此解决方案的有效性和正确性。因此,从业务角度来看,每年一次在数据库中修复问题可能要便宜得多。当然,我无法猜出成本是多少,每次客户能够购买的订单多于您的业务所需的订单。这取决于您(或您的公司)确定。

答案 2 :(得分:1)

如果您不想在数据库级别处理此问题,我会创建一个类来存储每个产品的购买计数,将这些计数存储在表中,并将其锁定在您的购买方法中。这与您的互斥方法类似,但您允许.NET为您处理锁定

public class PurchaseCounter(
{
    public Guid Product {get; set; } 
    public int MaxOrders {get; set; } 
    public int int CurrentOrders {get; set; } 
 }

public static bool Purchase(Product product, Client client)
{

    PurchaseCounter counter = purchaseCounterDictionary[product.ProductGuid];

    lock(counter)
    {
        if( counter.CurrentOrders < counter.MaxOrders )
        {
             //do logic to place order
             counter.CurrentOrders++;
             return true;  
        } 
        else
        {
             return false;
        }
    }

 }

}