MySQL的四種事務隔離級別

(一)事務的基本要素(ACID)mysql

①:原子性(Atomicity):【針對該事務的操做】事務開始後全部操做,要麼所有作完,要麼所有不作sql

②:一致性(Consistency):【針對數據庫狀態要改 一塊兒改。】事務開始前和結束後--->數據庫完整性約束沒有被破壞。eg:就像我給你轉帳,不可能我扣了錢,而你沒收到錢。數據庫

③:隔離性(Isolation):【針對事務與事務之間】一個事物所作的修改在最終提交以前,對其餘事務不可見的。session

④:持久性(durability):【針對數據庫保存】一旦事務提交後,所作的修改會永久保存數據庫中。此時即便系統崩潰了。修改的數據也不會消失。併發

(二)事務併發產生的問題spa

①第一類丟失更新【更新的丟了】:在沒有事務隔離的狀況下,兩個事物都同時更新一行數據,可是第二個事務中途失敗退出,致使對數據的修改失效。rest

eg:小明的工資是1000塊,事務A獲取工資1000,事務B匯入100塊,並提交到數據庫,工資變爲1100。以後呢,事務A發生異常,回滾了,回覆小明工資爲1000塊---->這就致使事務B的更新數據丟失了。code

髒讀【讀的錯了】:事務A讀取了事務B更新的數據,而後事務B回滾了,這樣A讀取到的數據就是髒數據。blog

③:不可重複讀【修改數據時】:在A事物內屢次取同一數據。在A事務還沒有提交時,另外一個B事務修改了同一數據,A再次讀到的數據就可能不同。eg:事務

小明的工資爲1000,事務A中把他的工資改成2000,但事務A還沒有提交。   與此同時,事務B正在讀取小明的工資,讀取到張三的工資爲2000。   隨後,事務A發生異常,而回滾了事務。張三的工資又回滾爲1000。   最後,事務B讀取到的張三工資爲2000的數據即爲髒數據,事務B作了一次髒讀。

 

④:第二類丟失更新【更新覆蓋了】:不可重複讀的特例。有A、B兩個事物同時讀取一行數據,而後A對它修改該提交,而B也進行了修改提交。這回形成A事物操做失效。

⑤:幻讀【新增、刪除數據時】:事務不獨立執行發生的一種現象。eg:系統管理員A將數據庫中全部學生成績從具體分數該爲ABCD等級制,但同時,系統管理員B在這個時候插入了一條具體的分數記錄。當A修改結束後發現還有一條記錄沒有修改過來,就好像發生了幻覺同樣。

(三)MySQL事務的隔離級別

 

(1)READ_UNCOMMITTED【未提交讀---不多用】   這是事務最低的隔離級別,它充許另一個事務能夠看到這個事務未提交的數據。   解決第一類丟失更新的問題,可是會出現髒讀不可重複讀、第二類丟失更新的問題幻讀 。 (2)READ_COMMITTED【提交讀---大多數數據庫默認的隔離級別(MySQL不是)】   保證一個事務修改的數據提交後才能被另一個事務讀取,即另一個事務不能讀取該事務未提交的數據。   解決第一類丟失更新和髒讀【√】的問題,但會出現不可重複讀第二類丟失更新的問題,幻讀問題【×】 (3)REPEATABLE_READ【可重複讀---MySQL默認的隔離級別(不會鎖住,讀取到的行)】   保證一個事務相同條件下先後兩次獲取數據是一致的 解決第一類丟失更新,髒讀、不可重複讀、第二類丟失更新的問題,但會出幻讀。 (4)SERIALIZABLE【可串行化,最高隔離級別---不多用(每行加鎖-->超時,鎖爭用)】   事務被處理爲順序執行。   解決全部問題

 

(四)具體的一些列子:

 

  一、讀未提交:

 

    (1)打開一個客戶端A,並設置當前事務模式爲read uncommitted(未提交讀),查詢表account的初始值:

 

 

 

    (2)在客戶端A的事務提交以前,打開另外一個客戶端B,更新表account:

 

 

 

 

 

    (3)這時,雖然客戶端B的事務還沒提交,可是客戶端A就能夠查詢到B已經更新的數據:

 

 

 

    (4)一旦客戶端B的事務由於某種緣由回滾,全部的操做都將會被撤銷,那客戶端A查詢到的數據其實就是髒數據:

 

 

 

     (5)在客戶端A執行更新語句update account set balance = balance - 50 where id =1,lilei的balance沒有變成350,竟然是400,是否是很奇怪,數據的一致性沒問啊,若是你這麼想就太天真 了,在應用程序中,咱們會用400-50=350,並不知道其餘會話回滾了,要想解決這個問題能夠採用讀已提交的隔離級別

 

 

 

 

  二、讀已提交

 

    (1)打開一個客戶端A,並設置當前事務模式爲read committed(未提交讀),查詢表account的初始值:

 

 

 

    (2)在客戶端A的事務提交以前,打開另外一個客戶端B,更新表account:

 

 

 

    (3)這時,客戶端B的事務還沒提交,客戶端A不能查詢到B已經更新的數據,解決了髒讀問題:

 

 

 

    (4)客戶端B的事務提交

 

 

    (5)客戶端A執行與上一步相同的查詢,結果 與上一步不一致,即產生了不可重複讀的問題,在應用程序中,假設咱們處於客戶端A的會話,查詢到lilei的balance爲450,可是其餘事務將lilei的balance值改成400,咱們並不知道,若是用450這個值去作其餘操做,是有問題的,不過這個機率真的很小哦,要想避免這個問題,能夠採用可重複讀的隔離級別

 

 

 

 

   三、可重複讀

 

     (1)打開一個客戶端A,並設置當前事務模式爲repeatable read,查詢表account的初始值:

 

 

    (2)在客戶端A的事務提交以前,打開另外一個客戶端B,更新表account並提交,客戶端B的事務竟然能夠修改客戶端A事務查詢到的行,也就是mysql的可重複讀不會鎖住事務查詢到的行,這一點出乎個人意料,sql標準中事務隔離級別爲可重複讀時,讀寫操做要鎖行的,mysql竟然沒有鎖,我了個去。在應用程序中要注意給行加鎖,否則你會以步驟(1)中lilei的balance爲400做爲中間值去作其餘操做

 

 

    (3)在客戶端A執行步驟(1)的查詢:

 

 

    (4)執行步驟(1),lilei的balance仍然是400與步驟(1)查詢結果一致,沒有出現不可重複讀的 問題;接着執行update balance = balance - 50 where id = 1,balance沒有變成400-50=350,lilei的balance值用的是步驟(2)中的350來算的,因此是300,數據的一致性卻是沒有被破壞,這個有點神奇,也許是mysql的特點吧

 

mysql> select * from account; +------+--------+---------+ | id | name | balance | +------+--------+---------+ | 1 | lilei | 400 | | 2 | hanmei | 16000 | | 3 | lucy | 2400 | +------+--------+---------+ 3 rows in set (0.00 sec) mysql> update account set balance = balance - 50 where id = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from account; +------+--------+---------+ | id | name | balance | +------+--------+---------+ | 1 | lilei | 300 | | 2 | hanmei | 16000 | | 3 | lucy | 2400 | +------+--------+---------+ 3 rows in set (0.00 sec)

 

    (5) 在客戶端A開啓事務,查詢表account的初始值

 

mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from account; +------+--------+---------+ | id | name | balance | +------+--------+---------+ | 1 | lilei | 300 | | 2 | hanmei | 16000 | | 3 | lucy | 2400 | +------+--------+---------+ 3 rows in set (0.00 sec)

 

    (6)在客戶端B開啓事務,新增一條數據,其中balance字段值爲600,並提交

 

mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> insert into account values(4,'lily',600); Query OK, 1 row affected (0.00 sec) mysql> commit; Query OK, 0 rows affected (0.01 sec)

 

    (7) 在客戶端A計算balance之和,值爲300+16000+2400=18700,沒有把客戶端B的值算進去,客戶端A提交後再計算balance之和,竟然變成了19300,這是由於把客戶端B的600算進去了

 

,站在客戶的角度,客戶是看不到客戶端B的,它會以爲是天下掉餡餅了,多了600塊,這就是幻讀,站在開發者的角度,數據的 一致性並無破壞。可是在應用程序中,咱們得代碼可能會把18700提交給用戶了,若是你必定要避免這狀況小几率情況的發生,那麼就要採起下面要介紹的事務隔離級別「串行化」

 

mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 18700 |
+--------------+
1 row in set (0.00 sec)

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

mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 19300 |
+--------------+
1 row in set (0.00 sec)

 

  

 

  4.串行化

 

    (1)打開一個客戶端A,並設置當前事務模式爲serializable,查詢表account的初始值:

 

mysql> set session transaction isolation level serializable; Query OK, 0 rows affected (0.00 sec) mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from account; +------+--------+---------+ | id | name | balance | +------+--------+---------+ | 1 | lilei | 10000 | | 2 | hanmei | 10000 | | 3 | lucy | 10000 | | 4 | lily | 10000 | +------+--------+---------+ 4 rows in set (0.00 sec)

 

    (2)打開一個客戶端B,並設置當前事務模式爲serializable,插入一條記錄報錯,表被鎖了插入失敗,mysql中事務隔離級別爲serializable時會鎖表,所以不會出現幻讀的狀況,這種隔離級別併發性極低,每每一個事務霸佔了一張表,其餘成千上萬個事務只有乾瞪眼,得等他用完提交纔可使用,開發中不多會用到。

 

mysql> set session transaction isolation level serializable; Query OK, 0 rows affected (0.00 sec) mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> insert into account values(5,'tom',0); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
相關文章
相關標籤/搜索