我们正在创建一个拍卖投标系统。对于产品,如果其金额高于上次出价,我们仅允许插入到 AuctionBid ID 表中。
此查询适用于单个操作。
select MAX(Amount) from dbo.AuctionBid where ProductId = 9 // Amount: 150
但是,在多线程并发环境中,同一产品存在多个拍卖出价,这可能会导致竞争条件。其中一个插入读取 (150) 并将插入例如 170,而同时另一个插入已经插入(例如 190)。
如何在 SQL 中解决这个问题?研究不同类型的锁。如果可能的话,尝试防止全表锁定。
我们的桌子有
拍卖出价 | 产品ID | 金额 |
---|---|---|
1 | 2(沙发) | 175 |
2 | 9(电视) | 100 |
3 | 9(电视) | 150 |
当前使用 Microsoft SQL Server 2019。(T-SQL)
这样的事情有用吗?将其放入您的插入过程中,如果提供的
@CurrentBid
小于最高出价,则不会插入。您可以添加额外的处理来大惊小怪,或者在出价未被接受时记录一些内容,但这里的关键点是,这会释放表上的锁,以防止任何其他 SPID 尝试执行相同的操作。
insert into AuctionBid
(
Bid
)
select
@CurrentBId
where @CurrentBId > (select max(Bid) from AuctionBId)
-- if you need to handle subsequent logic differently if the bid insert failed, do something like this:
if @@rowcount = 0
begin
raiserror('Oh noes! ur bid wasn''t accepted!', 0, 1) with nowait
end
我对全局临时表进行了测试,首先插入了出价为 50 的行(作为表的种子)。然后,我打开一个新表,并以 9000 的出价向该表中插入数据,但保持事务打开。最后,我打开了第三个选项卡并尝试插入值 8999,但它挂起,等待我的其他事务完成。这似乎意味着在插入完成时该表被锁定,无法插入。
在另一个事务仍处于打开状态时尝试插入的 SPID 正在尝试获取
LCK_M_S
共享锁,并且在另一个事务提交之前无法获得。
当其他 SPID 拥有锁时,您甚至无法从表中选择
max(Bid)
;正如你所希望的那样。
考虑 ff 程序
create or alter procedure dbo.addBid (
@ProductID int,
@BidAmount int
)
as
begin
declare @maxBid int;
begin transaction
set @maxBid = (
select max(Amount)
from dbo.AuctionBid with (holdlock, updlock)
where ProductId = 9
);
if (@bidAmount > @maxBid)
begin
insert into dbo.AuctionBid
(ProductId, Amount)
values
(@ProductID, @BidAmount);
end
else
begin
print 'Specified bid amount is not larger than current bid and is thus rejected';
end
commit
end
您可能还需要一些其他错误处理,但这就是要点。这里的想法是,您以阻止并发进程同时执行此操作的方式获取当前最大出价,并在与该选择相同的事务中执行插入。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
-- Get the current max bid for the product
SELECT MAX(Amount) FROM dbo.AuctionBid WHERE ProductId = 9;
-- Your application checks if the new bid is higher and proceeds to insert
INSERT INTO dbo.AuctionBid (ProductId, Amount) VALUES (9, <new_bid>);
COMMIT TRANSACTION;