it-swarm.com.de

CodeIgniter-Transaktionen - trans_status und trans_complete geben true zurück, es wird jedoch nichts festgeschrieben

Problem:

Ich habe in meinem Modell eine Funktion geschrieben, um eine Bestellung in meine Datenbank einzufügen. Ich verwende Transaktionen, um sicherzustellen, dass alles festgeschrieben wird, sonst wird es zurückgesetzt. 

Mein Problem ist, dass CodeIgniter keine Datenbankfehler anzeigt, die Transaktion jedoch rückgängig gemacht wird und dann TRUE für trans_status zurückgegeben wird. Dies geschieht jedoch nur dann, wenn die Bestellung einen Rabatt erhält. Wenn es keinen Rabatt auf die Bestellung gibt, wird alles gutgeschrieben und funktioniert einwandfrei.

Derzeit verwende ich CodeIgniter 3.19, PHP (7.2), mySQL (5.7) und Apache 2.4. (Arbeiten an Ubuntu 18.04)

Die Funktionslogik funktioniert so:

  • Fügt das Order Array in tbl_orders ein.
  • Speichert order_id und durchläuft jedes Bestellprodukt (fügt order_id hinzu) und fügt das Produkt in tbl_order_products ein, 
  • Speichert order_product_id und fügt es einer Reihe von Benutzeroptionen für Benutzer hinzu und fügt diese in tbl_order_attendance ein.
  • Nimmt das Zahlungsvorgangsfeld (fügt den order_id hinzu) und fügt dieses in tbl_transactions ein.
  • WENN es einen Rabatt auf die Bestellung gibt , verringert es den discount_redeem_count (Anzahl der einlösbaren Rabattcodes) um 1 .

Aktuelle Funktion

[Funktion]: 

public function add_order(Order $order, array $order_products, Transaction $transaction = NULL){
  $this->db->trans_start();

  $order->create_order_code();
  $order_array = $order->create_order_array();

  $this->db->insert('tbl_orders', $order_array);
  $order_id = $this->db->insert_id();
  $new_order = new Order($order_id);

  foreach($order_products as $key=>$value){
    $order_products[$key]->set_order($new_order);
    $order_product_array = $order_products[$key]->create_order_product_array();

    $this->db->insert('tbl_order_products', $order_product_array);
    $order_product_id = $this->db->insert_id();

    $product = $order_products[$key]->get_product();

    switch ($product->get_product_class()){
        case 'Iteration':
            $this->db->select('module_id, webcast_capacity, in_person_capacity');
            $this->db->from('tbl_modules');
            $this->db->where('iteration_id', $product->get_product_class_id());
            $results = $this->db->get()->result_array();
            break;
        case 'Module':
            $this->db->select('module_id, webcast_capacity, in_person_capacity');
            $this->db->from('tbl_modules');
            $this->db->where('module_id', $product->get_product_class_id());
            $results = $this->db->get->result_array();
            break;
      }

      if(!empty($results)){
        foreach($results as $result){
        $module_id = $result['module_id'];

        if($result['webcast_capacity'] !== NULL && $result['in_person_capacity'] !== NULL){
          $attendance_method = $order_products[$key]->get_attendance_method();
        }elseif($result['webcast_capacity'] !== NULL && $result['in_person_capacity'] === NULL){
          $attendance_method = 'webcast';
        }elseif($result['webcast_capacity'] === NULL && $result['in_person_capacity'] !== NULL){
          $attendance_method = 'in-person';
        }

        $order_product_attendance_array = array(
          'order_product_id' => $order_product_id,
          'user_id' => $order_products[$key]->get_customer(true),
          'module_id' => $module_id,
          'attendance_method' => $attendance_method,
        );

        $order_product_attendance[] = $order_product_attendance_array;
      }
      $this->db->insert_batch('tbl_order_product_attendance', $order_product_attendance);
    }

    if(!empty($order_products[$key]->get_discount())){
      $discount = $order_products[$key]->get_discount();
    }
  }

  if(!empty($transaction)){
    $transaction->set_order($new_order);
    $transaction_array = $transaction->create_transaction_array();
    $this->db->insert('tbl_transactions', $transaction_array);
    $transaction_id = $this->db->insert_id();
  }

  if(!empty($discount)){
    $this->db->set('discount_redeem_count', 'discount_redeem_count-1', false);
    $this->db->where('discount_id', $discount->get_discount_id());
    $this->db->update('tbl_discounts');
  }

  if($this->db->trans_status() !== false){
    $result['outcome'] = true;
    $result['insert_id'] = $order_id;
    return $result;
  }else{
    $result['outcome'] = false;
    return $result;
  }
}

Wenn diese Funktion mit einem Rabatt abgeschlossen hat, geben sowohl trans_complete als auch trans_statusTRUE zurück. Die Transaktion wird jedoch niemals festgeschrieben.

Was ich probiert habe:

  • Ich habe den Inhalt von $this->db->error() nach jeder Abfrage abgespeichert, und in keiner der Abfragen sind Fehler enthalten.

  • Ich habe this->db->last_query() verwendet, um jede Abfrage auszudrucken, und dann die Syntax online überprüft, um festzustellen, ob Probleme aufgetreten sind.

  • Ich habe auch versucht, auf CodeIgniters Manual Transactions zu wechseln:

[Beispiel]

$this->db->trans_begin();
 // all the queries
if($this->db->trans_status() !== false){
    $this->db->trans_commit();
    $result['outcome'] = true;
    $result['insert_id'] = $order_id;
    return $result;
}else{
    $this->db->trans_rollback();
    $result['outcome'] = false;
    return $result;
}
  • Ich habe versucht, echoing und var_dumping alle Rückkehr insert_ids auszuführen, und sie funktionieren alle. Ich habe auch die affected_rows() der UPDATE-Abfrage ausgegeben und es wird angezeigt, dass 1 Zeile aktualisiert wurde. Es wird jedoch noch nichts begangen:

[Werte entladen]

int(10) // order_id
int(10) // order_product_id
array(3) { 
    ["module_id"]=> string(1) "1" 
    ["webcast_capacity"]=> string(3) "250" 
    ["in_person_capacity"]=> string(3) "250" } // $results array (modules)

array(1) { 
    [0]=> array(4) { 
        ["order_product_id"]=> int(10 
        ["user_id"]=> string(1) "5" 
        ["module_id"]=> string(1) "1" 
        ["attendance_method"]=> string(7) "webcast" } } // order_product_attendance array

int(9) // transaction_id
int(1) // affected rows
string(99) "UPDATE `tbl_discounts` 
            SET discount_redeem_count = discount_redeem_count- 1 
            WHERE `discount_id` = 1" // UPDATE query

- Ich habe auch versucht, die letzte UPDATE-Abfrage durch eine völlig andere zu ersetzen, die versucht, eine andere Tabelle mit anderen Werten zu aktualisieren. Diese Abfrage funktionierte auch nicht, weshalb ich denke, dass ich mit der Transaktion eine gewisse Speichergrenze erreicht habe. Beim Überwachen von mysqld-Prozessen scheint jedoch keiner von ihnen Schwierigkeiten zu haben oder Schwierigkeiten zu haben.

  • Ich habe versucht, eine Bestellung zu übermitteln, die keinen Rabatt hat und der gesamte Prozess funktioniert! Das lässt mich glauben, dass mein Problem in meiner UPDATE-Abfrage liegt. [Nach dem Update:] Die Update-Abfrage scheint jedoch auch zu funktionieren.

Vorschläge ausprobiert:

  • Wir haben versucht, log_threshold auf 4 zu setzen, und haben die CodeIgniter-Protokolldateien durchgesehen, die keinen Verlauf eines Rollbacks anzeigen. 

  • Wir haben das MySQL Query Log geprüft:

[Abfrageprotokoll]

2018-12-03T15:20:09.452725Z         3 Query     UPDATE `tbl_discounts` SET discount_redeem_count = discount_redeem_count-1 WHERE `discount_id` = '1'
2018-12-03T15:20:09.453673Z         3 Quit

Es zeigt, dass ein QUIT-Befehl direkt nach der UPDATE-Abfrage gesendet wird. Dies würde einen Rollback auslösen, der trans_status gibt jedoch TRUE zurück.

Ich habe auch meine my.cnf-Datei für mySQL geändert, um innodb_buffer_pool_size=256M und innodb_log_file_size=64M zu haben. Am Ergebnis hat sich nichts geändert. 

  • Als @ebcode empfohlen, habe ich die UPDATE-Abfrage dahingehend geändert, dass anstelle der Standardmethoden der CodeIgniter Query Builder-Klasse eine simple_query() verwendet wird:

[Einfache Abfrage]

if(!empty($discount)){
    $this->db->simple_query('UPDATE `tbl_discounts` SET '.
    'discount_redeem_count = discount_redeem_count-1 WHERE '.
    '`discount_id` = \''.$discount['discount_id'].'\'');
}

Dies hat sich jedoch nicht anders auf das Ergebnis ausgewirkt .

Wenn Sie eine Idee haben, die ich noch nicht ausprobiert habe, oder weitere Informationen von mir benötigen, kommentieren Sie dies bitte und ich werde umgehend antworten.

Frage:

Warum gibt trans_statusTRUE zurück, wenn keine Transaktion festgeschrieben wird?

Um zu versuchen, den Benutzern etwas Klarheit zu vermitteln, die gerade jetzt diese Frage finden, werden die neuesten Aktualisierungen des Beitrags in Kursivschrift * angezeigt.

16
adamoffat

Ich habe mein Problem gefunden. Ich möchte mich bei allen bedanken, die versucht haben zu helfen, dies war meine Schuld.

Früher in der Controller-Methode, die diese Funktion aufruft, habe ich eine andere Funktion aufgerufen, die eine Transaktion startet. Diese Transaktion wurde nie abgeschlossen und wurde daher in dieser neuen Transaktion fortgesetzt. 

Da die Transaktion nur nicht ausgeführt wurde und keine Fehler aufgetreten sind, konnte ich keine Fehler oder einen Verlauf eines Rollbacks finden. Sobald ich jedoch die vorherige Transaktion abgeschlossen hatte, funktionierte alles.

Es gab keine Hinweise auf Probleme im mySQL-Abfrageprotokoll, im MySQL-Fehlerprotokoll oder in den CodeIgniter-Fehlerprotokollen. Ich konnte dieses Problem nur durch langsames Durchlesen des gesamten mySQL-Abfrageprotokolls finden.

Für alle, die auf dieses Problem stoßen: Überprüfen Sie Ihre anderen Transaktionen und stellen Sie sicher, dass sie alle geschlossen sind.

4
adamoffat

Basierend auf EDIT 5 :

Diese

$this->db->set('discount_redeem_count', 'discount_redeem_count-1', false);

sollte funktionieren (der Back-ticked-Befehl würde nicht .. der dritte Punkt des Übergebens des false-Parameters ist so, dass CI Ihre Parameter nicht mit Backticks entgeht, wodurch die set-Anweisung nicht als String übergeben wird.

Ich habe ein paar schnelle Tests in meinem eigenen Entwicklungscode mit einem ähnlichen Update durchgeführt. Die einzige Möglichkeit, die fehlgeschlagen war, bestand darin, die zu aktualisierende Tabelle so zu ändern, dass das Feld (discount_redeem_count in Ihrem Fall) nicht numerisch war. Wenn mein Feld zum Beispiel ein VARCHAR wäre, würde es nicht funktionieren, aber als ich es auf einem INT-Feld ausprobierte, funktionierte es ohne Probleme. 

Sind Sie sicher, dass das Feld discount_redeem_count numerisch ist?

0

Stellen Sie zunächst sicher, dass der Transaktionsstrikt-Modus aktiviert ist, bevor Sie Transaktionen starten.

$this->db->trans_strict(TRUE);
$this->db->trans_start();

Zweitens überprüfen Sie bitte die $ order Variable. Ist das ein Array? oder eine klasse? Wenn dies eine Klasse ist, ist sie wahrscheinlich in dieser Zeile fehlgeschlagen

$this->db->insert('tbl_orders', $order);

Drittens ist diese Zeile erfolgreich, wenn $ order variable eine Klasse ist. Wenn $ order variable ein Array ist, schlägt diese Zeile fehl.

$discount = $order->get_discount();

0
McBern

(Beide Vorschläge wurden versucht, ohne Erfolg.)

Vorschlag 1

Vielleicht ist das die wirkliche Antwort:

trans_status () muss ausgeführt werden, wenn Sie sich in einer Transaktion befinden. In Ihrem Beispiel setzt trans_complete () das Statusflag zurück.

(Dies ist jedoch traurig, wenn Sie Galera oder die Gruppenreplikation verwenden, da bei der Ausführung von COMMIT noch eine Transaktion fehlschlagen kann.)

Vorschlag 2

These are  `== NULL`:  NULL, '', FALSE, 0
These are `!== NULL`:        '', FALSE, 0

Beachten Sie, wie Sie den "dreifachen" !== für einige Tests gegen NULL verwenden, verwenden Sie den "doppelten" == für andere.

Tun Sie dies, um zu sehen, was Sie tatsächlich bekommen:

var_dump($result['webcast_capacity']);
0
Rick James

Versuchen Sie vielleicht, Ihren Aktualisierungscode durch einen Aufruf von simple_query zu ersetzen.

Veränderung:

if(!empty($discount)){
    $this->db->set('discount_redeem_count', 'discount_redeem_count-1', false);
    $this->db->where('discount_id', $discount['discount_id']);
    $this->db->update('tbl_discounts');
}

Zu:

if(!empty($discount)){
    $this->db->simple_query('UPDATE `tbl_discounts` SET '.
    'discount_redeem_count = discount_redeem_count-1 WHERE '.
    '`discount_id` = \''.$discount['discount_id'].'\'');
}

Ich habe ein bisschen in den CodeIgniter-Quellcode gestoßen, und es sieht so aus, als würde die standardmäßige Abfragefunktion eine Menge Housekeeping-Funktionen ausführen, die möglicherweise alles durcheinanderbringen. Und die simple_query-Funktion hat dieses bisschen Dokumentation:

/**
 * Simple Query
 * This is a simplified version of the query() function. Internally
 * we only use it when running transaction commands since they do
 * not require all the features of the main query() function.
 *
 * @param   string  the sql query
 * @return  mixed
 */
0
ebcode

Ich denke an Ihr Datenbankfeld discount_redeem_count name. 

Sind Sie sicher, dass discount_redeem_count keine Nummer ist? weil Sie hier versuchen, einen String-Wert zu drücken. Das Datenbankfeld sollte also var oder text sein.

Vielleicht hilfreich 

Vielen Dank. 

0
Jayesh Naghera