【问题标题】:T-SQL Concurrency Issue : Auction / Bidding SystemT-SQL 并发问题:拍卖/投标系统
【发布时间】:2011-07-04 15:06:59
【问题描述】:

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

两个人 - Geraldine 和 John - 想要为同一个拍卖品出价,目前售价为 50 英镑。杰拉尔丁出价 55 英镑,约翰出价 52 英镑。系统现在有两个页面“submit_bid.aspx”正在运行;页面的每个副本都会检查他们的出价是否足够高,他们都看到了,然后他们提交了出价。如果 John 的出价先通过,那么拍卖品的当前价格为 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 毫秒以释放锁。这是最佳做法吗?

【问题讨论】:

  • 或者记录“出价历史”,记录出价+金额,然后您可以查询最大(出价)并检查其是否为当前用户
  • 另一个问题,您真的希望用户在网站上不断地敲击网站以查看是否有最高出价,然后将他们的出价提高 1 美元吗?这就是为什么 ebay 让您输入您愿意支付的最高金额并为您增加金额。
  • 你应该选择一个答案。 41% 太低了。也请检查您的其他问题。

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


【解决方案1】:

来源:“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 甚至无法读取记录,因为他们在读取时也需要排他锁。这种方法需要太多的排他锁,而且锁的寿命太长(通常跨越用户控制 - absolute no-no)。这种方法几乎从未实施过。

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

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

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

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

比较

  • 乐观锁定独立于数据库 - 无需为隔离级别和隔离级别的数据库特定语法搞混。
  • 我会在时间戳上使用数字列 - 更少的数据和管理的麻烦

【讨论】:

    【解决方案2】:

    如果只使用这样的 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
    

    【讨论】:

      【解决方案3】:

      我按照上面 Alex K 的建议实施了“出价历史记录”。工作一种享受。谢谢亚历克斯 K。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-02
        • 2014-11-27
        • 2011-07-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多