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嵌套事务
参考博文。

1、ThinkPHP\Library\Think\Model.class.php
代码如下,第1501行注释掉就好
 

    /**
         * 启动事务
         * @access public
         * @return void
         */
        public function startTrans() {
            //$this->commit();
            $this->db->startTrans();
            return ;
        }

2、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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章