T-SQL并发问题:拍卖/竞标系统

时间:2011-07-04 15:06:59

标签: sql sql-server tsql sql-server-2008 concurrency

我目前正在使用ASP.NET 3.5和SQLServer 2008开发在线拍卖系统。我已经达到了开发的地步,我需要确保我的系统能够合理地处理可能在以下情况下出现的并发问题:

两个人 - 杰拉尔丁和约翰 - 想要竞标相同的拍卖品,目前价格为50英镑。 Geraldine以55英镑的价格出价,而约翰则出价52英镑。系统现在有两个页面'submit_bid.aspx'正在运行;页面的每个副本都会检查他们的出价是否足够高,他们都看到了,并且他们提交了出价。如果约翰的出价首先出现,那么拍卖品的价格目前是55英镑,过了一会儿就被52英镑的出价所取代。

我需要做的是锁定拍卖项目行,直到更新当前出价,然后再允许任何其他出价者检查当前出价并设置新出价。

我的问题是:使用T-SQL和/或ADO.NET执行此操作的最佳做​​法是什么?

我目前有一个AuctionItem表,其中包含以下字段(以及我为简洁起见未包含的其他字段):

AuctionItemID   INT
CurrentBidPrice MONEY
CurrentBidderID INT

我已经进行了一些研究,并提出了以下T-SQL(伪代码):

@Bid MONEY
@AuctionItemID INT

BEGIN TRANSACTION

SELECT @CurrentBidPrice = CurrentBidPrice
FROM AuctionItem
WITH (HOLDLOCK, ROWLOCK)
WHERE AuctionItemID = @AuctionItemID

/* Do checking for end of Auction, etc. */

if (@Bid > @CurrentBidPrice)
BEGIN
  UPDATE AuctionItem
  SET CurrentBidPrice = @Bid
  WHERE AuctionItemID = @AuctionItemID
END

COMMIT TRANSACTION

我还读过,如果我包含SET LOCK_TIMEOUT,我还可以减少失败的并发更新次数。例如:

SET LOCK_TIMEOUT 1000

...将使并发更新等待1000毫秒以释放锁。这是最佳做法吗?

3 个答案:

答案 0 :(得分:6)

来源:“chrisrlong”,http://www.dbasupport.com/forums/archive/index.php/t-7282.html

以下是用于处理多用户并发问题的方法:

  1. 什么都不做(不可取)

    • 用户1读取记录
    • 用户2读取相同的记录
    • 用户1更新该记录
    • 用户2更新同一记录

    用户2现在已经覆盖了用户1所做的更改。它们完全消失了,好像它们从未发生过一样。这称为“丢失更新”。

  2. 悲观锁定(读取时锁定记录。)

    • 用户1通过在记录上添加独占锁(FOR UPDATE子句)来读取记录并将其锁定
    • 用户2尝试读取并锁定相同的记录,但现在必须在用户1后面等待
    • 用户1更新记录(当然,提交)
    • 用户2现在可以使用用户1所做的更改
    • 来读取记录
    • 用户2使用来自用户1的更改
    • 更新记录

    丢失的更新问题已解决。这种方法的问题是并发性。用户1正在锁定他们可能永远不会更新的记录。用户2甚至无法读取记录,因为他们在阅读时也想要独占锁定。这种方法需要太多的独占锁定,而且锁的使用寿命太长(通常跨越用户控制 - 绝对禁止 - 否)。这种方法几乎从未实施。

  3. 使用乐观锁定。
    乐观锁定在阅读时不使用排他锁。相反,在更新期间进行检查以确保记录在读取后未被更改。通常,这是通过添加版本/ etc列(INT / numeric,保存在创建UPDATE语句时增加的数值)来完成的。 IE:

    UPDATE YOUR_TABLE
       SET bid = 52
     WHERE id = 10
       AND version = 6
    

    备用选项是使用时间戳,而不是数字列。除了实现乐观并发之外,此列仅用于而不是用于其他目的。它可以是数字或日期。想法是在插入行时给出一个值。每当读取记录时,也会读取timestamp列。执行更新时,将检查时间戳列。如果它在UPDATE时具有与读取时相同的值,则一切正常,执行UPDATE并且时间戳已更改!。如果时间戳值在UPDATE时间不同,则会向用户返回错误 - 他们必须重新读取记录,重新进行更改,并尝试再次更新记录。

    • 用户1读取记录,包括时间戳21
    • 用户2读取记录,包括时间戳21
    • 用户1尝试更新记录。 had(21)中的时间戳与数据库中的时间戳(21)匹配,因此执行更新并更新时间戳(22)。
    • 用户2尝试更新记录。手中的时间戳(21)匹配数据库(22)中的时间戳,因此返回错误。用户2现在必须重新读取记录,包括新的时间戳(22)和用户1的更改,重新应用他们的更改并重新尝试更新。
  4. 比较

    • 乐观锁定与数据库无关 - 无需隔离级别和隔离级别的数据库特定语法。
    • 我在时间戳上使用数字列 - 更少数据&麻烦管理

答案 1 :(得分:4)

如果只使用这样的1个语句,则不需要事务:​​

-- check if auction is over (you could also include this in the sql)

UPDATE AuctionItem   
SET CurrentBidPrice = @Bid   
WHERE AuctionItemID = @AuctionItemID 
AND CurrentBidPrice < @Bid

IF @@ROWCOUNT=1 
BEGIN
    --code for accepted bit
    SELECT 'NEW BIT ACCEPTED'
END ELSE
BEGIN
    --code for unaccepted bit
    SELECT 'NEW BIT NOT ACCEPTED'
END

答案 2 :(得分:0)

我遵循Alex K的建议并实施了“出价历史”。工作一种享受。谢谢Alex K。