2011-12-30 3 views
3

Я занимаюсь дизайном аукциона, например, с помощью Rails 3.1 и MySQL 5.1. У пользователей будет баланс по счету, поэтому важно, чтобы кто-то не предлагал цену за предмет аукциона, если у него недостаточно средств.Оптимистичная или пессимистическая блокировка в аукционном/банковском приложении (Rails/MySQL)

Очевидно, я буду паковать «победы» аукциона в сделку, выходит что-то вроде этого:

Сделка 1:

ActiveRecord::Base.transaction do 
    a = Account.where(:id=>session[:user_id]).first 
    # now comes a long part of code with various calculations and other table updates, i.e. time pases 
    a.balance -= the_price_of_the_item 
    a.save! 
end 

Кстати, я curerntly используя оптимистическую блокировку, следовательно, все мои таблицы имеют lock_version столбца.

Хотя такая транзакция выполняется, пользователь может через другой входной место другие предложения, поэтому всякий раз, когда они сделать ставку, кусок кода проверяет, если текущий доступный остаток достаточно

То же самое и здесь:

сделка 2:

ActiveRecord::Base.transaction do 
    a = Account.where(:id=>session[:user_id]).first 
    raise ActiveRecord::Rollback if a.balance < the_price_of_the_bid + Bids.get_total_bid_value_for_user(session[:user_id]) 
    # now process the bid saving 
end 

Очевидно мне нужно убедиться, что эти две операции не перекрывают друг друга, в противном случае сделка 2 может читать баланс в то время как сделка 1 находится в центре обработки и я в конечном итоге с h отрицательный остаток на счете (ставка сохраняется, а затем транзакция 1 совершает, тогда у пользователя есть возможность делать ставки за счет средств, которых у него больше нет).

Следует отметить, что транзакция 2 не вносит никаких изменений в Учетную запись, она просто считывает учетную запись. Я думаю, это сводится к вопросу: как предотвратить любые чтения для выбранных операторов SELECT во время выполнения транзакции.

Как сделать транзакцию 2 дождаться завершения транзакции 1? Возможно ли это с оптимистичной блокировкой и одним из доступных уровней изоляции транзакций MySQL или мне нужно использовать пессимистическую блокировку здесь? Если пезимистическая блокировка является единственным ответом, добавив a.lock! после прочтения записи учетной записи в каждой из двух транзакций достаточно?

Дизайн критерии, конечно

  • Ищу наиболее производительным решения, даже если это означает более кодирования.
  • непротиворечивость данных имеет первостепенное значение
+0

Просто попытайтесь понять ваши данные. Скажите, что у кого-то есть баланс 10. Могут ли они создать 2 ставки по 10 каждый (на данный момент мы не знаем, выиграет ли кто-нибудь или кто-нибудь выиграет)? Если они не могут, как вы это отслеживаете? –

+0

Нет, они не могут, в противном случае я рискую, что они выиграют обе ставки, и это приведет к балансу -10 на их счетах. Отслеживание выполняется в транзакции 2, оно проверяет доступный баланс, и только у пользователя достаточно денег на счете, тогда его ставка будет принята и сохранена. – KKK

+0

Так что код также идет и проверяет, есть ли в настоящее время открытые ставки? (или отражает баланс счета) –

ответ

2

Имея расходы в настоящее время почти 10 часов нон-стоп чтения различных сообщений и документов, а также суда и erroring с помощью консоли Rails, я хочу подвести итог мои выводы:

Оптимистическая блокировка: бесполезно для удовлетворения моих требований, блокировка срабатывает только в том случае, если я фактически сохраняю запись баланса счета. Но размещение ставки не обновляет запись учетной записи, поэтому она не будет запускать оптимистичную блокировку, если я не сохраню поле в записи учетной записи, которое отслеживает мои текущие целевые фонды для всех открытых ставок и, следовательно, обновит запись учетной записи при размещении ставок (что я не хотите делать, поскольку это потребует другого обновления БД всякий раз, когда ставка будет сохранена).

Так что это оставляет меня только с Пезимистической блокировкой.В целях простоты я решил поставить блокировку на запись пользователя, таким образом, код для моей транзакции 1 меняется на:

Сделки 1:

ActiveRecord::Base.transaction do  
    u = User.find(session[:user_id],:lock=>true) 
    a = Account.where(:id=>session[:user_id]).first 
    a.balance -= the_price_of_the_item 
    ... some more code here ... 
    a.save!  
end  

и сделки 2:

ActiveRecord::Base.transaction do 
    u = User.find(session[:user_id],:lock=>true) 
    raise ActiveRecord::Rollback if a.balance < the_price_of_the_bid + Bids.get_total_bid_value_for_user(session[:user_id])   
    # now process the bid saving 
    .... 
end 

Кроме того, я решил установить уровень изоляции транзакций MySQL в SERIALIZABLE.

1

Я интерпретирую ваш вопрос следующим образом.

  1. АККАУНТА имеет баланс
  2. АККАУНТА имеет много Инфо
  3. Тендерное имеет значение
  4. Тендерное предложение, которое не имеет ни вон, ни потерянное, является «Open»
  5. Когда Большой выигрывается, его стоимость вычитается из остатка на Счете.
  6. Ставка может быть сделана только в том случае, если сумма значений для «открытых» ставок меньше баланса.

Как таковой вы определили транзакции, которые имеют значение.

  1. Размещение предложения
  2. Победы предложения

Это, как я бы это сделать.

1.

account = Account.find_by_id(session[:user_id]) 
# maybe do some stuff here 

transaction do 
    account.lock! 

    bid_amount = account.bids.open.sum(:value) 
    if bid_amount + this_value > account.balance 
    raise "you're broke, mate" 
    end 

    account.bid.create!(:value => this_value) 
end 

Мы проводим блокировку строки на Счете в течение короткого времени, но при условии, что вы используете правильную базу данных, это должно быть в порядке.

Предполагая, что первый бит был прав, то следующий гораздо проще

2.

class Bid 
    def win! 
    transaction do 
     account.lock! 
     account.decrement(:balance, self.value) 
     account.save! 
     close! 
    end 
    end 
end 

Особенно, если вы сделали обновление SQL SET balance = balance - ? вам не нужно будет сделать блокировку на 2 один.

Но, как правило, делайте блокировку вокруг минимальной части кода.

Реально, вы не должны иметь замок более чем на 100 мс.

+0

Я думаю, что существует риск «грязного чтения» здесь, в части 1: Вы читаете остаток на счете до начала транзакции, , таким образом, если другой процесс обновляет остаток на счете с account = Account.find_by_id (session [: user_id]) и сделка делаю счет.lock! , то , если bid_amount + this_value> account.balance будет неверным. Иными словами, если часть 2. выполняется между поиском и блокировкой PArt 1. то часть 1 работает с неправильным балансом счета. Не так ли? – KKK

+0

Я считаю, что «блокировка!» Выполняет перезагрузку, поэтому перезагружает баланс в то время. –

+0

блокировка! абсолютно перезагружается: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locking/pessimistic.rb – mrm

Смежные вопросы