thinkphp3.2.3(5以下)的事务问题(事务回滚无效、多表事务等)
事务回滚无效
现象
public function test(){ $m = M('User'); $data=[ 3=> ['account'=>'300','password'=>'300','nickname'=>'300','autograph'=>'300','un'=>'309'], 5=> ['account'=>'100','password'=>'100','nickname'=>'100','autograph'=>'100','un'=>'305'], 8 =>['account'=>'100','password'=>'100','nickname'=>'100','autograph'=>'100','un'=>'306'] ]; $m->startTrans(); $pk=$m->add($data[3]); echo $pk; $m->rollback(); //$m->commit(); return; }
上述代码执行后数据库会被插入数据,回滚无效。根本原因是:实际上事务都没有开启。追查代码在ThinkPHP\Library\Think\Mode.class.php和ThinkPHP\Library\Think\Db\Dirver.class.php两个文件中。
在Dirver.class.php文件中,与事务相关的是:
// 事务指令数 protected $transTimes = 0; /** * 启动事务 * @access public * @return void */ public function startTrans() { $this->initConnect(true); if (!$this->_linkID) { return false; } //数据rollback 支持 if (0 == $this->transTimes) { // 记录当前操作PDO $this->transPdo = $this->_linkID; $this->_linkID->beginTransaction(); } $this->transTimes++; return; } /** * 用于非自动提交状态下面的查询提交 * @access public * @return boolean */ public function commit() { if ($this->transTimes == 1) { // 由嵌套事物的最外层进行提交 $result = $this->_linkID->commit(); $this->transTimes = 0; $this->transPdo = null; if (!$result) { $this->error(); return false; } } else{ $this->transTimes--; } return true; } / ** * 事务回滚 * @access public * @return boolean */ public function rollback() { if ($this->transTimes > 0) { $result = $this->_linkID->rollback(); $this->transTimes = 0; $this->transPdo = null; if (!$result) { $this->error(); return false; } } return true; }
在Model.class.php文件中,与事务相关的是:
/** * 启动事务 * @access public * @return void * */ public function startTrans() { $this->commit();//事务指令数归零 $this->db->startTrans(); return; } /** * 提交事务 * @access public * @return boolean */ public function commit() { return $this->db->commit(); } /** * 事务回滚 * @access public * @return boolean */ public function rollback() { return $this->db->rollback(); }
可以看到,在没有开启事务时, $this->transTimes == 0,但在Model.class.php文件中,开启事务时会先调一次commit()方法,本人猜测主要为了保证事务被提交,但这会让$this->transTimes == -1,这样在Dirver.class.php文件中,开启事务时根据$this->transTimes 的值判断是否真开启事务,显而易见-1不会开启,这样回滚自然无效了,解决办法如下:
将Dirver.class.php文件中commit()方法加一个else if判定,这样避免 $this->transTimes<0;
/** * 用于非自动提交状态下面的查询提交 * @access public * @return boolean */ public function commit() { if ($this->transTimes == 1) { // 由嵌套事物的最外层进行提交 $result = $this->_linkID->commit(); $this->transTimes = 0; $this->transPdo = null; if (!$result) { $this->error(); return false; } } else if($this->transTimes == 0){ //null }else{ $this->transTimes--; } return true; }
多表事务
现象
public function test(){ $User = M('User'); $Key = M('Key'); $User->startTrans(); // 开启事务 $Key->startTrans(); // 开启事务 $uid = $User->add(['name' => 'hongxuan']); $kid = $Key->add(['key'=>'test']);// $kid =M()->table('test_key')->add(['key'=>'test']); if ($uid && $kid) { // 插入成功 $User->rollback(); // 回滚 $Key->rollback(); // 回滚 // $User->commit(); // 提交 } else { // 添加失败 $User->rollback(); // 回滚 } }
回滚时,可以看到只对test_user有效,test_key回滚无效,使用下面的方法即可解决。
$Model = M(); // 实例化一个空对象
$Model->startTrans(); // 开启事务
$Model->table(‘test_user’)->add([‘name’=>‘admin’]);
$Model->table(‘test_key’)->add([‘key’=>‘test’]);
if (操作成功的条件) {
$Model->commit(); // 成功则提交事务
}else {
$Model->rollback(); // 否则将事务回滚
}
主要原因
在实例化模型(M/D方法)时,同时实例化两次及以上类,但事务指令均使用变量$this->transTimes,而在Dirver.class.php文件中开启事务,提交/回滚事务,都需要根据$this->transTimes的值进行判断,在+±-过程中就会出现问题,按上面的只将类实例化一次,可避免这种情况的发生,也就是说不支持所谓的嵌套事务
实现Mysql嵌套事务
参考博文。
- ThinkPHP\Library\Think\Model.class.php
代码如下,第1501行注释掉就好
/** * 启动事务 * @access public * @return void */ public function startTrans() { //$this->commit(); $this->db->startTrans(); return ; }
- ThinkPHP\Library\Think\Db\Dirver.class.php
代码如下:
/** * 启动事务 * @access public * @return void */ public function startTrans() { $this->transTimes++; $this->initConnect(true); if ( !$this->_linkID ) return false; //数据rollback 支持 if ($this->transTimes == 1) { $this->_linkID->beginTransaction(); } return ; } /** * 用于非自动提交状态下面的查询提交 * @access public * @return boolean */ public function commit() { if ($this->transTimes == 1) { $result = $this->_linkID->commit(); $this->transTimes = 0; if(!$result){ $this->error(); return false; } } else { --$this->transTimes; } return true; } /** * 事务回滚 * @access public * @return boolean */ public function rollback() { if ($this->transTimes == 1) { $result = $this->_linkID->rollback(); $this->transTimes = 0; if(!$result){ $this->error(); return false; } } else { --$this->transTimes; } return true; }
3、ThinlPHP\Library/Think/Db/Lite.class.php
代码如下:
/** * 启动事务 * @access public * @return void */ public function startTrans() { $this->transTimes++; $this->initConnect(true); if ( !$this->_linkID ) return false; //数据rollback 支持 if ($this->transTimes == 1) { $this->_linkID->beginTransaction(); } return ; } /** * 用于非自动提交状态下面的查询提交 * @access public * @return boolean */ public function commit() { if ($this->transTimes == 1) { $result = $this->_linkID->commit(); $this->transTimes = 0; if(!$result){ $this->error(); return false; } } else { --$this->transTimes; } return true; } /** * 事务回滚 * @access public * @return boolean */ public function rollback() { if ($this->transTimes == 1) { $result = $this->_linkID->rollback(); $this->transTimes = 0; if(!$result){ $this->error(); return false; } } else { --$this->transTimes; } return true; }
读写分离
参考读写分离处理处理办法。
现象
配置数据库读写分离后, 开启事务时报错:
ERR: There is no active transaction
解决办法
修改ThinkPHP/Library/Think/Db/Driver.class.php代码如下:
/** * 启动事务 * * @access public * @return void */ public function startTrans() { $this->initConnect(true); if ( !$this->_linkID ) return false; // 数据rollback 支持 if ($this->transTimes == 0) { //$this->_linkID->beginTransaction(); // by 52php.cnblogs.com foreach ($this->linkID as $_linkId) { $_linkId->beginTransaction(); } } $this->transTimes++; return ; } /** * 用于非自动提交状态下面的查询提交 * * @access public * @return boolean */ public function commit() { if ($this->transTimes > 0) { //$result = $this->_linkID->commit(); // by 52php.cnblogs.com foreach ($this->linkID as $_linkId) { $result = $_linkId->commit(); } $this->transTimes = 0; if(!$result){ $this->error(); return false; } } return true; } /** * 事务回滚 * * @access public * @return boolean */ public function rollback() { if ($this->transTimes > 0) { //$result = $this->_linkID->rollback(); // by 52php.cnblogs.com foreach ($this->linkID as $_linkId) { $result = $_linkId->rollback(); } $this->transTimes = 0; if(!$result){ $this->error(); return false; } } return true; }
- Thinkphp3.2.3设置session周期无效的问题
- spring@Transactional注解事务不回滚不起作用无效的问题处理
- MySql 执行事务无效无法回滚问题
- ssh 事务不能回滚的问题总结
- 解决thinkphp设置session周期无效的问题
- Spring事务处理异常回滚问题
- thinkphp自动验证无效的问题
- thinkphp 3.2.3配置路由无效,不起作用,去除默认路由Home
- java事务回滚失败问题分析
- 解决Hibernate的session.save/update/delete操作无效问题(事务管理)
- thinkphp设置session无效的问题
- Spring事务注解@Transactional回滚问题
- thinkphp事务处理无效时的解决办法,一击命中!
- MyBatis和SpringMVC集成事务在Junit测试下有效但是在实际项目无效的问题
- SSH spring事务管理不回滚问题
- ThinkPHP3.2.3框架模板文件中input的radio预选框checked的问题
- PHP 处理 MySQL INNODB 事务回滚(ThinkPHP、MySQL、PDO)
- Spring事务回滚问题疑难详解
- 使用事务实现--转账问题:从0001账户转1000块到0002账户。打开"隐式事务":设置为开,删除表中数据,回滚!(默认情况为关,如果打开了则不自动提交,学要手动提交)
- thinkphp多表操作事务回滚