MySQL - 事務 在學習事務這一概念前,我們需要需要構思一個場景 場景構思 假設該場景發生於一個銀行轉賬背景下,月中,又到了發工資的日子。潭州教育科技集團打算給Tuple老師發放一個月的工資。(此處,我們假設轉賬都是由人工操作的),整個過程本應該如下: 公司財務核對Tuple老師工資單 確認公司 ...
MySQL - 事務
在學習事務
這一概念前,我們需要需要構思一個場景
場景構思
假設該場景發生於一個銀行轉賬背景下,月中,又到了發工資的日子。潭州教育科技集團
打算給Tuple
老師發放一個月的工資。(此處,我們假設轉賬都是由人工操作的),整個過程本應該如下:
-
公司財務核對Tuple老師工資單
-
確認公司賬上還有這麼多錢
-
向銀行提出轉賬申請,銀行扣除公司財務卡上的指定金額
-
銀行向Tuple老師工資卡打入指定金額
-
銀行彙報雙方交易完成
但是,當這個過程執行完第3步的時候,突然大斷電!整個電力系統進入癱瘓。待電力系統回覆之後,銀行並不會繼續執行4、5步甚至連1,2,3步的操作記錄都丟失了。此時出現瞭如下的問題:
-
公司認為,工資已經發出
-
Tuple老師認為,公司還沒有發工資
-
銀行認為,從來就沒有發生過轉賬的事情
其實整個過程可以用一個詞來描述:資料庫中的數據產生了“不一致性”
一致性
上述背景中設計到了一個概念,叫做不一致性
,這是和一致性
相對的概念。那麼,什麼是一致性呢?
一致性
的意思是,在一系列資料庫行為的前後兩個時間點上,數據是正確對應的。放在上面的例子來看,就是操作前後,兩個賬戶的總金額是一樣的,這樣就保證不會憑空的丟失掉不該丟失掉的金錢。
原子操作
為了保證數據的一致性,我們可以將一系列
會破壞一致性的操作聲明為原子操作
。被聲明為原子操作的那些操作要麼一起完成,要麼一起失敗,這樣我們就避免了類似斷電
這類情況導致的數據不一致性。
事務
那麼如何才能實現MySQL中的原子操作呢?
MySQL以及大多數關係型資料庫都提供了一個叫做事務
的技術。我們可以聲明一個事務的開始
,在確認提交
或者指明放棄
前的所有操作,都先在一個叫做事務日誌
的臨時環境中進行操作。待操作完成,確保了數據一致性之後,那麼我們可以手動確認提交,也可以選擇放棄以上操作。
註意: 一旦選擇了提交
,那麼便不能再利用放棄操作
來撤銷更改了。
案例分析
我們首先創建我們本次案例需要使用的表,並給一些測試數據
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| python |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> USE `python`;
Database changed
mysql> SELECT DATABASE();
+------------+
| DATABASE() |
+------------+
| python |
+------------+
1 row in set (0.00 sec)
mysql> CREATE TABLE `account` (
-> `id` int PRIMARY KEY AUTO_INCREMENT,
-> `name` VARCHAR(20) NOT NULL,
-> `balance` DECIMAL(12,2)
-> );
Query OK, 0 rows affected (0.04 sec)
mysql> INSERT INTO `account`(`name`, `balance`)
-> VALUES ('TanzhouEDU', 10000000),('Tuple', 10000)
-> ;
Query OK, 2 rows affected (0.70 sec)
Records: 2 Duplicates: 0 Warnings: 0
斷電案例(非原子操作)
首先,查看現有的賬戶情況
mysql> SELECT * FROM `account`;
+----+------------+-------------+
| id | user | balance |
+----+------------+-------------+
| 1 | TanzhouEDU | 10000000.00 |
| 2 | Tuple | 10000.00 |
+----+------------+-------------+
2 rows in set (0.00 sec)
現在,潭州財務開始向Tuple老師發工資
mysql> UPDATE `account`
-> SET `balance` = `balance` - 10000
-> WHERE `user` = 'TanzhouEDU'
-> ;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM `account`;
+----+------------+------------+
| id | user | balance |
+----+------------+------------+
| 1 | TanzhouEDU | 9990000.00 |
| 2 | Tuple | 10000.00 |
+----+------------+------------+
2 rows in set (0.00 sec)
此時,發生了斷電,再登錄資料庫以後,錢已經消失了。
事務案例(原子操作)
MySQL中,事務操作包括4個:
-
START TRANSACTION
:開始一個新的事務 -
COMMIT
:提交當前事務,做出永久改變 -
ROLLBACK
:回滾當前事務,放棄修改 -
SET autocommit = {0 | 1}
:對當前會話禁用(0)或啟用(1)自動提交模式我們利用事務機制來重覆上述斷電的情況。看看是否能夠有所幫助。
mysql> SELECT * FROM `account`;
+----+------------+------------+
| id | user | balance |
+----+------------+------------+
| 1 | TanzhouEDU | 9990000.00 |
| 2 | Tuple | 10000.00 |
+----+------------+------------+
2 rows in set (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE `account`
-> SET `balance` = `balance`-1000
-> WHERE `user` = 'TanzhouEDU'
-> ;
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * FROM `account`;
+----+------------+------------+
| id | user | balance |
+----+------------+------------+
| 1 | TanzhouEDU | 9989000.00 |
| 2 | Tuple | 10000.00 |
+----+------------+------------+
2 rows in set (0.00 sec)
mysql> exit
Bye
tuple@MyVM:~$ mysql -utuple -p123456
mysql> USE `python`;
Database changed
mysql> SELECT * FROM `account`;
+----+------------+------------+
| id | user | balance |
+----+------------+------------+
| 1 | TanzhouEDU | 9990000.00 |
| 2 | Tuple | 10000.00 |
+----+------------+------------+
2 rows in set (0.00 sec)
我們可以看到,數據在斷電後,自動恢復到了數據修改前的樣子,它相當於如下一個操作過程。
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE `account`
-> SET `balance`=`balance`-1000
-> WHERE `user`='TanzhouEDU'
-> ;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM `account`;
+----+------------+------------+
| id | user | balance |
+----+------------+------------+
| 1 | TanzhouEDU | 9989000.00 |
| 2 | Tuple | 10000.00 |
+----+------------+------------+
2 rows in set (0.00 sec)
mysql> ROLLBACK;
Query OK, 0 rows affected (0.02 sec)
mysql> SELECT * FROM `account`;
+----+------------+------------+
| id | user | balance |
+----+------------+------------+
| 1 | TanzhouEDU | 9990000.00 |
| 2 | Tuple | 10000.00 |
+----+------------+------------+
2 rows in set (0.00 sec)
接著,我們來做一次正確的操作,真正的給Tuple老師發一次工資。
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE `account`
-> SET `balance` = `balance`-10000
-> WHERE `user` = 'TanzhouEDU'
-> ;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> UPDATE `account`
-> SET `balance` = `balance`+10000
-> WHERE `user`= 'Tuple'
-> ;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM `account`;
+----+------------+------------+
| id | user | balance |
+----+------------+------------+
| 1 | TanzhouEDU | 9980000.00 |
| 2 | Tuple | 20000.00 |
+----+------------+------------+
2 rows in set (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.03 sec)
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM `account`;
+----+------------+------------+
| id | user | balance |
+----+------------+------------+
| 1 | TanzhouEDU | 9980000.00 |
| 2 | Tuple | 20000.00 |
+----+------------+------------+
2 rows in set (0.00 sec)
通過上面的例子可以看出,一旦commit了,那麼rollback還是斷電都不能反悔了。