mysql事務以及隔離級別

mysql事務以及隔離級別

1. 簡介

MySQL 事務主要用於處理操做量大,複雜度高的數據。好比說,在人員管理系統中,你刪除一我的員,你即須要刪除人員的基本資料,也要刪除和該人員相關的信息,如信箱,文章等等,這樣,這些數據庫操做語句就構成一個事務!html

  • 在 MySQL 中只有使用了 Innodb 數據庫引擎的數據庫或表才支持事務。
  • 事務處理能夠用來維護數據庫的完整性,保證成批的 SQL 語句要麼所有執行,要麼所有不執行。
  • 事務用來管理 insert,update,delete 語句

2. 事務的基本要素ACID

通常來講,事務是必須知足4個條件(ACID)::原子性(Atomicity,或稱不可分割性)、一致性(Consistency)、隔離性(Isolation,又稱獨立性)、持久性(Durability)。mysql

  • 原子性:一個事務(transaction)中的全部操做,要麼所有完成,要麼所有不完成,不會結束在中間某個環節。事務在執行過程當中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務歷來沒有執行過同樣。
  • 一致性:在事務開始以前和事務結束之後,數據庫的完整性沒有被破壞。這表示寫入的資料必須徹底符合全部的預設規則,這包含資料的精確度、串聯性以及後續數據庫能夠自發性地完成預約的工做。好比A向B轉帳,不可能A扣了錢,B卻沒收到。
  • 隔離性:數據庫容許多個併發事務同時對其數據進行讀寫和修改的能力,隔離性能夠防止多個事務併發執行時因爲交叉執行而致使數據的不一致。事務隔離分爲不一樣級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(Serializable)。
  • 持久性:事務完成後,事務對數據庫的全部更新將被保存到數據庫,不能回滾。

3. 事務的併發問題

  1. 髒讀:容許讀取未提交的髒數據,好比:事務A讀取了事務B更新的數據,而後B回滾操做,那麼A讀取到的數據是髒數據;
  2. 不可重複讀:若是你在時間點t1讀取了一些記錄,在t2時間點也想從新讀取同樣的數據時,這些記錄可能已經被改變,或者消失,好比:事務 A 屢次讀取同一數據,事務 B 在事務A屢次讀取的過程當中,對數據做了更新並提交,致使事務A屢次讀取同一數據時,結果不一致。
  3. 幻讀:系統管理員A將數據庫中全部學生的成績從具體分數改成ABCDE等級,可是系統管理員B就在這個時候插入了一條具體分數的記錄,當系統管理員A改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺同樣,這就叫幻讀。

不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住知足條件的行,解決幻讀須要鎖表。sql

4. 事務的4種隔離級別

爲了解決上面事務的併發問題,sql標準提出了4種隔離級別,下面是每種隔離級別可以解決的問題對應關係:shell

事務隔離級別 髒讀 不可重複讀 幻讀
read-uncommitted N N N
read-committed Y N N
repeatable-read(default) Y Y N
serializable Y Y Y

mysql的默認隔離級別是Repeatable。數據庫

查看系統級和會話級的隔離級別:session

mysql> select @@global.tx_isolation,@@tx_isolation; 
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set, 2 warnings (0.01 sec)

下面用例子說明一下這四種隔離級別:併發

1. read-uncommittedsvn

更改隔離級別爲read-uncommitted:性能

mysql> set session tx_isolation='read-uncommitted';
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)

首先,準備一些測試數據:測試

mysql> select * from user;
+----+----------+------+
| id | name     | age  |
+----+----------+------+
|  1 | zhangsan |   25 |
|  2 | lisi     |   26 |
|  3 | wangwu   |   27 |
|  4 | nike     |   28 |
|  5 | lucy     |   29 |
+----+----------+------+
5 rows in set (0.00 sec)

客戶端A:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user;
+----+----------+------+
| id | name     | age  |
+----+----------+------+
|  1 | zhangsan |   25 |
|  2 | lisi     |   26 |
|  3 | wangwu   |   27 |
|  4 | nike     |   28 |
|  5 | lucy     |   29 |
+----+----------+------+
5 rows in set (0.00 sec)

客戶端B:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update user set age=52 where name='zhangsan';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

客戶端A:

mysql> select * from user;
+----+----------+------+
| id | name     | age  |
+----+----------+------+
|  1 | zhangsan |   52 |
|  2 | lisi     |   26 |
|  3 | wangwu   |   27 |
|  4 | nike     |   28 |
|  5 | lucy     |   29 |
+----+----------+------+
5 rows in set (0.00 sec)

能夠看到,客戶端B的事務尚未提交,在客戶端A的事務內就看到了更新的數據。

客戶端B:

mysql> rollback;
Query OK, 0 rows affected (0.02 sec)

客戶端A:

mysql> select * from user;
+----+----------+------+
| id | name     | age  |
+----+----------+------+
|  1 | zhangsan |   25 |
|  2 | lisi     |   26 |
|  3 | wangwu   |   27 |
|  4 | nike     |   28 |
|  5 | lucy     |   29 |
+----+----------+------+
5 rows in set (0.00 sec)

因爲客戶端B的事務回滾,客戶端A讀取的數據又變成了原始數據,所以上一次客戶端A讀取的數據變成了髒數據。在併發事務中,這種讀取數據的問題就叫作髒讀。

2. read-commited

要解決上面的問題,能夠把數據庫的隔離級別改爲read-commited。

客戶端A:

mysql> set session tx_isolation='read-committed';
Query OK, 0 rows affected, 1 warning (0.00 sec)

再按照上述步驟測試一下,發現髒讀問題已經解決,在事務B沒有commit以前,事務A不會讀取到髒數據。

下面演示一下不可重複讀的問題。

客戶端A:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user;
+----+----------+------+
| id | name     | age  |
+----+----------+------+
|  1 | zhangsan |   25 |
|  2 | lisi     |   26 |
|  3 | wangwu   |   27 |
|  4 | nike     |   28 |
|  5 | lucy     |   29 |
+----+----------+------+
5 rows in set (0.00 sec)

客戶端B:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update user set age=52 where name='zhangsan';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

客戶端A:

mysql> select * from user;
+----+----------+------+
| id | name     | age  |
+----+----------+------+
|  1 | zhangsan |   52 |
|  2 | lisi     |   26 |
|  3 | wangwu   |   27 |
|  4 | nike     |   28 |
|  5 | lucy     |   29 |
+----+----------+------+
5 rows in set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

能夠看到在客戶端B的事務提交先後,客戶端A讀取到的數據不同了。也就是重複讀取相同的數據有不一樣的結果。

我的理解,髒讀也屬於不可重複讀的一個範疇,只是髒讀在事務B未提交以前就致使兩次讀取數據不同,不可重複讀在事務B提交以後致使兩次讀取結果不同。還有就是髒讀之因此叫髒數據,是由於這條數據沒有真正的在數據庫中保存過,這是事務的一箇中間狀態。而不可重複讀兩次讀取不一樣的數據實際都已經存在於數據庫中了。

3. repeatable-read

要解決不可重複讀的問題,能夠將數據庫的隔離級別改成repeatable-read。

客戶端A:

mysql> set session tx_isolation='repeatable-read';
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

再按照上述步驟測試一下,發現不可重複讀的問題已經解決,在事務B沒有commit以後,事務A讀取的數據沒有變化,關閉這個事務從新打開一個事務纔會讀到更新後的數據。

下面演示一下幻讀的問題。

客戶端A:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user;
+----+----------+------+
| id | name     | age  |
+----+----------+------+
|  1 | zhangsan |   25 |
|  2 | lisi     |   26 |
|  3 | wangwu   |   27 |
|  4 | nike     |   28 |
|  5 | lucy     |   29 |
+----+----------+------+
5 rows in set (0.00 sec)

客戶端B:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into user values(6,'shell',30);
Query OK, 1 row affected (0.01 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

客戶端A:

mysql> insert into user values(6,'shell',30);
ERROR 1062 (23000): Duplicate entry '6' for key 'PRIMARY'
mysql> select * from user;
+----+----------+------+
| id | name     | age  |
+----+----------+------+
|  1 | zhangsan |   25 |
|  2 | lisi     |   26 |
|  3 | wangwu   |   27 |
|  4 | nike     |   28 |
|  5 | lucy     |   29 |
+----+----------+------+
5 rows in set (0.00 sec)

能夠看到,對於客戶端A來講,命名沒有id爲6的數據,可是仍是插入失敗,再查詢一下仍是沒有啊,感受產生了幻覺,這就是幻讀問題。幻讀和不可重複讀的區別在於,不可重複讀重點是更新後的讀取,幻讀重點是插入刪除這些操做,解決不可重複讀,只須要對對應的數據行加鎖就好了。解決幻讀則須要對整張表加鎖。

若是兩個事務B沒有提交以前事務A執行插入會如何呢?咱們來看一下:

客戶端A:

mysql> insert into user values(6,'shell',30);

能夠看到若是插入的id和事務B同樣,那麼事務A的操做會被阻塞,直到事務B提交commit後,纔會報錯:

客戶端A:

mysql> insert into user values(8,'svn',32);




ERROR 1062 (23000): Duplicate entry '8' for key 'PRIMARY'

若是客戶端A插入到的數據事務B不衝突,那麼會當即返回成功:

客戶端A:

mysql> insert into user values(9,'svn',32);
Query OK, 1 row affected (0.00 sec)

4. serializable

要解決幻讀的問題,能夠將數據庫的隔離級別改成serializable。

客戶端A:

mysql> set session tx_isolation='serializable';
Query OK, 0 rows affected, 1 warning (0.00 sec)

再按照上述步驟測試一下,發現幻讀的問題已經解決,當事務B嘗試insert的事務,被阻塞,也就是事務A將整張表鎖住了。直到事務A提交commit之後,事務B的操做纔會返回結果。

在這種狀況下,只容許一個事務在執行,其它事務必須等待這個事務執行完後才能執行。沒有併發,只是單純的串行。

5. 總結

  1. mysql中默認事務隔離級別是可重複讀時並不會鎖住讀取到的行;
  2. 事務隔離級別爲讀提交時,寫數據只會鎖住相應的行;
  3. 事務隔離級別爲可重複讀時,若是有索引(包括主鍵索引)的時候,以索引列爲條件更新數據,會存在間隙鎖間隙鎖、行鎖、下一鍵鎖的問題,從而鎖住一些行;若是沒有索引,更新數據時會鎖住整張表;
  4. 事務隔離級別爲串行化時,讀寫數據都會鎖住整張表;
  5. 隔離級別越高,越能保證數據的完整性和一致性,可是對併發性能的影響也越大,魚和熊掌不可兼得啊。對於多數應用程序,能夠優先考慮把數據庫系統的隔離級別設爲Read Committed,它可以避免髒讀取,並且具備較好的併發性能。儘管它會致使不可重複讀、幻讀這些併發問題,在可能出現這類問題的個別場合,能夠由應用程序採用悲觀鎖或樂觀鎖來控制。
參考:

http://www.runoob.com/mysql/m...

https://www.cnblogs.com/huano...

https://blog.csdn.net/taylor_...

相關文章
相關標籤/搜索