php - Транзакции CodeIgniter-trans_status и trans_complete возвращают true, но ничего не фиксируется




mysql (4)

Проблема:

Я написал функцию в моей модели, чтобы вставить заказ в мою базу данных. Я использую транзакции, чтобы убедиться, что все зафиксировано, иначе оно будет откатано.

Моя проблема в том, что CodeIgniter не показывает никаких ошибок базы данных, однако он откатывает транзакцию, но затем возвращает TRUE для trans_status . Однако это происходит только при наличии скидки на заказ. Если на заказ скидки нет, все фиксируется и работает правильно.

В настоящее время я использую CodeIgniter 3.19, PHP (7.2), mySQL (5.7) и Apache 2.4. (Работает на Ubuntu 18.04)

Функциональная логика работает так:

  • Вставляет массив заказов в tbl_orders
  • Сохраняет order_id , просматривает каждый из продуктов заказа (прикрепляет order_id ) и вставляет продукт в tbl_order_products ,
  • Сохраняет order_product_id и присоединяет его к массиву опций посещаемости пользователей и вставляет их в tbl_order_attendance
  • Принимает массив платежных транзакций (присоединяет order_id ) и вставляет его в tbl_transactions
  • Если на заказ есть скидка , она уменьшает discount_redeem_count (количество подлежащих обмену кодов скидок) на 1.

Актуальная функция

[Функция]:

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

Когда эта функция завершается со скидкой , trans_complete и trans_status возвращают TRUE . Однако сделка никогда не совершается.

Что я пробовал:

  • Я сбросил содержимое $this->db->error() после каждого запроса, и ни в одном из запросов нет ошибок.

  • Я использовал this->db->last_query() чтобы распечатать каждый запрос, а затем проверил синтаксис онлайн, чтобы увидеть, были ли какие-то проблемы, их не было.

  • Я также попытался перейти на использование транзакций CodeIgniters вручную, например:

[Пример]

$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;
}
  • Я попытался echo и var_dump всех возвращаемых insert_ids и они все работают, я также insert_ids них insert_ids affected_rows() запроса UPDATE и он показывает, что 1 строка была обновлена. Тем не менее, все еще ничего не совершается

[Значения сброшены]

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

- Я также попытался заменить последний запрос UPDATE совершенно другим, который пытается обновить другую таблицу с другими значениями. Этот запрос ТАКЖЕ не сработал, что заставляет меня думать, что я достиг своего рода ограничения памяти при транзакции. Однако при мониторинге процессов mysqld ни один из них, похоже, не зашкаливает и не испытывает затруднений.

  • Я попытался отправить заказ, который не имеет скидки, и весь процесс работает! Что приводит меня к мысли, что моя проблема связана с моим запросом ОБНОВЛЕНИЯ. [После обновления:] Но похоже, что запрос на обновление работает также.

Попытки предложения:

  • Мы попытались установить log_threshold 4, и просмотрели файлы журналов CodeIgniter, которые не показывают историю отката.

  • Мы проверили журнал запросов MySQL:

[Журнал запросов]

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

Это показывает, что команда QUIT отправляется непосредственно после запроса UPDATE . Это может инициировать откат, однако trans_status возвращает TRUE .

Я также изменил свой файл my.cnf для mySQL, чтобы он имел innodb_buffer_pool_size=256M и innodb_log_file_size=64M . Там не было никаких изменений в результате.

  • Как рекомендовано @ebcode, я изменил запрос UPDATE чтобы использовать simple_query() вместо использования методов по умолчанию из класса Query Builder CodeIgniter:

[Простой запрос]

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

Однако это не повлияло на результат по-другому.

Если у вас есть идея, которую я еще не попробовал или вам нужна дополнительная информация, пожалуйста, прокомментируйте, и я отвечу быстро.

Вопрос:

Почему trans_status возвращает TRUE если ни одна из моих транзакций не была зафиксирована?

Чтобы попытаться прояснить ситуацию с пользователями, которые только что нашли этот вопрос, последние обновления к сообщению будут выделены курсивом *


(Оба эти предложения были опробованы, но безрезультатно.)

Предложение 1

Возможно, это реальный ответ:

trans_status () должен быть запущен, когда вы находитесь внутри транзакции. В вашем примере trans_complete () сбрасывает флаг состояния.

(Тем не менее, это печально, если вы используете Galera или Group Replication, поскольку транзакция все еще может завершиться неудачей при запуске COMMIT .)

Предложение 2

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

Обратите внимание, как вы используете «тройной» !== для некоторых тестов против NULL, но используйте «двойной» == для других.

Сделайте это, чтобы увидеть, что вы на самом деле получаете:

var_dump($result['webcast_capacity']);

Во-первых, вы должны обязательно включить режим Transaction Strict перед началом транзакций.

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

Во-вторых, пожалуйста, проверьте переменную $ order. это массив? или класс? если это класс, то, вероятно, не удалось в этой строке

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

В-третьих, если переменная $ order является классом, эта строка будет выполнена успешно. Если переменная $ order является массивом, эта строка не будет выполнена.

$discount = $order->get_discount();


На основании EDIT 5 :

это

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

должен работать (обратный тикет не будет .. весь смысл передачи false третьего параметра заключается в том, что CI не экранирует ваши параметры с помощью обратных галочек, что препятствует передаче оператора set в виде строки.

Я провел несколько быстрых тестов в своем собственном коде разработки с обновлением, похожим на это, и единственный способ заставить его выйти из строя - это изменить обновляемую таблицу так, чтобы поле ( discount_redeem_count в вашем случае) не было числовым. Если бы моим полем был, например, VARCHAR, он бы не работал, но когда я попробовал его в поле INT, он работал без проблем.

Вы уверены, что поле discount_redeem_count является числовым?


Я думаю о названии поля базы данных в поле discount_redeem_count .

Вы уверены, что discount_redeem_count не является номером? потому что здесь вы пытаетесь выдвинуть строковое значение. Поэтому поле базы данных должно быть var или text.

Может быть полезно.

Благодарю.





codeigniter