2014-07-05 8 views
5

mam jakiś kod Rails ActiveRecord, który wygląda tak:Rails ActiveRecord - jak mogę zablokować tabelę do czytania?

new_account_number = Model.maximum(:account_number) 
# Some processing that usually involves incrementing 
# the new account number by one. 
Model.create(foo: 12, bar: 34, account_number: new_account_number) 

Ten kod działa poprawnie na własną rękę, ale mam pewne zadania w tle, które są przetwarzane przez pracowników DelayedJob. Jest dwóch pracowników i jeśli obydwoje zaczną przetwarzać partię zadań, które zajmują się tym kodem, kończą tworzenie nowych rekordów Model, które mają ten sam numer konta, ze względu na opóźnienie pomiędzy znalezieniem maksimum a utworzeniem nowego rekordu z jeszcze wyższym poziomem numer konta.

Na razie rozwiązałem go, dodając ograniczenie unikalności na poziomie bazy danych do tabeli modeli, a następnie ponowić próbę, ponownie wybierając maksimum w przypadku, gdy to ograniczenie wyzwoli wyjątek.

Jednak to wygląda jak włamanie.

Dodawanie auto inkrementacji na poziomie bazy danych do kolumny account_number nie jest opcją, ponieważ przypisywanie przypisów do konta oznacza więcej niż tylko inkrementację.

Idealnie chciałbym zablokować tabelę do czytania, więc żaden inny nie może wykonać kwerendy maksymalnej selekcji względem tabeli, dopóki nie skończę. Jednak nie jestem pewien, jak to osiągnąć. Używam PostgreSQL.

+1

Ograniczenia nie są hackami, ręczne blokowanie tabel jest hakerem. –

Odpowiedz

0

Można użyć metody lock od ActiveRecord::Locking::Pessimistic.

+0

Twoja odpowiedź jest poprawna, ale możesz podać kilka przykładów. ;) –

+13

Twój link pokazuje tylko przykłady blokowania wierszy, a nie blokowania tabel, o które pytano. –

4

Ustawienie unikalnego ograniczenia NIE JEST włamaniem. To sprawia, że ​​dane są spójne. Przy okazji masz jeszcze kilka opcji tutaj:

  1. zablokowanie niektórych DB zasobu (na przykład może to być wyjątkowy rekord) używając SELECT FOR UPDATE lub blokuje Doradcze PostreSQL (patrz: docs).

  2. Użyj sekwencji (docs).

Główna różnica między dwoma podejściami jest taka, że ​​nr 1 nie pozwala mieć luk w twoich numerach, ponieważ inna sesja będzie czekać na zatwierdzenie transakcji i # 2 zezwoli.

6

Na podstawie the ActiveRecord::Locking docs wygląda na to, że Rails nie zapewnia wbudowanego interfejsu API do blokad na poziomie stołu.

Ale nadal można to zrobić z surowym SQL. W przypadku Postgres wygląda na to, że blokada musi zostać uzyskana w ramach transakcji i jest automatycznie zwalniana po zakończeniu transakcji.

Należy pamiętać, że używany tutaj kod SQL będzie różny w zależności od bazy danych.

1

Istnieje już odpowiedź na pytanie, jak zablokować cały stół, ale uważam, że powinieneś tego unikać. Zamiast tego uważam, że powinnaś rzucić okiem na blokadę doradczą. Daje pewność, że ten sam blok kodu nie zostanie wykonany na dwóch komputerach jednocześnie, przy jednoczesnym zachowaniu otwartego stołu dla innych firm.

Nadal korzysta z bazy danych, ale nie blokuje twoich tabel.

Można użyć gem nazwie "with_advisory_lock" tak:

Model.with_advisory_lock("ADVISORY_LOCK_NAME") do 
    # Your code 
end 

https://github.com/ClosureTree/with_advisory_lock

To nie działa z SQLite.

+0

Bardzo ładne, ale dodaje zależność gem. :) –

1

No cóż, technicznie jest tak samo, aby zablokować stół lub zawsze zablokować zapis innej tabeli przed uzyskaniem dostępu do tabeli.

Więc może masz inny stolik z maksymalnie jednym rekordzie, sprecyzowane zablokować tego rekordu z http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html przed odczytu/zapisu z tabeli chcesz zablokować:

LockTable.last.with_lock do 
    // the things that needed for your table 
end 
0

nie trzeba zablokować tabeli hali aby zablokować fragment kodu dla pojedynczego procesu na raz. blokowanie pełnej tabeli powoduje problemy z wydajnością. Możesz zablokować cały ten sam wiersz za pomocą metody "with_lock". Ten sposób jest w pełni chroniony. nie potrzeba dodatkowego klejnotu. tworzy również transakcję. w ten sposób: m = Model.order(:id).first m.with_lock do #aquire lock #some code here for a single process at a time
end #release lock

Powiązane problemy