我有以下问题:
我们的系统只有在发布时才允许购买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;
但我不太喜欢这两种解决方案。你怎么解决这个问题?
答案 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。 (警告,文章相当长,但信息量很大)。但我认为您的选择基本上是:
.Count()
。虽然你可能会发现最后一个选项很有趣,但这实际上是一个严肃的选择。修复这种并发问题很难,可能到处都是。它可能会迫使你做出重大的架构改变。此外,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;
}
}
}
}