Skip to content

Commit

Permalink
解决嵌套事务使用不当(rollback之后不抛出捕获到的异常)时,回滚后事务还被提交的问题
Browse files Browse the repository at this point in the history
  • Loading branch information
cmpan committed Sep 21, 2017
1 parent 25b59a9 commit 480a4e3
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 66 deletions.
7 changes: 7 additions & 0 deletions lib/DBAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ abstract class DBAbstract implements DBInterface
* @var int
*/
protected $transactions = 0;

/**
* 事务链中还应继续执行回滚
* 在嵌套事务中,开启多少次事务就应该执行多少次回滚
* @var int
*/
protected $rollbackAgain = false;

/**
* 记录当前请求执行的SQL查询语句
Expand Down
2 changes: 0 additions & 2 deletions lib/Finder.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ public function fetchAll($offset = 0, $rows = 0)
} else {
$opts['limit'] = "{$offset}";
}
} else {
throw new Exception('param error, one of the params must be greater than 0');
}

$sql = QueryBuilder::optionsToSql($opts);
Expand Down
22 changes: 19 additions & 3 deletions lib/adapter/MySQLi.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,19 @@ public function beginTransaction()
*/
public function commit()
{
if ($this->rollbackAgain) {
throw new \wf\db\Exception('Please throw exception after rollback in last catch block');
}

--$this->transactions;

if($this->transactions == 0 && false === $this->mysqli->commit()) {
throw new \wf\db\Exception('transaction commit error: ' . $this->getLastErr());
}

if ($this->transactions < 0) {
$this->transactions = 0;
}
}

/**
Expand Down Expand Up @@ -201,9 +209,17 @@ public function setAutoCommit($isAutoCommit = false)
*/
public function rollback()
{
--$this->transactions;

if ($this->transactions <= 0 && false === $this->mysqli->rollback()) {

// 事务链中一旦出现异常,为避免事务链中异常后面的SQL还执行,应返回到第一个事务再回滚
if ($this->transactions -- > 1) {
$this->rollbackAgain = true;
return;
}

$this->rollbackAgain = false;

// 出现异常立即回滚,回滚后再出现commit则应该抛出异常
if (false === $this->mysqli->rollback()) {
throw new \wf\db\Exception('transaction rollback error: '.$this->getLastErr());
}
}
Expand Down
21 changes: 18 additions & 3 deletions lib/adapter/PDOMySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,19 @@ public function beginTransaction()
*/
public function commit()
{
if ($this->rollbackAgain) {
throw new \wf\db\Exception('Please throw exception after rollback in last catch block');
}

--$this->transactions;

if($this->transactions == 0 && false === $this->dbh->commit()) {
throw new \wf\db\Exception('transaction commit error: ' . $this->getLastErr());
}

if ($this->transactions < 0) {
$this->transactions = 0;
}
}

/**
Expand Down Expand Up @@ -211,9 +219,16 @@ public function setAutoCommit($isAutoCommit = false)
*/
public function rollback()
{
--$this->transactions;

if ($this->transactions <= 0 && false === $this->dbh->rollback()) {
// 事务链中一旦出现异常,为避免事务链中异常后面的SQL还执行,应返回到第一个事务再回滚
if ($this->transactions -- > 1) {
$this->rollbackAgain = true;
return;
}

$this->rollbackAgain = false;

// 出现异常立即回滚,回滚后再出现commit则应该抛出异常
if (false === $this->dbh->rollback()) {
throw new \wf\db\Exception('transaction rollback error: ' . $this->getLastErr());
}
}
Expand Down
70 changes: 69 additions & 1 deletion test/MySQLiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/**
* PDOMySQL test case.
*/
class MySQLTest extends PHPUnit_Framework_TestCase
class MySQLiTest extends PHPUnit_Framework_TestCase
{
/**
*
Expand Down Expand Up @@ -203,5 +203,73 @@ public function testRollBack()
$lastStr = $this->mySQLi->getColumn("SELECT str FROM wk_test_table ORDER BY id DESC");
$this->assertEquals($uniqe, $lastStr);
}


/**
* 测试嵌套事务
*/
public function testNestedTrans()
{
$uniqe = uniqid();
$this->insertRow($uniqe);

try {
$trans = $this->mySQLi->beginTransaction();
$this->insertRow();
$this->insertRow();

try {
$trans = $this->mySQLi->beginTransaction();
$this->insertRow();
$this->insertRow();
$trans->commit();
} catch (\wf\db\Exception $e) {
$trans->rollback();
}

try {
$trans = $this->mySQLi->beginTransaction();
$this->insertRow();
$this->insertRow();

try {
$trans = $this->mySQLi->beginTransaction();
$this->insertRow();
$this->insertRow();

// 在此出现异常
throw new \wf\db\Exception('~');

$trans->commit();
} catch (\wf\db\Exception $e) {
$trans->rollback();
}

// 应避免:事务因异常关闭,因上面回滚后没有再抛出异常,导致继续执行这里
$this->insertRow();

$trans->commit();
} catch (\wf\db\Exception $e) {
$trans->rollback();
}

$trans->commit();
} catch (\wf\db\Exception $e) {
$trans->rollback();
}

// 检测事务没有添加成功(最后添加的是事务开启前的)
$lastStr = $this->mySQLi->getColumn("SELECT str FROM wk_test_table ORDER BY id DESC");
$this->assertEquals($uniqe, $lastStr);


// 检查事务已正常关闭
$uniqe = uniqid();
$this->insertRow($uniqe);
$lastStr = $this->mySQLi->getColumn("SELECT str FROM wk_test_table ORDER BY id DESC");
$this->assertEquals($uniqe, $lastStr);

print 'testNestedTrans done';
}
}

Loading

0 comments on commit 480a4e3

Please sign in to comment.