2012-05-17 20 views
15

Próbuję wymusić unikalność wartości w jednym z moich pól tabeli. Zmiana tabeli nie jest opcją. Muszę użyć ActiveRecord, aby warunkowo wstawić wiersz do tabeli, ale jestem zaniepokojony synchronizacją.Warunki wyścigu w Railsach first_or_create

Czy first_or_create w Rails ActiveRecord zapobiega warunkom wyścigu?

Jest to kod źródłowy first_or_create z GitHub:

def first_or_create(attributes = nil, options = {}, &block) 
    first || create(attributes, options, &block) 
end 

Czy to możliwe, że duplikat wpisu spowoduje w bazie danych ze względu na problemy z synchronizacją z wieloma procesami?

+2

AR jest pełen takich warunków wyścigowych jak ten. – dbenhur

+0

Zobacz (SO dup) [Jak uniknąć sytuacji wyścigowej w mojej aplikacji Rails?] (Http://stackoverflow.com/questions/3037029/how-do-i-avoid-a-race-condition-in-my -rails-app) i Rails Cookbook [Unikanie warunków wyścigu z optymistycznym blokowaniem] (http://underpop.free.fr/r/ruby-on-rails/cookbook/I_0596527314_CHP_3_SECT_19.html) – dbenhur

+0

@dbenhur - nie mogę użyć optymistyczne blokowanie, ponieważ wymaga dodania pola do tabeli. Jednym z moich warunków było to, że nie mogę dodać pola, więc nie jest ono duplikatem. –

Odpowiedz

5

Tak, jest to możliwe.

Możesz znacznie zmniejszyć prawdopodobieństwo konfliktu z optimistic lub pessimistic locking. Oczywiście optymistyczne blokowanie wymaga dodania pola do tabeli, a pesymistyczne blokowanie również nie skaluje - i zależy to od możliwości twojego magazynu danych.

Nie jestem pewien, czy potrzebujesz dodatkowej ochrony, ale jest ona dostępna.

+0

Nie mogłem użyć optymistycznego blokowania, ponieważ modyfikowanie tabeli nie było możliwe. Problem z pesymistycznym blokowaniem polegał na tym, że nie mogłem określić poziomu izolacji blokady potrzebnego przez Railsy bez wstawiania składni specyficznej dla InnoDB. Skończyło się na użyciu 'first_or_create' pomimo możliwości duplikowania wierszy. Chociaż sądzę, że mogłem użyć pomocników do sprawdzania poprawności w ActiveRecord, aby zapewnić wyjątkowość. –

+2

Cierpią z powodu tego samego stanu wyścigu –

17

szynach 4 documentation for find_or_create_by zapewnia cynk, które mogą być przydatne w tej sytuacji:

proszę zauważyć ta metoda nie jest atomowy, biegnie najpierw zaznaczyć, a jeśli nie ma wyników INSERT jest próbował. Jeśli istnieją inne wątki lub procesy, pomiędzy obydwoma połączeniami występuje stan wyścigu i może się zdarzyć, że otrzymasz dwa podobne rekordy.

Niezależnie od tego, że jest to problem, czy nie, zależy od logiki aplikacji, ale w tym konkretnym przypadku, w którym wiersze mają UNIQUE wyjątek może zostać podniesiony, po prostu ponownie:

begin 
    CreditAccount.find_or_create_by(user_id: user.id) 
rescue ActiveRecord::RecordNotUnique 
    retry 
end 

Podobne w przypadku Rails 3 może być użyteczny błąd. (Nie jestem pewien, czy ten sam błąd ActiveRecord::RecordNotUnique został zgłoszony w Rails 3, więc Twoja implementacja może być inna).

+0

Tylko dla zapisu: Rails 3 rzuca "ActiveRecord :: StatementInvalid" (co nie jest bardzo specyficzne). – spickermann

+0

Bummer.Więc nadal możesz potencjalnie wykonać "rescue ActiveRecord :: StatementInvalid", aby spróbować ponownie, nawet jeśli nie czyta się tak dobrze jak w Railsach 4? –

+0

Możesz. Ale problem polega na tym, że "StatementInvalid" zawiera także niepoprawny SQL, 'NULL' w innych niż zero kolumnach i tak dalej. Możesz ponownie spróbować czegoś, co nigdy nie zadziała. Być może chcesz użyć czegoś takiego jak https://github.com/nfedyashev/retryable#readme, aby uniknąć uruchamiania w pętli. – spickermann

Powiązane problemy