Транзакции CodeIgniter - trans_status и trans_complete возвращают true, но ничего не фиксируется - PullRequest
0 голосов
/ 01 декабря 2018

Проблема:

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

Моя проблема заключается в том, что 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 ing и var_dump возвращая 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

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

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

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

[Simple Query]

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, если ни одна из моих транзакций не фиксируется?

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

Ответы [ 6 ]

0 голосов
/ 14 декабря 2018

Я нашел свою проблему.Я хочу поблагодарить всех, кто пытался помочь, однако, в этом была моя ошибка.

Ранее в методе Controller, который вызывает эту функцию, я вызывал другую функцию, которая запускает транзакцию.Эта транзакция никогда не была закрыта и поэтому продолжалась в этой новой транзакции.

Поскольку транзакция просто не была совершена и ошибок не было, я не смог найти ни ошибок, ни истории отката.Однако, как только я закрыл предыдущую транзакцию, все заработало.

Не было никаких свидетельств каких-либо проблем в журнале запросов mySQL, журнале ошибок mySQL или журналах ошибок CodeIgniter.Я смог найти эту проблему, только медленно прочитав весь журнал запросов MySQL.

Для всех, кто сталкивался с этой проблемой: Проверьте остальные транзакции и убедитесь, что они все закрыты.

0 голосов
/ 11 декабря 2018

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

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

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

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

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

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

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

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

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

var_dump($result['webcast_capacity']);
0 голосов
/ 05 декабря 2018

На основе РЕДАКТИРОВАТЬ 5 :

Это

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

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

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

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

0 голосов
/ 05 декабря 2018

Во-первых, перед началом транзакции вы должны обязательно включить режим «Строгое транзакции».

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

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

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

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

$discount = $order->get_discount();

0 голосов
/ 04 декабря 2018

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

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

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

Спасибо.

0 голосов
/ 04 декабря 2018

Может быть, попытаться заменить ваш код обновления вызовом simple_query?

Изменить:

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

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

/**
 * 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
 */
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...