Проблема параллелизма T-SQL: Система аукционов / торгов - PullRequest
8 голосов
/ 04 июля 2011

В настоящее время я занимаюсь разработкой системы онлайн-аукциона с использованием ASP.NET 3.5 и SQLServer 2008. Я достиг стадии разработки, когда мне нужно убедиться, что моя система разумно обрабатывает проблему параллелизма, которая может возникнуть, когда:

Два человека - Джеральдин и Джон - хотят сделать ставку на один и тот же предмет аукциона, который сейчас стоит 50 фунтов.Джеральдина вводит ставку в 55 фунтов стерлингов, а Джон вводит ставку в 52 фунта.Теперь в системе работает две копии страницы submit_bid.aspx;каждая копия страницы проверяет, достаточно ли высока их ставка, они оба видят, что это так, и подают заявки.Если ставка Джона проходит сначала, то цена предмета аукциона в настоящее время составляет 55 фунтов стерлингов, а мгновение спустя она заменяется ставкой 52 фунтов стерлингов.

Что мне нужно сделать, это заблокировать строку предмета аукциона до текущейцена предложения обновляется, прежде чем позволить любому другому участнику проверить текущую цену предложения и сделать новую ставку.

Мой вопрос: каков наилучший способ сделать это с использованием T-SQL и / или ADO.NET??

В настоящее время у меня есть таблица AuctionItem, которая имеет следующие поля (плюс другие поля, которые я не включил для краткости):

AuctionItemID   INT
CurrentBidPrice MONEY
CurrentBidderID INT

Я провел некоторые исследования и придумалиследующий T-SQL (псевдокод-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

Я также прочитал, что, если я включу SET LOCK_TIMEOUT, я также могу уменьшить количество неудачных одновременных обновлений.Например:

SET LOCK_TIMEOUT 1000

... приведет к одновременному обновлению, ожидающему 1000 миллисекунд для снятия блокировки.Это лучшая практика?

Ответы [ 3 ]

6 голосов
/ 04 июля 2011

Источник: "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
    

    Альтернативным вариантом является использование метки времени, а не числового столбца. Этот столбец используется ни для каких других целей , кроме реализации оптимистичного параллелизма. Это может быть число или дата. Идея состоит в том, что ему присваивается значение при вставке строки. Всякий раз, когда запись читается, столбец отметки времени также читается. Когда выполняется обновление, проверяется столбец отметки времени. Если во время ОБНОВЛЕНИЯ оно имеет то же значение, что и при чтении, то все в порядке, ОБНОВЛЕНИЕ выполняется и метка времени изменяется! . Если значение метки времени отличается во время ОБНОВЛЕНИЯ, то пользователю возвращается ошибка - он должен заново прочитать запись, повторно внести свои изменения и попытаться обновить запись снова.

    • Пользователь 1 читает запись, включая метку времени 21
    • Пользователь 2 читает запись, включая метку времени 21
    • Пользователь 1 пытается обновить запись. Отметка времени в had (21) совпадает с отметкой времени в базе данных (21), поэтому обновление выполняется, а отметка времени обновляется (22).
    • Пользователь 2 пытается обновить запись. Временная метка в руке (21) не не соответствует временной метке в базе данных (22), поэтому возвращается ошибка. Пользователь 2 должен теперь перечитать запись, включая новую отметку времени (22) и изменения пользователя 1, повторно применить их изменения и повторить попытку обновления.

Сравнение

  • Оптимистическая блокировка не зависит от базы данных - нет необходимости взламывать с уровнями изоляции и специфичным для базы данных синтаксисом для уровней изоляции.
  • Я бы использовал числовой столбец с отметкой времени - меньше данных и хлопот для управления
4 голосов
/ 04 июля 2011

Вы не нуждаетесь в транзакции, если просто используете 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
0 голосов
/ 03 ноября 2011

Я последовал предложению Алекса К выше и реализовал «Историю ставок». Работает угощение. Спасибо Алекс К.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...