web-dev-qa-db-fra.com

Transactions CodeIgniter - trans_status et trans_complete renvoient true mais rien n'est commis

Problème:

J'ai écrit une fonction dans mon modèle pour insérer une commande dans ma base de données. J'utilise des transactions pour m'assurer que tout est valide, sinon il sera annulé. 

Mon problème est que CodeIgniter ne montre aucune erreur de base de données, mais il annule la transaction, mais renvoie ensuite TRUE pour trans_status. Cependant, cela ne se produit que s'il y a une réduction sur la commande. S'il n'y a pas de réduction sur la commande, tout est validé et fonctionne correctement.

J'utilise actuellement CodeIgniter 3.19, PHP (7.2), MySQL (5.7) et Apache 2.4. (Travailler sur Ubuntu 18.04)

La logique de fonction fonctionne comme telle:

  • Insère le tableau d'ordre dans tbl_orders
  • Enregistre order_id et parcourt chacun des produits de la commande (joint order_id) et insère le produit dans tbl_order_products
  • Enregistre order_product_id et l'attache à un tableau d'options de présence des utilisateurs et l'insère dans tbl_order_attendance
  • Prend le tableau de transaction de paiement (attache le order_id) et l'insère dans tbl_transactions
  • SI il y a un rabais sur la commande , le discount_redeem_count (nombre de codes de rabais échangeables) est diminué de 1 .

Fonction réelle

[Une fonction]: 

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;
  }
}

Lorsque cette fonction termine avec une remise , trans_complete et trans_status renvoient TRUE. Cependant, la transaction n'est jamais engagée.

Ce que j'ai essayé

  • J'ai vidé le contenu de $this->db->error() après chaque requête et il n'y a aucune erreur dans aucune des requêtes.

  • J'ai utilisé this->db->last_query() pour imprimer chaque requête, puis j'ai vérifié la syntaxe en ligne pour voir s'il y avait des problèmes, il n'y en avait pas.

  • J'ai également essayé de changer pour utiliser CodeIgniters Manual Transactions comme:

[Exemple]

$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;
}
  • J'ai essayé echoing et var_dumping tous les return insert_ids et ils fonctionnent tous, j'ai également sorti le affected_rows() de la requête UPDATE et il montre qu'une ligne a été mise à jour. Cependant, rien n'est encore commis:

[Valeurs Dumpées]

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

- J'ai également essayé de remplacer la dernière requête UPDATE par une requête complètement différente qui tente de mettre à jour une table différente avec des valeurs différentes. Cette requête AUSSI n'a pas fonctionné, ce qui me fait penser que je suis en train d'atteindre une sorte de limite de mémoire avec la transaction. Toutefois, lors de la surveillance des processus mysqld, aucun d’entre eux ne semble avoir de difficulté ni de difficulté.

  • J'ai essayé de soumettre une commande qui n'a pas de réduction et l'ensemble du processus fonctionne! Ce qui me porte à croire que mon problème vient de ma requête UPDATE. [After Update:] Mais il semble que la requête de mise à jour fonctionne également.

Suggestions Essayées:

  • Nous avons essayé de définir log_threshold sur 4 et avons examiné les fichiers journaux CodeIgniter qui ne montrent aucun historique de restauration. 

  • Nous avons vérifié le journal de requêtes mySQL:

[Journal de requête]

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

Cela montre qu'une commande QUIT est envoyée directement après la requête UPDATE. Cela initierait une restauration, cependant le trans_status renvoie TRUE.

J'ai également changé mon fichier my.cnf pour mySQL afin qu'il ait innodb_buffer_pool_size=256M et innodb_log_file_size=64M. Il n'y a pas eu de changement dans le résultat. 

  • En tant que @ebcode recommandé, j'ai modifié la requête UPDATE pour utiliser un simple_query() au lieu d'utiliser les méthodes par défaut de la classe Query Builder Class de CodeIgniter:

[Requête simple]

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

Cependant, cela n'a pas affecté le résultat différemment .

Si vous avez une idée que je n'ai pas encore essayée ou si vous avez besoin de plus d'informations de ma part, merci de commenter et je vous répondrai rapidement.

Question:

Pourquoi trans_status renvoie-t-il TRUE si aucune de mes transactions n'est en cours de validation?

Afin d'essayer d'apporter plus de clarté aux utilisateurs qui viennent de trouver cette question maintenant, les dernières mises à jour du message apparaîtront en italique *

16
adamoffat

J'ai trouvé mon problème. Je tiens à remercier toutes les personnes qui ont essayé d’aider, mais celle-ci était de ma faute.

Auparavant, dans la méthode Controller qui appelle cette fonction, j'ai appelé une autre fonction qui démarre une transaction. Cette transaction n’a jamais été finalisée et a donc été reprise dans cette nouvelle transaction. 

Comme la transaction n’était tout simplement pas validée et qu’il n’y avait pas d’erreur, je n’ai trouvé aucune erreur ni historique d’annulation. Cependant, dès que j'ai clôturé la transaction précédente, tout a fonctionné.

Il n'y avait aucune preuve de problèmes dans le journal de requête mySQL, le journal des erreurs mySQL ou les journaux des erreurs CodeIgniter. Je n'ai pu trouver ce problème qu'en lisant lentement l'intégralité du journal de requête mySQL.

Pour tous ceux qui rencontrent ce problème: Vérifiez vos autres transactions et assurez-vous qu'elles sont toutes clôturées.

4
adamoffat

Basé sur EDIT 5 :

Ce

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

devrait fonctionner (la coche en arrière ne ferait pas .. l’intérêt de passer le troisième paramètre false est de faire en sorte que CI n’échappe pas à vos paramètres avec des backticks, ce qui empêcherait que l’instruction set soit transmise sous forme de chaîne.

J'ai fait quelques tests rapides dans mon propre code de développement avec une mise à jour similaire à celle-ci et la seule façon de le faire échouer a été de modifier la table en cours de mise à jour pour que le champ (discount_redeem_count dans votre cas) ne soit pas numérique. Si mon domaine était, par exemple, un VARCHAR, cela ne fonctionnerait pas, mais lorsque je l'ai essayé sur un champ INT, cela fonctionnait sans problème. 

Etes-vous certain que le champ discount_redeem_count est numérique?

0
Javier Larroulet

(Ces deux suggestions ont été essayées, en vain.)

Suggestion 1

Peut-être que c'est la vraie réponse:

trans_status () doit être exécuté lorsque vous êtes dans une transaction. Dans votre exemple, trans_complete () réinitialise l'indicateur d'état.

(Cependant, cela est triste si vous utilisez Galera ou une réplication de groupe, car une transaction peut toujours échouer lors de l'exécution de COMMIT.)

Suggestion 2

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

Remarquez comment vous utilisez le "triple" !== pour certains tests avec NULL, mais utilisez le "double" == pour les autres.

Faites ceci pour voir ce que vous obtenez réellement:

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

Peut-être essayez-vous de remplacer votre code de mise à jour par un appel à simple_query?

Changement:

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');
}

À:

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

J'ai jeté un coup d'œil autour du code source de CodeIgniter, et il semble que la fonction de requête par défaut effectue beaucoup de tâches ménagères qui risquent de tout gâcher. Et la fonction simple_query a ce peu de documentation:

/**
 * 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

Je pense à votre champ de base de données discount_redeem_count nom. 

Etes-vous sûr que discount_redeem_count n'est pas un numéro? car voici que vous essayez de pousser une valeur de chaîne. Donc, le champ de base de données doit être var ou text.

Peut-être utile. 

Merci. 

0
Jayesh Naghera