多表關聯的情況下,一條新記錄的生成往往涉及多張表的操作。
一個典型的場景,銀行轉帳。要完成 A 轉帳到 B,
- 從 A 帳戶減去相應金額
- 給 B 帳戶加上相應金額。
這兩步要么一起成功,要么都失敗,否則就會造成數據不一致。比如 A 的錢少了,但 B 的錢沒增加,或者 A 的扣款失敗,B 的錢也增加了。
所以需要一種機制來保證這一操作過程中每一步的正確性,當其中任意操作失敗時應該將已經進行過的操作回滾,保證整體都失敗。
此時這些被綁定的一連串操作便形成了 事務。
下面創建一張空表,為后面示例作准備。
mysql> CREATE TABLE test(idx int);
Query OK, 0 rows affected (0.02 sec)
事務的語法
- MySQL 中,通過
START TRANSACTION
語句來開始一個事務,也可以使用別名BEGIN
和BEGIN WORK
語句。 COMMIT
語句提交修改。- 通過
ROLLBACK
語句回滾。
其中 COMMIT
或 ROLLBACK
均可用來結束一個事務。
來看一個簡單的示例:
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO test values(1);
Query OK, 1 row affected (0.01 sec)
mysql> INSERT INTO test values(2);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM test;
+------+
| idx |
+------+
| 1 |
| 2 |
+------+
2 rows in set (0.00 sec)
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM test;
Empty set (0.00 sec)
上面創建了一個名為 test
的表,開始事務,向其中分兩次插入記錄。
然后查詢剛剛插入的兩條記錄。因為此時是處於一個事務中,所以這兩條記錄實際上是可以被回滾的。
調用 ROLLBACK
后兩次查詢,表中已經沒有了剛剛的兩條記錄。
autocommit
拋開事務,MySQL 默認情況下開啟了一個自動提交的模式 autocommit
,一條語句被回車執行后該語句便生效了,變更會保存在 MySQL 的文件中,無法撤消。當使用相應語句比如 BEGIN
顯式聲明開始一個事務時,autocommit
默認會是關閉狀態。
可通過查詢 @@autocommit
變量來查看當前是否是自動提交的狀態,
mysql> SELECT @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
1 row in set (0.00 sec)
通過設置 autocommit
為 0
可關閉自動提交,
SET autocommit = 0;
無論是否是自動提交模式,語句執行后都會生效,區別在於,非自動模式下,沒提交的那些操作是可以回滾的,一旦提交后便不可撤消了。換句話說,當 autocommit
關閉時,一直是處於事務操作中的,可隨時調用 ROLLBACK
進行回滾。
下面的語句展示了 autocommit
關閉時 COMMIT
與否的影響,
mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
1 row in set (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
Empty set (0.00 sec)
mysql> insert into test values(1);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(2);
Query OK, 1 row affected (0.00 sec)
mysql> select * from test;
+------+
| idx |
+------+
| 1 |
| 2 |
+------+
2 rows in set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
Empty set (0.00 sec)
mysql> insert into test values(3);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+------+
| idx |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
事務中會觸發隱式提交的操作
雖說事務模式下關閉了 autocommit
必需手動執行 COMMIT
才能提交,但有些語句和操作是會隱式觸發提交的,在進行事務過程中需要注意,這些操作可在官方文檔 Statements That Cause an Implicit Commit 中查找到。
所以不能單純地認為一個事務中所有操作都是絕對安全可回滾的。
Node.js 示例
以下是來自 npm 模塊 mysqljs/mysql 關於事務的示例:
connection.beginTransaction(function(err) {
if (err) { throw err; }
connection.query('INSERT INTO posts SET title=?', title, function (error, results, fields) {
if (error) {
return connection.rollback(function() {
throw error;
});
}
<span class="pl-k">var</span> log <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>Post <span class="pl-pds">'</span></span> <span class="pl-k">+</span> <span class="pl-smi">results</span>.<span class="pl-smi">insertId</span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">'</span> added<span class="pl-pds">'</span></span>;
<span class="pl-smi">connection</span>.<span class="pl-en">query</span>(<span class="pl-s"><span class="pl-pds">'</span>INSERT INTO log SET data=?<span class="pl-pds">'</span></span>, log, <span class="pl-k">function</span> (<span class="pl-smi">error</span>, <span class="pl-smi">results</span>, <span class="pl-smi">fields</span>) {
<span class="pl-k">if</span> (error) {
<span class="pl-k">return</span> <span class="pl-smi">connection</span>.<span class="pl-en">rollback</span>(<span class="pl-k">function</span>() {
<span class="pl-k">throw</span> error;
});
}
<span class="pl-smi">connection</span>.<span class="pl-en">commit</span>(<span class="pl-k">function</span>(<span class="pl-smi">err</span>) {
<span class="pl-k">if</span> (err) {
<span class="pl-k">return</span> <span class="pl-smi">connection</span>.<span class="pl-en">rollback</span>(<span class="pl-k">function</span>() {
<span class="pl-k">throw</span> err;
});
}
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>success!<span class="pl-pds">'</span></span>);
});
});
});
});
總結
關於 autocommit
與事務,他們其實是這樣的關系:
- MySQL 每一步操作都可看成一個原子操作。
- 默認情況下,
autocommit
是開啟狀態,所以第一條語句都是執行后自動提交,語句產生的效果被記錄保存了下來。 - 關於
autocommit
后,語句不會自動提交,需要手動調用COMMIT
來讓效果被持久化。 - 也可通過
BEGIN
開始一個事務,此時autocommit
隱式地被關閉了,因此事務操作過程中也是需要顯式調用COMMIT
來讓效果永久生效。 BEGIN
開啟事務后,使用COMMIT
或ROLLBACK
來結束該事務。事務結束后autocommit
回到原有的狀態。
所以,autocommit
這個開關相當於一個記錄的事務標記,它被關閉時你一直處於一個可回滾的狀態。而 BEGIN
開啟的是一次臨時事務,一旦 COMMIT
或 ROLLBACK
本次事務便結束了。