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_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
ing 所有返回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
进程时,它们似乎都没有出现峰值或有困难。
尝试过的建议:
我们尝试将 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=256M
和innodb_log_file_size=64M
。结果没有任何变化。
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 但没有提交的主要内容,如果未能解决你的问题,请参考以下文章