现象
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的值进行判断,在+±-过程中就会出现问题,按上面的只将类实例化一次,可避免这种情况的发生,也就是说不支持所谓的嵌套事务
参考博文。
/**
* 启动事务
* @access public
* @return void
*/
public function startTrans() {
//$this->commit();
$this->db->startTrans();
return ;
}
/**
* 启动事务
* @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;
}