webentwicklung-frage-antwort-db.com.de

Einzeltransaktion über mehrere Threads hinweg

Soweit ich weiß, sind alle Transaktionen threadgebunden (d. H. Mit dem in ThreadLocal gespeicherten Kontext). Zum Beispiel wenn:

  1. Ich starte eine Transaktion in einer übergeordneten Transaktionsmethode
  2. Führen Sie die Datenbankeinfügung Nr. 1 in einem asynchronen Aufruf aus
  3. Fügen Sie in einem anderen asynchronen Aufruf Datenbank-Insert 2 ein

Das ergibt dann zwei verschiedene Transaktionen (eine für jede Einfügung), obwohl sie dasselbe "Transaktions" -Elternteil geteilt haben.

Nehmen wir zum Beispiel an, ich füge zwei Inserts ein (und verwende ein sehr einfaches Beispiel, d. H. Aus Gründen der Kürze keinen Executor oder keine abschließbare Zukunft usw.):

@Transactional
public void addInTransactionWithAnnotation() {
    addNewRow();
    addNewRow();
}

Führt beide Einfügungen wie gewünscht als Teil derselben Transaktion aus.

Wenn ich diese Einfügungen jedoch für die Leistung parallelisieren wollte:

@Transactional
public void addInTransactionWithAnnotation() {
    new Thread(this::addNewRow).start();
    new Thread(this::addNewRow).start();
}

Dann wird jeder dieser gespawnten Threads überhaupt nicht an der Transaktion teilnehmen, da Transaktionen Thread-gebunden sind.

Schlüsselfrage : Gibt es eine Möglichkeit, die Transaktion sicher an die untergeordneten Threads weiterzugeben?

Die einzigen Lösungen, an die ich gedacht habe, um dieses Problem zu lösen:

  1. Verwenden Sie JTA oder einen XA-Manager, der per Definition dazu in der Lage sein sollte. Ich möchte jedoch im Idealfall XA nicht für meine Lösung verwenden, da dies mit einem Mehraufwand verbunden ist
  2. Leiten Sie die gesamte Transaktionsarbeit, die ich ausführen möchte (im obigen Beispiel die Funktion addNewRow()), an einen einzelnen Thread und führen Sie die gesamte vorherige Arbeit in Multithread-Form aus.
  3. Finden Sie heraus, wie Sie InheritableThreadLocal für den Transaktionsstatus nutzen und auf die untergeordneten Threads übertragen können. Ich bin mir nicht sicher, wie ich das machen soll.

Gibt es noch mehr Lösungen? Auch wenn es ein bisschen nach einem Workaround schmeckt (wie meine Lösungen oben)?

8
Dovmo

Zunächst eine Klarstellung: Wenn Sie mehrere Einfügungen derselben Art beschleunigen möchten, wie Ihr Beispiel vorschlägt, erzielen Sie wahrscheinlich die beste Leistung, wenn Sie die Einfügungen im selben Thread ausgeben und einige verwenden Art der Stapeleinfügung . Abhängig von Ihrem DBMS stehen verschiedene Techniken zur Verfügung:

Was Ihre eigentliche Frage betrifft, würde ich persönlich versuchen, die gesamte Arbeit an einen Arbeitsthread zu leiten. Dies ist die einfachste Option, da Sie sich weder mit ThreadLocals noch mit der Auflistung/Löschung von Transaktionen anlegen müssen. Darüber hinaus können Sie, wenn Sie Ihre Arbeitseinheiten im selben Thread haben, die oben beschriebenen Stapelverarbeitungstechniken anwenden, um eine bessere Leistung zu erzielen.

Das Weiterleiten von Arbeit an Arbeitsthreads bedeutet nicht, dass Sie einen einzelnen Arbeitsthread benötigen. Sie könnten einen Pool von Arbeitnehmern haben und eine gewisse Parallelität erzielen, wenn dies für Ihre Anwendung wirklich von Vorteil ist. Denken Sie in Produzenten/Konsumenten.

2
gpeche

Die JTA-API verfügt über mehrere Methoden, die implizit auf die Transaktion des aktuellen Threads angewendet werden. Sie hindert Sie jedoch nicht daran, eine Transaktion zwischen Threads zu verschieben oder zu kopieren oder bestimmte Vorgänge für eine Transaktion auszuführen, die nicht an den aktuellen (oder einen anderen) Thread gebunden ist. Dies verursacht kein Ende der Kopfschmerzen, aber es ist nicht das Schlimmste ...

Für Raw-JDBC haben Sie überhaupt keine JTA-Transaktion. Sie haben eine JDBC-Verbindung, die eigene Vorstellungen zum Transaktionskontext hat. In diesem Fall ist die Transaktion verbindungsgebunden und nicht threadgebunden. Geben Sie die Verbindung um und die Sendung geht damit. Verbindungen sind jedoch nicht unbedingt threadsicher und stellen wahrscheinlich sowieso einen Leistungsengpass dar. Daher hilft es Ihnen nicht wirklich, einen Thread zwischen mehreren Threads gleichzeitig zu teilen. Sie benötigen wahrscheinlich mehrere Verbindungen, die glauben, dass sie sich in derselben Transaktion befinden. Dies bedeutet, dass Sie XA benötigen, da die Datenbank auf diese Weise solche Fälle identifiziert. An diesem Punkt kehren Sie zu JTA zurück, haben aber jetzt eine JCA im Bild, um die Verbindungsverwaltung ordnungsgemäß zu handhaben. Kurz gesagt, Sie haben den JavaEE-Anwendungsserver neu erfunden.

Für Frameworks, die auf JDBC-Ebene liegen, z. ORMs wie Hibernate haben eine zusätzliche Komplikation: Ihre Abstraktionen sind nicht unbedingt threadsicher. Sie können also keine Sitzung haben, die gleichzeitig an mehrere Threads gebunden ist. Es können jedoch mehrere Sitzungen gleichzeitig stattfinden, die jeweils an derselben XA-Transaktion teilnehmen.

Wie immer läuft es auf Amdahls Gesetz hinaus. Wenn die Geschwindigkeit, die Sie durch die Verwendung mehrerer Connections per tx erhalten, damit mehrere Threads gleichzeitig die E/A-Arbeit der Datenbank gemeinsam nutzen können, im Verhältnis zu der Geschwindigkeit, die Sie durch die Stapelverarbeitung erhalten, sehr hoch ist, lohnt sich der Aufwand für XA. Wenn die Beschleunigung in der lokalen Berechnung liegt und die DB-E/A eine untergeordnete Rolle spielt, sollten Sie einen einzelnen Thread verwenden, der die JDBC-Verbindung verwaltet und die Nicht-E/A-Berechnungsarbeit in einen Thread-Pool verlagert.

1
jbosstxnerd