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

 

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