CodeIgniter 事务 - trans_status 和 trans_complete 返回 true 但没有提交

Posted

技术标签:

【中文标题】CodeIgniter 事务 - trans_status 和 trans_complete 返回 true 但没有提交【英文标题】:CodeIgniter Transactions - trans_status and trans_complete return true but nothing being committed 【发布时间】:2019-05-03 00:58:07 【问题描述】:

问题:

我在我的模型中编写了一个函数来向我的数据库中插入一个订单。我正在使用事务来确保所有内容都提交,否则它将被回滚。

我的问题是 CodeIgniter 没有显示任何数据库错误,但是它正在回滚事务,但随后为 trans_status 返回 TRUE。但是,只有在订单有折扣时才会发生这种情况。如果订单没有折扣,则一切都会提交并正常工作。

我目前正在使用 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_completetrans_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;

我已经尝试echoing 和var_dumping 所有返回insert_ids 并且它们都有效,我还输出了UPDATE 查询的affected_rows(),它显示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 进程时,它们似乎都没有出现峰值或有困难。

我已尝试提交没有折扣的订单,整个过程正常!这让我相信我的问题出在我的 UPDATE 查询上。 [更新后:]但似乎更新查询也可以正常工作。

尝试过的建议:

我们尝试将 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

它表明在UPDATE 查询之后直接发送了一个QUIT 命令。这将启动回滚,但是 trans_status 正在返回 TRUE

我还将 mySQL 的my.cnf 文件更改为具有innodb_buffer_pool_size=256Minnodb_log_file_size=64M。结果没有任何变化。

按照@ebcode 的建议,我将UPDATE 查询更改为使用simple_query(),而不是使用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

为了让刚刚发现这个问题的用户更清楚,帖子的最新更新将以斜体显​​示 *

【问题讨论】:

您检查了 $discount['discount_id'] 值吗? @Vickel 嘿,是的,我做到了。当我使用“$this->db->last_query()”时,它显示包含了“discount_id”值。 (在我的测试中,ID 值为 1) 您还没有在没有向我们展示的地方运行$this->db->trans_strict(FALSE); 吗? @DFriend 非常感谢您的评论!绝对不是,这是我检查的第一件事。 日志文件中也没有条目? 【参考方案1】:

我发现了我的问题。我想对所有试图提供帮助的人说声谢谢,但这是我的错。

在调用此函数的 Controller 方法的前面,我调用了另一个启动事务的函数。该交易从未关闭,因此继续进行此新交易。

因为事务没有提交并且没有错误,我无法找到任何错误或任何回滚历史。但是,一旦我关闭了之前的交易,一切都正常了。

在 mySQL 查询日志、mySQL 错误日志或 CodeIgniter 错误日志中没有任何问题的证据。我只能通过慢慢阅读整个mySQL查询日志才能发现这个问题。

对于遇到此问题的任何人:检查您的其他交易并确保它们都已关闭。

【讨论】:

不提交事务不是错误。因此,您不会在日志中发现错误。最好的方法是在开始事务时始终键入事务提交语句。然后在它们之间填充你的逻辑。这个错误很常见,我也不例外:)【参考方案2】:

也许尝试用调用 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
 */

【讨论】:

已尝试,问题已被编辑。不幸的是,这没有任何区别。感谢您的回答!【参考方案3】:

首先,您应该确保在开始交易之前打开 Transaction Strict 模式。

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

其次,请检查 $order 变量。这是一个数组吗?还是一堂课? 如果这是一个类,那么它可能在这一行失败了

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

第三,如果 $order 变量是一个类,那么这一行就会成功。如果 $order 变量是一个数组,那么这一行将失败。

$discount = $order->get_discount();

【讨论】:

嘿,起初我没有包含完整的代码,因为我认为我的对象可能会令人困惑,但是我现在看到相反的情况,我已经更新了我的问题以显示带有对象的完整代码.同样来自 CodeIgniter Docs:“默认情况下,CodeIgniter 以严格模式运行所有事务”。 我认为如果订单没有折扣,您的折扣对象可能会在第一次通过时失败。因为它没有初始化。 嘿!所以不是每个订单都会有折扣。但是,该功能会检查两次以确保在使用它进行更新之前有折扣。此外,当我使用$this->db->last_query() 检查 SQL 查询时,它会输出一个完整的语法查询,可以在 phpMyAdmin 中使用。【参考方案4】:

基于EDIT 5

这个

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

应该可以工作(反引号不会......传递false第三个参数的全部意义在于CI不会用反引号转义您的参数,这将阻止set语句作为字符串。

我在自己的开发代码中进行了一些快速测试,更新类似于那个,我让它失败的唯一方法是更改​​正在更新的表,以便字段(在你的情况下为discount_redeem_count)不是t 数字。例如,如果我的字段是 VARCHAR,它就不起作用,但是当我在 INT 字段上尝试它时,它没有问题。

您确定discount_redeem_count 字段是数字吗?

【讨论】:

您好,谢谢您的回答!我只尝试在 UPDATE 查询上插入反引号,正如我在 answer 上看到的那样。另外,我检查了 mySQL 字段数据类型,它是一个 INT 值。 无赖...我似乎找不到任何其他方法让它失败:(如果我这样做了,我会更新【参考方案5】:

我正在考虑您的数据库字段discount_redeem_count 名称。

你确定discount_redeem_count 不是数字吗?因为在这里你试图推送一个字符串值。所以数据库字段应该是var或者text。

也许有帮助。

谢谢。

【讨论】:

感谢您的回答!但是,我所有的其他外键都是使用相同的方法完成的。使用字符串从数据库中查询整数值以前从未给我带来任何问题。 不确定.. 可能是缓存问题,使用 $this->db->cache_delete_all();更新前查询。【参考方案6】:

(这两个建议都试过了,都无济于事。)

建议 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']);

【讨论】:

嘿那里,所以我已经倾倒了几乎所有东西的价值,这一切都在工作。插入的数组都返回insert_ids。但是,实际上并没有提交任何事务,trans_status 返回TRUE。我可以在问题中包含所有数组的转储,如果这样可以更清楚的话。 @adamoffat - trans_status 必须在之前调用 trans_complete。 (请参阅添加的文本。) 我在另一个答案中看到了这一点,但是CodeIgniters documentation 与之相矛盾。无论如何,我试图这样做,但函数的结果没有变化。 :( 您对比较运算符是正确的,我已经更新以确保它是一致的。我故意使用严格的比较。

以上是关于CodeIgniter 事务 - trans_status 和 trans_complete 返回 true 但没有提交的主要内容,如果未能解决你的问题,请参考以下文章

在 PHP / CodeIgniter 中,不止一个事务开始了,而另一个事务没有完成;比如嵌套事务

CodeIgniter - 使用事务插入多个表

事务无法在 codeigniter 中回滚

使用 CodeIgniter 事务?

函数内所有查询的 Codeigniter 事务

php Codeigniter事务#procedo