1. 程式人生 > >mysql事務以及隔離級別

mysql事務以及隔離級別

系統 www. 情況 ria 人員管理 等待 UNC 完整 執行過程

mysql事務以及隔離級別

1. 簡介

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

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

2. 事務的基本要素ACID

一般來說,事務是必須滿足4個條件(ACID)::原子性(Atomicity,或稱不可分割性)、一致性(C

onsistency)、隔離性(Isolation,又稱獨立性)、持久性(Durability)。

  • 原子性:一個事務(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改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺一樣,這就叫幻讀。

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

4. 事務的4種隔離級別

為了解決上面事務的並發問題,sql標準提出了4種隔離級別,下面是每種隔離級別能夠解決的問題對應關系:

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

mysql的默認隔離級別是Repeatable。

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

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-uncommitted

更改隔離級別為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/mysql-transaction.html

https://www.cnblogs.com/huanongying/p/7021555.html

https://blog.csdn.net/taylor_tao/article/details/7063639

mysql事務以及隔離級別