CodeIgniter源码分析 7.3 - 数据库驱动之事务处理
事务处理
事务处理有两种方式:手动和自动;
事务的自动处理避免了我们根据执行结果进行手动的 rollback/commit,简化了我们处理事务的流程!
事务处理主要由下面几个函数组成:
自动处理:trans_start() , trans_complete()
手动处理:trans_begin() ,trans_rollback(),trans_commit();
自动处理
trans_start
public function trans_start($test_mode = FALSE)
{
//首先判断事务是否开启,那为什么要判断事务是否开启呢?是因为 trans_off() 可以关闭事务
if ( ! $this->trans_enabled)
{
return;
}
// 事务有可能是嵌套的,其实我们可以看出来,CI 框架里的事务嵌套只是对数字的处理而已,什么意思?
// 意思就是尽管你用如下的事务嵌套,可能你会认为它是每组提交一次,但是通过这里的代码我们看出,
// 它对事务的嵌套就是用了一个数字做了嵌套的层级的累加而已,所以我们有必要怀疑在后面 trans_complete
// 中它会递减这个 _trans_depth,然后将所谓嵌套的事务一次性提交
/*
trans_start
trans_start
sql....
trans_complete
trans_complete
*/
if ($this->_trans_depth > 0)
{
$this->_trans_depth += 1;
return;
}
// 多层事务嵌套只做一次 trans_begin(),这个 trans_begin() 位于每个驱动中!
// $test_mode 在文档中被叫做 '测试模式',如果为 true 传入trans_begin 函数后会导致 _trans_failure 这个变量为true,
// 而这个 _trans_failure 用在事务提交函数 trans_complete() 中会导致事务回滚,文档中也说了 :
// 你可以选择性的将你的事务系统设置为 “测试模式”,这将导致你的所有 查询都被回滚,就算查询成功执行也一样
$this->trans_begin($test_mode);
$this->_trans_depth += 1;
}
通过分析这段代码可以看到事务的嵌套处理原来只是对层级的数字累计而已,只是调用了一次真实的事务处理,并且我们也看到事务的 '自动处理' 在内部其实调用了手动处理事务的函数 (trans_begin())!
trans_complete
public function trans_complete()
{
//还是判断事务是否可用
if ( ! $this->trans_enabled)
{
return FALSE;
}
//看到没,事务的提交真的将 _trans_depth 递减,并且当 _trans_depth=0 时会提交事务
if ($this->_trans_depth > 1)
{
$this->_trans_depth -= 1;
return TRUE;
}
else
{
$this->_trans_depth = 0;
}
//判断 sql 的执行结果,如果执行失败(query() 函数中 sql 失败会修改 _trans_status 的状态),则回滚事务
if ($this->_trans_status === FALSE OR $this->_trans_failure === TRUE)
{
$this->trans_rollback();
// trans_strict 用来控制嵌套的多组事务的回滚机制,一旦 trans_strict=true,我们看到 _trans_status 会变成true,
// 那么非常肯定的是当下组事务如果 sql 执行成功的话,一定会提交事务;
// 对于 trans_strict 其实在文档中也有说明,叫做事务的 ’严格模式‘,具体见:https://codeigniter.org.cn/user_guide/database/transactions.html
if ($this->trans_strict === FALSE)
{
$this->_trans_status = TRUE;
}
log_message('debug', 'DB Transaction Failure');
return FALSE;
}
$this->trans_commit();
return TRUE;
}
事务提交的源码中,我们看到对嵌套事务像剥洋葱一样,处理到最外层时则会提交事务;同时也看到对于嵌套的多组事务通过事务的 严格模式 控制了它们的回滚机制!
手动处理
在看事务的手动处理之前,我们有必要说下数据库事务 自动提交 (autocommit)!
那什么是事务的自动提交?
在 mysql 中每个查询都被当做一个单独的事务自动执行,也就是说每个执行的 sql 都会被当成一次事务,这就叫事务的自动提交!mysql 默认是开启了事务的自动提交。
我们使用如下的命令可以查看事务是否开启了自动提交:
image.png那我们就有个疑问?
既然每条执行的 sql 都启用了事务的自动提交的话,那包在多组 sql 外的事务组岂不是鸡肋?如果不是,又是如何处理事务的呢?
trans_begin
public function trans_begin($test_mode = FALSE)
{
// 如果 _trans_depth 不为0,那就是继续向外剥嵌套的事务呗
if ( ! $this->trans_enabled OR $this->_trans_depth > 0)
{
return TRUE;
}
// $test_mode 造成的影响我们在分析 trans_start() 函数时说了:
//测试模式会导致事务回滚,而事务提交中就是依靠这个 _trans_failure 来决定是否回滚事务
$this->_trans_failure = ($test_mode === TRUE);
//原来在处理事务提交时,将 sql 事务的自动提交给关了
$this->conn_id->autocommit(FALSE);
return is_php('5.5')
? $this->conn_id->begin_transaction()
: $this->simple_query('START TRANSACTION'); // can also be BEGIN or BEGIN WORK
}
通过分析上面的开启事务的源码我们发现,原来框架事务处理中先将 事务的自动提交给关了,这样事务的提交就只能靠手动方式了,也就是 trans_commit() 了!
trans_commit
public function trans_commit()
{
// When transactions are nested we only begin/commit/rollback the outermost ones
if ( ! $this->trans_enabled OR $this->_trans_depth > 0)
{
return TRUE;
}
//提交事务,并将事务状态设置为默认状态
if ($this->conn_id->commit())
{
$this->conn_id->autocommit(TRUE);
return TRUE;
}
return FALSE;
}
至此整个数据库驱动的源码分析就结束了,关于本节事务 自动提交 的还有个问题有必要说下:
你有没有觉得当我们往数据库写入100万条数据时,如果每条 sql 都 commit 一次事务会给数据库带来性能问题?如果你有兴趣你可以测试一下100万条数据 '加事务的入库' 和 '不加事务入库' 的区别,有惊喜哦!