I am currently developing an online Auction system using ASP.NET 3.5 and SQLServer 2008. I have reached the point in development where I need to ensure that my system sensibly handles the concurrency issue which may arise when:
Two people - Geraldine and John - want to bid on the same auction item which is currently going for £50. Geraldine enters a bid of £55 and John enters a bid of £52. The system now has two copies of the page 'submit_bid.aspx' running; each copy of the page checks to see that their bid is high enough, they both see that it is, and they submit the bids. If John's bid goes through first then the auction item price is currently £55 and a moment later it's being replaced by a bid of £52.
What I need to do is to lock the auction item row until the current bid price is updated before allowing any other bidder to check the current bid price and placing a new bid.
My question is: what is the best practice way for doing this using T-SQL and / or ADO.NET?
I currently have an AuctionItem table which has the following fields (plus other fields I haven't included for brevity):
AuctionItemID INT
CurrentBidPrice MONEY
CurrentBidderID INT
I have performed some research and come up with the following T-SQL (pseudocode-ish):
@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
I have also read that if I include the SET LOCK_TIMEOUT
I can also reduce the number of failed concurrent updates. For example:
SET LOCK_TIMEOUT 1000
...will make a concurrent update wait for 1000 milliseconds for a lock to be released. Is this best practice?
SQL Server locks the minimum number of resources required to complete a transaction. It uses different types of locks to support as much concurrency as possible while maintaining data consistency and transaction isolation.
In optimistic concurrency control, users do not lock data when they read it. When a user updates data, the system checks to see if another user changed the data after it was read. If another user updated the data, an error is raised. Typically, the user receiving the error rolls back the transaction and starts over.
Source: "chrisrlong", http://www.dbasupport.com/forums/archive/index.php/t-7282.html
Here are the methodologies used to handle multi-user concurrency issues:
Do Nothing (Undesirable)
User 2 has now over-written the changes that User 1 made. They are completely gone, as if they never happened. This is called a 'lost update'.
Pessimistic locking (Lock the record when it is read.)
The lost update problem is solved. The problem with this approach is concurrency. User 1 is locking a record that they might not ever update. User 2 cannot even read the record because they want an exclusive lock when reading as well. This approach requires far too much exclusive locking, and the locks live far too long (often across user control - an absolute no-no). This approach is almost never implemented.
Use Optimistic Locking.
Optimistic locking does not use exclusive locks when reading. Instead, a check is made during the update to make sure that the record has not been changed since it was read. Generally this is done by adding a version/etc column (INT/numeric, holding a numeric value that is increased when an UPDATE statement is made). IE:
UPDATE YOUR_TABLE
SET bid = 52
WHERE id = 10
AND version = 6
An alternate option is to use a timestamp, rather than a numeric column. This column is used for no other purpose than implementing optimistic concurrency. It can be a number or a date. The idea is that it is given a value when the row is inserted. Whenever the record is read, the timestamp column is read as well. When an update is performed, the timestamp column is checked. If it has the same value at UPDATE time as it did when it was read, then all is well, the UPDATE is performed and the timestamp is changed!. If the timestamp value is different at UPDATE time, then an error is returned to the user - they must re-read the record, re-make their changes, and try to update the record again.
You don't need a transaction if just use 1 statement like this:
-- 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With