MySQL事務
事務
事務的概念就不介紹了, 它的目的是解決ACID問題.
- MySQL中的事務必須是InnoDB、Berkeley DB引擎,myisam不支援, 新出了一個memory, 貌似也是不支援的, 大家可以查查。
- MySQL預設是autocommit=1,也就是說預設是立即提交,如果想開啟事務,先設定autocommit=0,然後用START TRANSACTION、 COMMIT、 ROLLBACK來使用具體的事務。
你可能會有一個疑問: 查詢會不會開事務?
根據上面兩點可以得出, 如果你在INNODB事務引擎下, 並且autocommit=1 (預設值), 答案是會, 否則不會. 其他引擎不支援事務, 這個問題也不存在.
這時候你可能又有另外一個疑問: 我只是執行單個查詢語句, 為什麼要開事務?
- 如果你一次執行單條查詢語句,則沒有必要啟用事務支援,資料庫預設支援SQL執行期間的讀一致性;
- 如果你一次執行多條查詢語句,例如統計查詢,報表查詢,在這種場景下,多條查詢SQL必須保證整體的讀一致性,否則,在前條SQL查詢之後,後條SQL查詢之前,資料被其他使用者改變,則該次整體的統計查詢將會出現讀資料不一致的狀態,此時,應該啟用事務支援。
簡而言之, 因為你需要ACID中的CI: consistency && isolated
巢狀事務
Transactions cannot be nested. This is a consequence of the implicit commit performed for any current transaction when you issue a START TRANSACTION statement or one of its synonyms.
Mysql是不支援巢狀事務的,開啟了一個事務的情況下,再開啟一個事務,會隱式的提交上一個事務. 所以我們就要在系統架構層面來支援事務的巢狀, 常見的做法就是SAVEPOINT.
savePoint機制
==測試資料表結構==
id | stock |
---|---|
1 | 109 |
START TRANSACTION;
SAVEPOINT a;
UPDATE `user` SET stock = 1 WHERE id = 1;
SAVEPOINT b;
UPDATE `user` SET stock = 2 WHERE id = 1;
ROLLBACK TO b;
commit;
執行的程式碼,會發有資料表中id=1的行stock值被修改為1。
解讀一下程式碼
- 開啟事物
- 建立儲存點a
- 更新資料stock為1
- 建立儲存點b(此時id=1的資料stock=1)
- 更新stock為2
- 回滾至savepoint b
- 提交事物
粗讀這段程式碼,即可以知道,savepoint是可以在事務進行中建立儲存點,類似遊戲中的存檔一樣,但是有個不一樣的地方就是savepoint的持有者是事務,當事務commit或者是完全rollback之後,savepoint就會釋放。
THINKPHP
PHP框架THINKPHP5的巢狀事務處理策略: 如果開啟了supportSavepoint, 則利用SAVEPOINT來等價子事務, 否則啥也不幹, MySQL驅動下預設開啟
這產生了一個很重要的結論: 子事務的回滾不會導致事務回滾, 只有第一個事務的回滾才是真正的ROLLBACK
==TP5.1關於巢狀事務的實現原始碼擷取==
/**
* 啟動事務
* @access public
* @return void
* @throws \PDOException
* @throws \Exception
*/
public function startTrans()
{
$this->initConnect(true);
if (!$this->linkID) {
return false;
}
++$this->transTimes;
try {
if (1 == $this->transTimes) {
$this->linkID->beginTransaction();
} elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
$this->linkID->exec(
$this->parseSavepoint('trans' . $this->transTimes)
);
}
} catch (\Exception $e) {
if ($this->isBreak($e)) {
--$this->transTimes;
return $this->close()->startTrans();
}
throw $e;
}
}
/**
* 事務回滾
* @access public
* @return void
* @throws PDOException
*/
public function rollback()
{
$this->initConnect(true);
if (1 == $this->transTimes) {
$this->linkID->rollBack();
} elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
$this->linkID->exec(
$this->parseSavepointRollBack('trans' . $this->transTimes)
);
}
$this->transTimes = max(0, $this->transTimes - 1);
}
/**
* 用於非自動提交狀態下面的查詢提交
* @access public
* @return void
* @throws PDOException
*/
public function commit()
{
$this->initConnect(true);
if (1 == $this->transTimes) {
$this->linkID->commit();
}
--$this->transTimes;
}
/**
* 是否支援事務巢狀
* @return bool
*/
protected function supportSavepoint()
{
return false;
}
/**
* 生成定義儲存點的SQL
* @access protected
* @param $name
* @return string
*/
protected function parseSavepoint($name)
{
return 'SAVEPOINT ' . $name;
}
==從TP的實現原始碼上面可以知道,TP框架在出現巢狀事務時,處理方法就是採用savepoint的策略。startTrans 與 rollback方法都不是直接執行mysql的rollback。他執行分兩步==
- 判斷是不是第一次開啟事務,如果是的話就開啟事務,否則這是單純的執行savepoint方法。
- 回滾rollback的時候也是同理,只有當時最外層的事務執行rollback時,他才會執行mysql的rollback,否則都是rollback to savepoint。
本作品採用《CC 協議》,轉載必須註明作者和本文連結