按部就班 MySQL 事務隔離級別

本篇文章的重點在於總結MYSQL事務。mysql

什麼是事務

事務簡言之就是一組 SQL 執行要麼所有成功,要麼所有失敗。MYSQL 的事務在存儲引擎層實現。sql

事務都有 ACID 特性:數據庫

  • 原子性(Atomicity):一個事務必須被視爲一個不可分割的單元;
  • 一致性(Consistency):數據庫老是從一種狀態切換到另外一種狀態;
  • 隔離性(Isolation):一般來講,事務在提交前對於其餘事務不可見;
  • 持久性(Durablity):一旦事務提交,所作修改永久保存數據庫;

事務最經常使用的例子就是銀行轉帳。假設 polo 需給 lynn 轉帳1000元,以下步驟:併發

  • 確認 polo 帳戶餘額高於1000元;
  • 從 polo 的帳戶餘額減去1000元;
  • 將 lynn 的帳戶餘額增長1000元;

SQL語句以下:ui

mysql> BEGIN;
mysql> SELECT balance FROM bank_account WHERE uid=10001;
mysql> UPDATE bank_account SET balance=balance-1000 WHERE uid=10001;
mysql> UPDATE bank_account SET balance=balance+1000 WHERE uid=10002;
mysql> COMMIT;
複製代碼

mysql 啓動事務可以使用 BEGIN 或 START TRANSACTION;上述三個步驟執行在一個事務中就可以保證數據的完整性,要麼所有成功,要麼所有失敗。spa

MYSQL 提供兩種事務型引擎:Innodb 和 NDBCluster。默認採用自動提交模式,執行一條語句自動 COMMIT。經過 AUTOCOMMIT 變量可啓用或者禁用自動提交模式:code

mysql> SHOW VARIABLES LIKE "AUTOCOMMIT";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)

mysql> SET AUTOCOMMIT=1
複製代碼

AUTOCOMMIT=1 表示開啓默認提交,0表示關閉默認提交須要手動提交。事務

隔離級別

事務隔離性的解釋:一般狀況下,事務在提交以前對於其餘事務不可見。ci

數據庫有四種隔離級別,固然 MYSQL 也是如此。分別是:it

  • 未提交讀,READ UNCOMMITTED
  • 已提交讀,READ COMMITTED
  • 可重複讀,REPEATABLE READ
  • 串行化,SEAIALIZABLE

關於隔離級別的兩個理解

  • 書本解釋,每種級別都規定了一個事務中所作修改,哪些在事務內和事務間是可見的。
  • 個人理解,隔離級別就是決定一個事務的修改另外一個事務什麼狀況下能看到。

二者區別在因而否存在事務內可見性,但不管哪一個級別在事務內的操做確定是可見的,重點在事務間可見性。

下面開始說明 MYSQL 的四種隔離級別,先準備一張學生表:

mysql> CREATE TABLE `student` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(32) NOT NULL DEFAULT '',
 PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8 |
複製代碼

只有id(主鍵自增)與name字段

未提交讀

事務中修改沒有提交對其餘事務也是可見的,俗稱髒讀。很是不建議使用。

示例演示,客戶端A和B設置隔離級別爲未提交讀

mysql> SET SESSION TX_ISOLATION='READ-UNCOMMITTED';
複製代碼

客戶端A與B開啓事務並查詢student

mysql> BEGIN;
mysql> SELECT * FROM student;
Empty set (0.00 sec)
複製代碼

當前,客戶端 A 和 B 都是空數據。此時在客服端 B 插入一條新的數據

mysql> INSERT INTO student(name) VALUES("polo");
Query OK, 1 row affected (0.00 sec)
複製代碼

此時事務還未提交,客服端 A 查看一下 student 表,以下:

mysql> SELECT * FROM student;
+----+------+
| id | name |
+----+------+
|  1 | polo |
+----+------+
1 row in set (0.00 sec)
複製代碼

能夠看出,客戶端 A 看到 B 未提交的修改。客戶端 B 執行回滾操做,以下:

mysql> ROLLBACK
複製代碼

成功以後,客戶端 A 查看 student 表:

mysql> SELECT * FROM student;
Empty set (0.00 sec)
複製代碼

輸出顯示,客戶端A查看數據爲空。

以上能夠看出未提交讀隔離級別的危險性,對於一個沒有提交事務所作修改對另外一個事務是可見狀態,容易形成髒讀。非特殊狀況不得使用此級別

已提交讀

多數數據庫系統默認爲此級別(MYSQL不是)。已提交讀級別即爲一個事務只能看到已提交事務所作的修改,也就解決了未提交讀的問題,即髒讀的問題。

示例演示,客戶端A和B設置隔離級別爲已提交讀,執行以下命令:

mysql> SET SESSION TX_ISOLATION='READ-COMMITTED';
複製代碼

客戶端 A與 B 開啓事務並查詢 student

mysql> BEGIN; 
mysql> SELECT * FROM student;
Empty set (0.00 sec)
複製代碼

結果顯示,客戶端A和B都爲空。接着,客戶端 B 插入一條新的數據但不執行提交:

mysql> INSERT INTO student (name) VALUES('polo');
複製代碼

接下來,客戶端 A 查看一下 student 數據:

mysql> SELECT * FROM student;
Empty set (0.00 sec)
複製代碼

注意這裏與上面不一樣了,在客戶端B沒有提交事務狀況下無數據。下面客戶端B提交事務:

mysql> COMMIT;
複製代碼

客戶端 A 再查看下 student 表。

mysql> SELECT * FROM student; 
+----+------+
| id | name |
+----+------+
|  1 | polo |
+----+------+
1 row in set (0.00 sec)
複製代碼

這樣咱們就成功讀取到了客戶。

從上面的示例能夠看出,提交讀沒有了髒讀問題,但咱們能夠看到在客戶端 A 的一個事務中執行兩次一樣的 SELECT 語句獲得不一樣結果,所以已提交讀又被稱爲不可重複讀。一樣篩選條件可能獲得不一樣的結果。

可重複讀

如其名所言,解決已提交讀不可重複讀取的問題。

示例演示,客戶端A和B設置隔離級別爲可重複讀。首先設置隔離級別:

mysql> SET SESSION tx_isolation='REPEATABLE-READ'
複製代碼

客戶端A與B開啓事務並查看

mysql> BEGIN; 
mysql> SELECT * FROM student;
+----+------+
| id | name |
+----+------+
|  1 | polo |
+----+------+
1 rows in set (0.00 sec)
複製代碼

客服端 B 更新 polo 爲 adam 並提交事務

mysql> UPDATE student SET name='adam' WHERE id=1;
mysql> COMMIT
複製代碼

客戶端A查看student表,結果以下:

mysql> SELECT * FROM student;
+----+------+
| id | name |
+----+------+
|  1 | polo |
+----+------+
1 rows in set (0.00 sec)
複製代碼

客戶端 A 查看數據未變,沒有不可重複讀問題。

客戶端 A 提交事務,並查看student表。

mysql> COMMIT;
mysql> SELECT * FROM student;
+----+------+
| id | name |
+----+------+
|  1 | polo |
+----+------+
1 rows in set (0.00 sec)
複製代碼

從上面的示例可知,可重複讀兩次讀取內容同樣。該級別並無解決幻讀的問題。可是MYSQL在可重複讀基礎上增長了MVCC機制解決了此問題,此處沒法演示幻讀的效果。

那什麼是幻讀?首先,可重複讀鎖定範圍爲當前查詢到的內容,如執行

mysql> SELECT * FROM student WHERE id>=1
複製代碼

鎖定的即 id>=1 查到的行,爲行級鎖。如另外一事務執行並默認提交如下語句

mysql> INSERT INTO student (name) VALUES ('stephen');
複製代碼

新增的這行並無被鎖定,此時讀取 student

mysql> SELECT * FROM student WHERE id>=1;
+----+---------+
| id | name    |
+----+---------+
|  1 | polo    |
|  2 | stephen |
+----+---------+
2 rows in set (0.00 sec)
複製代碼

出現了幻讀。關於這個問題,除了使用 MYSQL 的 MVCC 機制,還能夠用可串行化隔離級別解決此問題。

串行化

串行化是最高隔離級別,強制事務串行執行。執行串行了,那麼也就解決了一切的問題,這個級別只有在對數據一致性要求很是嚴格且沒用併發的狀況下使用。

示例演示,客戶端 A 和 B 設置隔離級別爲可串行化。

mysql> SET SESSION tx_isolation='SERIALIZABLE';
複製代碼

首先,客戶端 A 執行一下查詢:

mysql> SELECT * FROM student WHERE id<4;
+----+---------+
| id | name    |
+----+---------+
|  1 | polo    |
|  2 | stephen |
+----+---------+
2 rows in set (0.00 sec)
複製代碼

接下來,客戶端 B 執行數據新增:

mysql> INSERT INTO student (name) VALUES('yunteng'); 好的!效果出現了,此時咱們會發現INSERT語句被阻塞執行,緣由就是A執行了查詢表student同時知足id<4,已被鎖定。若是查詢表student條件爲id<3,則新增語句可正常執行。

彙總圖

隔離級別 英文 髒讀 不可重複讀 幻讀 加鎖讀
未提交讀 READ UNCOMMITTED
提交讀 READ COMMITTED
可重複讀 REPEATABLE READ
串行化 SERIALIZABLE

關於事務的隔離級別到此結束。具體使用何種狀況還要根據具體的業務場景來決定。

相關文章
相關標籤/搜索