事務回滾無效
現象
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;
}