[JAVA] Transaktionen, Sperren und doppelte Abrechnung

Dieser Artikel ist der 23. Tag des CAM Engineer Adventskalenders 2019. Gestern war @ cotsupas Ich habe einen Suchbefehl mit Shell Script ausgeführt.

Einführung

Hallo, das ist @ takehzi. Ich habe eine Aufgabe erledigt, um eine doppelte Abrechnung in meinem Unternehmen zu verhindern, daher wird es ein Memorandum sein.

Der Hintergrund ist, wenn der Benutzer zweimal auf die Zahlungsschaltfläche klickt. Der Zahlungsvorgang wurde zweimal ausgeführt, daher habe ich ihn behoben. (Der Prozess der Kontrolle sogar der Front ist implementiert)

Was ist eine Transaktion?

Ich hatte nur ein flauschiges Verständnis von Transaktionen, also habe ich es zuerst nachgeschlagen. Einfach ausgedrückt ist eine Transaktion eine Arbeitseinheit, die aufeinanderfolgende DB-Operationen (eine oder mehrere SQL-Operationen) als Gruppe behandelt.

DB führt nur SQL aus und weiß nicht, von wo zu wo sich eine Verarbeitungsgruppe befindet. Wenn Sie von dieser Seite aus eine Transaktion (für DB) festlegen, kann die DB-Seite erkennen, dass es sich um eine Gruppe handelt, und diese als einen Prozess (eine Gruppe) behandeln.

Das Ergebnis der Transaktion ist · Verpflichten ・ Rollback Es gibt nur zwei.

Wenn die gesamte Verarbeitung in der Transaktion erfolgreich ist, geben Sie ein Commit aus, um Änderungen an allen relevanten Tabellen zu ermöglichen und die Transaktion zu beenden.

Wenn einer der Vorgänge fehlschlägt, geben Sie ein Rollback aus, um die Änderungen an allen relevanten Tabellen (zuvor vor dem Eintritt in den Transaktionsprozess) ungültig zu machen und die Transaktion zu beenden.

Sperren

Um eine doppelte Abrechnung zu verhindern, muss eine Sperrsteuerung für die DB durchgeführt werden. Dieses Mal habe ich mich für "SELECT ... FOR UPDATE" entschieden, um eine exklusive Sperre (pessimistische Sperre) durchzuführen.

Es besteht auch eine gute Chance, dass ein anderer Benutzer eine Zahlung leistet, während eine doppelte Abrechnung stattfindet. Stellen Sie sicher, dass die Zeile gesperrt ist.

Da müssen Sie den Primärschlüssel indizieren (oder konditionieren), um die Zeile zu sperren Sie müssen es auch im Voraus indizieren.

Ich werde versuchen, SELECT ... FOR UPDATE zu verwenden.

mysql> select * from user;
+----+--------------+------+
| id | name         | age  |
+----+--------------+------+
|  1 | takehzi      |   25 |
|  2 | komatsunana  |   23 |
|  3 | yoshiokariho |   26 |
+----+--------------+------+
3 rows in set (0.00 sec)

Verwenden Sie SELECT ... FOR UPDATE in dieser Tabelle, um das Alter von Takhzi von 25 auf 20 zu ändern.

A)
==================================
//Transaktionsstart
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

//id=Sperren Sie Zeilen, wenn Sie einen Datensatz abrufen.
mysql> select * from user where id=1 for update;
+----+---------+------+
| id | name    | age  |
+----+---------+------+
|  1 | takehzi |   25 |
+----+---------+------+
1 row in set (0.00 sec)

===================================
B)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where id=1 for update;
//...Warten

B wartet, weil A eine Zeilensperre hat. Wenn dann A-Datensätze aktualisiert und festgeschrieben werden, wird die Sperre aufgehoben und die select-Anweisung von B ausgeführt.

================================
A)
//Ändern Sie das Alter von 25 auf 20 Jahre
mysql> update user set age="20" where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

//Ende der Transaktion
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

==============================
B)
mysql> select * from user where id=1 for update;
+----+---------+------+
| id | name    | age  |
+----+---------+------+
|  1 | takehzi |   20 |
+----+---------+------+
1 row in set (9.26 sec)

B's SQL wird ausgeführt, wenn A festschreibt. Es ist der in B erhaltene Wert, aber es ist 20 statt 25.

Ich habe dieses SELECT ... FOR UPDATE verwendet, um die doppelte Abrechnung zu steuern.

Problem der doppelten Abrechnung und Gegenmaßnahmen

Basierend auf diesen Bedingungen haben wir beschlossen, eine Sitzungstabelle einzurichten, um die doppelte Abrechnung zu steuern. Es wird gesteuert, ob eine Tracking-ID vorhanden ist oder nicht. Das Architekturdiagramm ist wie folgt. スクリーンショット 2019-12-22 23.29.38.png

Der tatsächliche Code.

  public void tempInsert(String serviceName, AmazonPayChargeParam param){

    assert StringUtils.isNoneBlank(serviceName);
    assert StringUtils.isNoneBlank(param.getReferenceId());
    assert StringUtils.isNoneBlank(param.getCustCode());
    assert StringUtils.isNoneBlank(param.getItemId());

    try{
      AmazonPaySession session = new AmazonPaySession();
      session.setReferenceId(param.getReferenceId());
      session.setCustCode(param.getCustCode());
      session.setItemId(param.getItemId());
      session.setServiceName(serviceName);

      amazonPaySessionRepository.insert(session);
    }catch (DuplicateKeyException dke){
      //Steuert Einschränkungsfehler an erster Stelle aufgrund doppelter Einfügungen.
      auditLogHelper.loggingParam(dke.getMessage());
    }
  }

Zunächst wird das Einfügen in der Sitzungstabelle als temporäre Daten verarbeitet. (Zu diesem Zeitpunkt ist trackingId null.)

public String authorization(Long apiAuthId, String serviceName, AmazonPayChargeParam param) {

    ChargeRequest chargeRequest = getChargeRequest(param);
    String trackingId = chargeRequest.getChargeReferenceId();

      AuthorizationDetails authDetails = null;
      switch(chargeRequest.getType()) {

//...Abkürzung

        case BILLING_AGREEMENT_ID:

          //Sperren Sie die Leitung hier.TrackingId kann auf der doppelten Seite abgerufen werden.
          AmazonPaySession sessionData = amazonPaySessionRepository.tempSelectForUpdate(serviceName, param.getReferenceId(), param.getCustCode(), param.getItemId());

          //Die überlappende Seite verfolgt_Holen Sie sich einen Ausweis und erhalten Sie eine frühzeitige Rückkehr.
          if(Objects.nonNull(sessionData.getTrackingId())){
            return sessionData.getTrackingId();
          }

      //Rechnungsprozess
          authDetails = execBillingAgreement(serviceName, param, chargeRequest, apiAuthId, authDetails, trackingId, sessionData);

//...Abkürzung

      //Gibt schließlich die Tracking-ID zurück
      return trackingId; 
  }

Bestimmen Sie hier das Vorhandensein oder Fehlen einer Tracking-ID. Wenn eine Tracking-ID (doppelte Seite) vorhanden ist, wird diese Tracking-ID vorzeitig zurückgegeben.

Da "noch keine Tracking-ID = Zahlung erfolgt ist", wird der Zahlungsvorgang so ausgeführt, wie er ist. Stellen Sie sicher, dass Sie die ausgegebene Tracking-ID zur Sitzungstabelle hinzufügen.

Auf diese Weise haben wir SELECT ... FOR UPDATE verwendet, um die doppelte Abrechnung zu steuern.

Am Ende

Ich habe es ziemlich locker geschrieben (ich hatte keine Zeit, es zu sehr aufzuschieben ...) In der Tat gibt es mehr Dinge zu beachten.

-Was ist die Transaktionsseparationsstufe (wie viel Lesephänomen ist zulässig) -Dieses Mal ermöglicht REPEATABLE-READ Phantom-Lesevorgänge. ・ Ist die ACID-Eigenschaft garantiert? (Ich denke, dass die Unabhängigkeit davon abhängt, was Sie machen) ・ Ist die Leitung richtig verriegelt? ・ Gibt es ein totes Schloss?

Bei der Betrachtung der Systemebene und der Codeebene sind noch weitere Punkte zu berücksichtigen, z Ich habe die Anerkennung dieser Renovierung kurz zusammengefasst.

Um ehrlich zu sein, ist mein Verständnis immer noch nicht eindeutig, deshalb würde ich gerne weiter hart lernen! Als ich diesmal versuchte, einen Artikel wie diesen zu schreiben, wurde mir klar, dass ich weniger bewusst war, als ich erwartet hatte Ich möchte diese Ausgabe in Zukunft positiver gestalten!

Recommended Posts

Transaktionen, Sperren und doppelte Abrechnung
Über Ruby einfache Anführungszeichen und doppelte Anführungszeichen