我正在努力解决库存分配和并发性这一经典问题,我想知道在这种情况下是否有人可以指导我进行最佳实践。
我的情况是,我们准备了一个带有几个“槽位”的订单,这些订单将在流程的某个阶段由唯一的库存物料填充,此时,我要确保没有人将相同的唯一单位分配给一个广告位的顺序不同。例如,用户下周四想要一辆面包车,因此我保留了一个“面包车”插槽,但在稍后的时间点,我从院子中分配了一个特定的车辆到该插槽。我要确保两个不同的运营商不能在下周四将同一辆面包车分配给两个不同的客户。
我们已经有一个库存可用性检查过程,我们比较一个日期范围内的两个表的汇总,这两个表的总和(一个是项入,另一个是项出)的结果告诉我是否有特定的我想在该日期分配给该广告位的商品,但我想防止其他用户在同一时间将同一商品分配给自己的广告位。
我已经在该站点上进行了一些谷歌搜索和研究,看起来我需要一个“悲观锁定”解决方案,但是我不确定如何有效地将其部署到位。
将从带有实体框架的Web API(使用.Net的REST API)中调用分配过程,我已经考虑了以下两种解决方案:
选项1-由数据库处理
在分配时,我开始交易并在用于评估库存可用性的两个表上获得排他锁。
该过程将确认库存可用性,将单元分配到插槽中,然后释放锁。
我认为这可以防止两个用户试图将同一唯一单位分配给两个不同订单的竞争状况,但是我不满意为需要查询这些表的所有其他进程锁定两个表,直到分配过程完成为止我认为这可能会导致尝试读取这些表的其他进程遇到瓶颈。在这种情况下,我认为尝试执行重复分配的第二个进程应该排在队列中,直到第一个释放锁,因为它无法查询可用性表,并且在这样做时将使可用性检查失败并报告缺货警告-这样可以有效地阻止第二个订单分配相同的库存。
在纸上这听起来像可行,但我有两个担忧。首先是它将影响性能,其次是我忽略了某些东西。另外,我是第一次在这个项目上使用Postgres(我通常是SQL Server专家),但我认为Postgres仍然具有执行此操作的功能。
选项2-使用某种手动锁定
我认为我的情况是在音乐会或电影院的销售过程中会遇到票务网站的问题,我已经看到他们设立了计时器,说诸如“您的票证将在5分钟后过期”之类的事情,但我不知道如何他们在后端实施这种系统。他们是否在分配过程以某种到期时间开始之前创建“保留”库存表,然后将试图分配相同单位的其他用户“列入黑名单”,直到该计时器到期?
很抱歉,很长的介绍,但是我想完全解释这个问题,因为我已经看到了很多有关类似情况的问题,但是没有什么真正帮助我决定如何进行的。
我的问题是,这两个选项(如果有)中的哪一个是“正确方法”?
编辑:与我所见到的问题最接近的是How to deal with inventory and concurrency,但它没有讨论选项1(可能是因为这是一个糟糕的主意)
答案 0 :(得分:3)
我认为选项2经过一些调整会更好。
这是我必须处理的情况
答案 1 :(得分:0)
我不确定您的数据库的布局方式,但是如果每个库存项目都是数据库中自己的记录,则表上只有一个IsUsed标志。当您更新记录时,只需确保将IsUsed = 0作为where子句的一部分即可。如果修改的总数返回为0,那么您知道其他更新了它。
答案 2 :(得分:0)
有多种方法可以解决此问题,我只是在回答自己的想法,并最终决定何时为客户解决此问题。
1。。如果在这些资源上的INSERT和UPDATE上的通信量不大,则可以通过在存储过程中执行类似的操作来完全锁定表,但是也可以这样做在简单的客户端代码中:
CREATE PROCEDURE ...
AS
BEGIN
BEGIN TRANSACTION
-- lock table "a" till end of transaction
SELECT ...
FROM a
WITH (TABLOCK, HOLDLOCK)
WHERE ...
-- do some other stuff (including inserting/updating table "a")
-- release lock
COMMIT TRANSACTION
END
2。。通过使代码获取自己创建的锁来使用悲观锁。放入要锁定的额外表pr资源类型,并在要锁定的资源的ID上设置唯一约束。然后,通过尝试插入一行来获取锁,然后通过删除它来释放锁。盖上时间戳记,以便您可以清理丢失的锁。该表可能如下所示:
Id bigint
BookingId bigint -- the resource you want to lock on. Put a unique constrain here
Creation datetime -- you can use these 2 timestamps to decide when to automatically remove a lock
Updated datetime
Username nvarchar(100) -- maybe who obtained the lock?
通过这种方法,很容易确定需要锁定的代码以及不带锁定即可读取资源和保留表的代码段。
3。。如果这是开始时间和结束时间分配的资源,则可以将此时间间隔的粒度设置为例如15分钟。一天中的每个15分钟时隙都将获得一个从0开始的数字。然后,您可以在预订表旁边创建一个表,其中开始和结束时间戳现在由该时隙的数字组成。选择一个合理的开始时间戳作为数字0。然后,您将为每个预订插入任意数量的具有不同时隙号的行。当然,您需要对“ Timeslot” +“ ResourceId”具有唯一的约束,以便任何插入(如果已为该时隙保留)将被拒绝。 更新该表可以很好地在表上的触发器中进行保留,这样您就可以在保留表上保留真实的时间戳,并且当执行插入或更新操作时,您可以更新时隙表,如果违反该表,则会引发错误唯一约束从而回滚了交易并防止了两个表中的更改。
答案 3 :(得分:0)
如果您有一个用于在数据库中存储车辆的表,那么您可以对用户没有悲观的等待锁,将其分配给用户选择的插槽。
一旦获得该锁,该锁将由一个事务持有,直到提交或回滚为止。如果尝试获取车辆的锁,所有其他事务将立即失败。因此,无需在db中等待事务。 这将可扩展,因为db中没有等待txns的等待队列来获得要分配的车辆锁。
对于失败的交易,您可以立即将其回滚并要求用户选择其他车辆或插槽。
现在,如果您有多辆相同类型的车辆,并且有机会分配相同的车辆,我的意思是说我的意思是说,在同一位置向两个用户分配相同的注册号。因为只有一项交易会赢,而其他交易会失败。
以下是此的PostgreSQL查询:
SELECT *
FROM vehicle
WHERE id = ?
FOR UPDATE nowait