6 Stimmen

nhibernate Deadlocks

Ich verwende den folgenden Code in einer ASP.NET-Seite, um einen Datensatz zu erstellen, dann die Datensätze zu zählen, um sicherzustellen, dass ich eine festgelegte Grenze nicht überschritten habe, und die Transaktion zurückzusetzen, wenn dies der Fall ist.

using (var session = NhibernateHelper.OpenSession())
using (var transaction = session.BeginTransaction())
{
    session.Lock(mall, LockMode.None);

    var voucher = new Voucher();
    voucher.FirstName = firstName ?? string.Empty;
    voucher.LastName = lastName ?? string.Empty;
    voucher.Address = address ?? string.Empty;
    voucher.Address2 = address2 ?? string.Empty;
    voucher.City = city ?? string.Empty;
    voucher.State = state ?? string.Empty;
    voucher.Zip = zip ?? string.Empty;
    voucher.Email = email ?? string.Empty;
    voucher.Mall = mall;
    session.Save(voucher);

    var issued = session.CreateCriteria<Voucher>()
        .Add(Restrictions.Eq("Mall", mall))
        .SetProjection(Projections.Count("ID"))
        .UniqueResult<int>();

    if (issued >= mall.TotalVouchers)
    {
        transaction.Rollback();
        throw new VoucherLimitException();
    }

    transaction.Commit();
    return voucher;
}        

Allerdings bekomme ich eine Menge von Deadlocks. Ich vermute, dass dies geschieht, weil ich versuche, die Datensätze in einer Tabelle zu zählen, in die ich gerade eine Einfügung vorgenommen habe, und eine Sperre noch auf der eingefügten Zeile gehalten wird, was die Blockierung verursacht.

  • Kann jemand dies bestätigen?
  • Kann jemand eine Lösung vorschlagen?

Ich habe versucht, SetLockMode(LockMode.None) auf die letzte Abfrage aufrufen, aber das führt zu einer NullReferenceException, die ich nicht herausfinden kann.

Bearbeiten: Wenn ich die Abfrage ausführe, bevor ich das Objekt speichere, funktioniert es, aber dann erreiche ich nicht das Ziel, zu überprüfen, dass meine Einfügung nicht irgendwie über das Limit hinausgeht (im Fall von gleichzeitigen Einfügungen).

Bearbeiten: Ich habe festgestellt, dass die Verwendung von IsolationLevel.ReadUncommited im Aufruf session.BeginTransaction das Problem löst, aber ich bin kein Datenbankexperte. Ist dies die geeignete Lösung für das Problem oder sollte ich meine Logik irgendwie anpassen?

3voto

onupdatecascade Punkte 3306

Ein solches Design wäre anfällig für Deadlocks - typischerweise (nicht immer) ist es unwahrscheinlich, dass eine Verbindung sich selbst blockiert, aber mehrere Verbindungen, die Einfügungen und Aggregate für dieselbe Tabelle durchführen, sind sehr wahrscheinlich blockiert. Das liegt daran, dass zwar alle Aktivitäten in einer Transaktion aus der Sicht der Verbindung, die die Arbeit ausführt, vollständig erscheinen - die Datenbank sperrt eine Transaktion nicht für "ihre eigenen" Datensätze -, aber die Aggregatabfragen von ANDEREN Transaktionen würden versuchen, die gesamte Tabelle oder große Teile davon gleichzeitig zu sperren, und diese würden sich blockieren.

Uncommitted lesen ist no ist in diesem Fall Ihr Freund, denn er sagt im Grunde "Sperren ignorieren", was irgendwann bedeutet, dass die Regeln, die Sie für die Daten aufgestellt haben, verletzt werden. D.h. die Zählung der Datensätze in der Tabelle wird ungenau sein, und Sie werden aufgrund dieser ungenauen Zählung handeln. Ihre Zählung wird 10 oder 13 zurückgeben, wenn die wirkliche Antwort 11 ist.

Der beste Rat, den ich habe, ist, Ihre Einfügelogik so umzugestalten, dass Sie die Idee der Zählung erfassen, ohne buchstäblich die Zeilen zu zählen. Dafür gibt es mehrere Möglichkeiten. Eine Idee, die ich habe, ist folgende: Nummerieren Sie die eingefügten Belege buchstäblich mit einer Sequenz und erzwingen Sie ein Limit für die Sequenz selbst.

  1. Erstellen Sie eine Sequenztabelle mit den Spalten (ich schätze) MallID, nextVoucher, maxVouchers
  2. Geben Sie in diese Tabelle die Mallids, 1 und den Grenzwert für jede Mall ein
  3. Ändern Sie die Einfügelogik in diesen Pseudocode:

    Begin Transaction Sanity check the nextVoucher for Mall in the sequence table; if too many exist abort If less than MaxVouchers for Mall then { check, fetch, lock and increment nextVoucher if increment was successful then use the value of nextVoucher to perform your insert. Include it in the target table. } Error? Rollback No Error? Commit

Eine Sequenztabelle wie diese beeinträchtigt die Gleichzeitigkeit etwas, aber ich denke, nicht so sehr wie das Zählen der Zeilen in der Tabelle ständig. Stellen Sie sicher, dass Sie Perf-Tests durchführen. Außerdem ist das [check, fetch, lock and increment] wichtig - Sie müssen die Zeile in der Sequenztabelle exklusiv sperren, um zu verhindern, dass eine andere Verbindung in dem Sekundenbruchteil vor der Inkrementierung denselben Wert verwendet. Ich kenne die SQL-Syntax dafür, aber ich fürchte, ich bin kein nHibernate-Experte.

Bei Fehlern beim Lesen von unbestätigten Daten lesen Sie bitte hier nach: http://sqlblog.com/blogs/merrill_aldrich/archive/2009/07/29/transaction-isolation-dirty-reads-deadlocks-demo.aspx (Haftungsausschluss: Merrill Aldrich bin ich :-)

0voto

JBland Punkte 1253

2 Fragen:

  1. Wie häufig werden Gutscheine gelöscht
  2. Etwaige Einwände (abgesehen von der Reinheit) gegen eine db-Pegel-Auslöser?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X