需求的描述是有一个数组,循环处理数组数据,写入并更新数据库,写入+更新
为一组数据处理,想要达到的效果是循环中如果某一组数据写入/更新失败,回滚当前组的数据库操作;
以此需求为前提写了下面这段事务处理代码,但总觉得这样写不合适
$this->db->trans_begin();
foreach ($record_data as $key => $value) {
/**
* 更新数据库操作
**/
若干代码.......
/**
* 插入数据库操作
**/
若干代码.......
if ($this->db->trans_status() === FALSE) {
$this->db->trans_rollback();
} else {
$this->db->trans_commit();
// 这里需要记录当前组操作成功的
$ok_ids[] = $value['id'];
$user_monetary[$value['user_id']] = $diff;
}
}
上面这段代码是一段典型的CI框架手动开启数据库事务的例子,有几个纠结/疑惑的点:
-
我把开启事务放在循环之前,把执行事务处理的部分放在代码里,可以把
更新+插入
当做一组数据处理,如果我循环执行到中间,有一组数据执行失败,触发了回滚,会不会把我之前的已经处理过的数据也回滚掉? -
如果我把开启事务放在循环体里,因为每次进入循环体都要开启事务,然后进行事务处理,会不会影响执行效率,每次循环次数在
100~500
之间不等;
由于上面的疑惑写了一段测试代码:
$ins_data = [
'monetary' => 202,
'consume_time' => 20170798,
];
$this->db->trans_begin();
for ($i=0; $i < 100; $i++) {
if($i == 50) {
$ins_data['user_id'] = 366750;
}else {
$ins_data['user_id'] = 3667;
}
$this->xxx_model->insert($ins_data); // 插入到数据库
if($i == 50) {
$this->db->trans_rollback();
}else {
$this->db->trans_commit();
}
}
运行这段代码的结果是:插入操作无一例外正常执行,366750
这条记录还是插入了数据库里面,并无产生回滚事件。
查阅资料:
在事务中,每个正确的原子操作都会被顺序执行,
直到遇到错误的原子操作
,此时事务会将之前的操作进行回滚。回滚的意思是如果之前是插入操作,那么会执行删 除插入的记录,如果之前是update操作,也会执行update操作将之前的记录还原。
因此,正确的原子操作是真正被执行过的。是物理执行。
看到说直到遇到错误的原子操作才能进行回滚
,为了验证这个,我放弃了在循环中执行事务,把代码改成了在循环体外执行事务的处理:
$ins_data = [
'monetary' => 202,
'consume_time' => 20170798,
];
$this->db->trans_begin();
for ($i=0; $i < 100; $i++) {
$this->xxx_model->insert($ins_data); // 插入到数据库
}
$this->db->trans_rollback();
// $this->db->trans_commit();
上面这段代码中,当最后操作rollback
的时候,数据库并不会插入数据,当是commit
的时候,数据库才会插入数据。那么这一现象就打翻了前面说的只有遇到错误的原子操作才能进行回滚
的陈述,这时候我就更懵了。
那么什么是事务?事务的触发条件是什么?符合我这种场景的数据操作该如何进行事务处理呢?还请路过的大神能够指点一下、抱拳~
我是这样认为的:
你的那段测试代码,在循环体内多次commit了。由于你的事务是在循环开始前就开启了,当循环体内第一次commit的时候,这个事务就结束了,所以后面执行的回滚是无效的。也就是说事务的开始和结束是配对的,区间的所有操作都是放在一个数据库连接中。可以这样改下程序,把提交放到循环结束后在提交,回滚不变: