Rails-ActiveRecord - wie kann ich die Sperre eine Tabelle zum Lesen?
Habe ich einige Rails ActiveRecord code, der wie folgt aussieht:
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)
Dieser code funktioniert gut auf seine eigene, aber ich habe einige hintergrund-jobs verarbeitet werden, indem DelayedJob Arbeitnehmer. Es gibt zwei Arbeiter, und wenn Sie beide starten der Verarbeitung einer batch-jobs, die sich mit diesem code, Sie am Ende die Schaffung neuer Model
Datensätze, die den gleichen account_number, weil der Verzögerung zwischen der Feststellung der maximal-und das erstellen eines neuen Datensatzes mit einem noch höheren Konto-Nummer.
Jetzt habe ich es gelöst, indem Sie hinzufügen einer Eindeutigkeit auf der Datenbank-Ebene auf die Modelle-Tabelle und wiederholen Sie dann den Vorgang, indem Sie erneut auf die maximale im Fall dieser Einschränkung löst eine Ausnahme aus.
Aber es fühlt sich an wie ein hack.
Hinzufügen von auto-Inkrementieren auf der Datenbank-Ebene, um die account_number
Spalte ist nicht eine option, weil die account_number zuweisen bringt mehr als nur Inkrementieren.
Idealerweise würde ich gerne um die Tabelle zu sperren, in Frage, die für das Lesen, so dass keine anderen ausführen kann, die maximale select-Abfrage gegen die Tabelle, bis ich fertig bin. Allerdings bin ich mir nicht sicher, wie das geht. Ich verwende Postgresql.
- Einschränkungen sind nicht hacks, Handbuch Tabelle sperren ist ein hack.
- Sperren Sie die Tabelle mit
EXCLUSIVE
läßt, liest passieren
Du musst angemeldet sein, um einen Kommentar abzugeben.
Basierend auf die ActiveRecord::Sperren docs es sieht aus wie Schienen nicht bieten eine eingebaute API für den table-level locks.
Aber man kann immer noch tun Sie dies mit raw-SQL. Für Postgres, das sieht aus wie
Die Sperre erworben werden muss innerhalb einer Transaktion und wird automatisch freigegeben, sobald die Transaktion beendet.
Beachten Sie, dass die SQL, die Sie hier verwenden, hängt von Ihrer Datenbank.
Gibt es bereits Antworten auf, wie Sie die gesamte Tabelle sperren, aber ich glaube, Sie sollten versuchen zu vermeiden, dass. Stattdessen glaube ich, dass Sie sollten geben advisory locks einen Blick. Es stellt sicher, dass der gleiche code-block nicht ausgeführt wird, auf zwei Rechnern gleichzeitig, während noch halten Sie die Tabelle zu öffnen, die für andere Unternehmen.
Es verwendet immer noch die Datenbank, aber nicht sperren Sie Ihre Tabellen.
Können Sie den Juwel namens "with_advisory_lock" wie folgt:
https://github.com/ClosureTree/with_advisory_lock
Es funktioniert nicht mit SQLite.
Einstellung unique-Einschränkung IST NICHT ein hack ist. Es ist, was macht Ihre Daten konsistent.
Übrigens haben Sie ein paar mehr Optionen hier:
Schloss einige DB-Ressource (z.B. es könnte sein, eine einzigartige Datensatz) mit
WÄHLEN Sie FÜR die AKTUALISIERUNG oder PostreSQL Advisory Locks (siehe docs).
Verwendung einer Sequenz (docs).
Der wesentliche Unterschied zwischen beiden Ansätzen ist #1 nicht ermöglichen, haben Lücken in Ihren zahlen, weil andere session wartet, bis die Transaktion ein commit und #2 erlaubt.
Können Sie die
lock
Methode von ActiveRecord::Sperren::Pessimistisch.Gut, technisch ist es das gleiche zum sperren einer Tabelle oder für immer sperren einen Datensatz einer anderen Tabelle, bevor Sie auf die Tabelle.
So können Sie eine weitere Tabelle mit max einen Datensatz, immer sperren, dass der Datensatz mit http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html, bevor Sie Lesen/schreiben aus der Tabelle, die Sie sperren möchten:
Sie nicht haben, um es zu sperren Halle Tabelle sperren, um ein Stück code für einen einzelnen Prozess zu einer Zeit. sperren einer Tabelle verursacht Performanz-Probleme.sperren Sie können eine einzelne derselben Zeile die ganze Zeit mit "with_lock" - Methode.dieser code ist vollständig geschützt. keine extra-Juwel benötigt. es schafft auch eine Transaktion. wie diese:
m = Model.order(:id).first
m.with_lock do #aquire lock
#some code here for a single process at a time
end #release lock