mysql中的事務(wù)隔離性

事務(wù)擁有原子性、隔離性、一致性、持久性(acid)。

  • 原子性:要么全成功,要么全失敗。
  • 隔離性:事務(wù)之間必須保持隔離性,互補干擾,不然會出現(xiàn)臟讀,不可重復(fù)讀和幻讀。
  • 一致性:事務(wù)的一致性指的是在一個事務(wù)執(zhí)行之前和執(zhí)行之后數(shù)據(jù)庫都必須處于一致性狀態(tài)。如果事務(wù)成功地完成,那么系統(tǒng)中所有變化將正確地應(yīng)用,系統(tǒng)處于有效狀態(tài)。如果在事務(wù)中出現(xiàn)錯誤,那么系統(tǒng)中的所有變化將自動地回滾,系統(tǒng)返回到原始狀態(tài)。分布式中難點就是事務(wù)的一致性。
  • 持久性:事務(wù)提交的結(jié)果,將永遠保持在數(shù)據(jù)庫中。

注意:

  • mysql中的innodb引擎支持事務(wù),myisam不支持。
  • 代碼中不要使用大事務(wù)。因為事務(wù)都是mysql操作,大多數(shù)mysql操作會數(shù)據(jù)行加共享鎖或者排他鎖的,這些鎖都是阻塞操作,只有在事務(wù)結(jié)束后這些鎖才會釋放。如果事務(wù)執(zhí)行時間較長,鎖將一直不會釋放,如果其他請求也正好獲得鎖住的數(shù)據(jù)行,則會一直等待,等待那個大事務(wù)結(jié)束后才能獲得。

mysql中的事務(wù)自動提交AUTOCOMMIT

在Innodb存儲引擎中事務(wù)默認采取自動提交的模式,也就是說,如果不是顯式的開始一個事務(wù),則每個sql都當作是一個事務(wù)執(zhí)行操作,同樣遵循mvcc,直到顯式的執(zhí)行commit或者rollback表示該事務(wù)結(jié)束。
在當前連接中可以設(shè)置autocommit變量來啟用或者關(guān)閉自動提交,0為關(guān)閉,1為開啟。

 mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 行于數(shù)據(jù)集 (0.04 秒)

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 秒)

測試:客戶端A和客戶端B設(shè)置autocommit為關(guān)閉狀態(tài),對行加排他鎖
1.在客戶端A加鎖

mysql> update users set name='ds' where id=12;
Query OK, 1 rows affected (0.02 秒)
  1. 在客戶端B上也update
mysql> update users set name='ds' where id=12;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

按照之前的理解,在客戶端B上肯定是不會加鎖的,且是可以執(zhí)行成功的。事實證明顯id為12的數(shù)據(jù)已經(jīng)加來排他鎖。

  1. 在客戶端A上執(zhí)行commit后,客戶端B上id為12的行獲得排他鎖,執(zhí)行成功
mysql> commit;
 Query OK, 0 rows affected (0.01 秒)

注意:修改autocommit變量對非事務(wù)類型的表,比如myisam或者內(nèi)存表,不會有任何的影響,對這類表沒有commit或者rollback的概念,也可以說一只處于autocommit啟用的模式

誤區(qū):之前以為只有start transaction來算是事務(wù)執(zhí)行,其實每個sql都是一個事務(wù)的提交,只不過在innodb中是采用的自動提交的方式。

并發(fā)造成事務(wù)隔離性破壞

  • 第一類丟失更新
    在沒有事務(wù)隔離的情況下,倆個事務(wù)都同時更新一條數(shù)據(jù),但是第二個事務(wù)卻中途退出,導(dǎo)致對第一個事務(wù)的更新失敗。
    例:張三讀取火車票,查詢剩余500張,同時李四也查詢并且購買了一張,剩余499張,并且修改了數(shù)據(jù)庫。隨后,張三購買過程中發(fā)生異常,導(dǎo)致事務(wù)回滾,票數(shù)恢復(fù)為500,這樣就導(dǎo)致了李四的修改失敗。

  • 第二類丟失更新,不可重復(fù)讀的特例
    倆個并發(fā)事務(wù)同時讀取一行數(shù)據(jù),然后其中一個對其進行了修改,另一個也進行了修改,然后造成了第一個寫操作的失敗。
    例:現(xiàn)在火車票還剩500張, 張三讀取到票還有500張,操作還未完成,事務(wù)還未提交。于此同時,李四購買了一張票,剩余票為499.最后提交了事務(wù)。隨后,張三也購買了一張,剩余票又為499,但是應(yīng)該是498。覆蓋了張三的事務(wù)。

  • 臟讀
    是指事務(wù)a修改了數(shù)據(jù)庫的某行數(shù)據(jù),但并未提交事務(wù)時,事務(wù)b這時要取這行數(shù)據(jù)進行相關(guān)操作,讀到的數(shù)據(jù)是a已經(jīng)修改過的了,接著a事務(wù)由于某些原因回退。導(dǎo)致b事務(wù)讀到的這行數(shù)據(jù)和數(shù)據(jù)庫不一致,這就是臟讀。
    例:火車票剩余500張,張三購買了一張火車票,數(shù)據(jù)庫修改但是事務(wù)沒提交
    隨后李四也要購買,查到剩余火車票為499張,接著張三發(fā)生突發(fā)事情,事務(wù)回退,火車票剩余500張,這時張三讀到的數(shù)據(jù)和數(shù)據(jù)庫不一致,導(dǎo)致臟讀。

  • 不可重復(fù)讀
    不可重復(fù)讀是指在對于數(shù)據(jù)庫中的某條數(shù)據(jù),一個事務(wù)范圍內(nèi)多次查詢返回不同的數(shù)據(jù)值(這里不同是指某一條或多條數(shù)據(jù)的內(nèi)容前后不一致,但數(shù)據(jù)條數(shù)相同),這是由于在查詢間隔,該事務(wù)需要用到的數(shù)據(jù)被另一個事務(wù)修改并提交了。需要注意的是在某些情況下不可重復(fù)讀并不是問題。不可重復(fù)讀可能發(fā)生在update,delete操作中

    例:現(xiàn)在火車票有500張, 張三購買火車票,讀到還有500張,操作還沒有完成,事務(wù)還沒完成。于此同時李四也要買車票,并且購買了一張,并提交了事務(wù)。這是張三又查了一遍火車票,變成了499張,在一個事務(wù)中前后讀取到的倆次數(shù)據(jù)不一致,導(dǎo)致了不可重復(fù)讀。

  • 幻讀

幻讀:幻讀是事務(wù)非獨立執(zhí)行時發(fā)生的一種現(xiàn)象。例如事務(wù)T1對一個表中所有的行的某個數(shù)據(jù)項做了從“1”修改為“2”的操作,這時事務(wù)T2又對這個表中插入了一行數(shù)據(jù)項,而這個數(shù)據(jù)項的數(shù)值還是為“1”并且提交給數(shù)據(jù)庫。而操作事務(wù)T1的用戶如果再查看剛剛修改的數(shù)據(jù),會發(fā)現(xiàn)還有一行沒有修改,其實這行是從事務(wù)T2中添加的,就好像產(chǎn)生幻覺一樣,這就是發(fā)生了幻讀?;米x和不可重復(fù)讀都是讀取了另一條已經(jīng)提交的事務(wù)(這點就臟讀不同),所不同的是不可重復(fù)讀可能發(fā)生在update,delete操作中,而幻讀發(fā)生在insert操作中

事務(wù)的隔離級別
mysql的innodb引擎的相應(yīng)的隔離級別可以解決相應(yīng)的上述問題。innodb默認的隔離級別是repeatable_read,這個級別可以解決第一類更新失敗、第二類更新失敗、臟讀、不可重復(fù)讀的問題,沒解決幻讀問題

  • read-uncommited(讀取未提交的)
    在該隔離級別,所有事務(wù)都可以看到其他事務(wù)未提交的執(zhí)行結(jié)果。這是事務(wù)最低的隔離級別。解決第一類丟失更新和第二類丟失的問題,但是會出現(xiàn)臟讀、不可重復(fù)讀、幻讀 。本隔離級別很少用于實際應(yīng)用,因為它的性能也不比其他級別好多少

  • read-commited(讀取已提交的)
    保證一個事務(wù)修改的數(shù)據(jù)提交后才能被另外一個事務(wù)讀取,即另外一個事務(wù)不能讀取其他事務(wù)未提交的數(shù)據(jù)。解決第一類丟失更新、第二類丟失更新和臟讀的問題,但會出現(xiàn)不可重復(fù)讀和幻讀問題

  • repeatable-read(可重復(fù)讀)
    保證一個事務(wù)相同條件下前后兩次獲取的數(shù)據(jù)是一致的,為什么能保證前后倆次獲取的數(shù)據(jù)是一致的呢?原因就是mvcc,查詢時只能查到創(chuàng)建版本小于等于數(shù)據(jù)的當前版本且刪除版本大于當前版本的數(shù)據(jù)。解決第一類丟失更新,第二類丟失更新,臟讀、不可重復(fù)讀的問題,但會出現(xiàn)部分幻讀的問題,為什么是部分呢?請看下面的測試

  • serializable(串行化)
    在這個級別,可能導(dǎo)致大量的超時現(xiàn)象和鎖競爭。只要操作產(chǎn)生了共享鎖,就不允許其他事務(wù)修改!簡而言之也是解決幻讀的關(guān)鍵--也就是事務(wù)會將讀取的數(shù)據(jù)集(數(shù)據(jù)行)加上共享鎖和間隙鎖,其他事務(wù)不能寫,可以讀,直到事務(wù)結(jié)束,共享鎖釋放。它解決了所有隔離性相關(guān)問題,但是生產(chǎn)環(huán)境不會使用該隔離級別,效率太低。

注意:隔離級別越高,出現(xiàn)的并發(fā)問題就越少,但是這樣消耗的性能更大,而且并發(fā)能力會很低。所以適合自己的隔離級別是最重要的。

事務(wù)相關(guān)命令

  • 查看當前的事務(wù)隔離級別

    select @@global.tx_isolation; //查看全局的隔離級別,生產(chǎn)環(huán)境使用
    select @@session.tx_isolation;//查看當前會話級別,下面測試使用它查詢
    select @@tx_isolation;
    
  • 設(shè)置事務(wù)隔離級別

    set [global | session ] transaction isolation level [read uncommitted | read committed | repeatable read | serializable]
    

    1.默認的行為(不帶session和global)是為下一個(未開始)事務(wù)設(shè)置隔離級別,得重新開啟事務(wù),不能使用本次事務(wù)。
    2.如果你使用GLOBAL關(guān)鍵字,是在修改完之后創(chuàng)建的所有新連接(除了不存在的連接)設(shè)置默認事務(wù)級別,你需要SUPER權(quán)限來做這個,生產(chǎn)環(huán)境使用global。
    3.使用SESSION 關(guān)鍵字將在當前連接上執(zhí)行的事務(wù)設(shè)置默認事務(wù)級別,不需要重新開啟事務(wù)。 任何客戶端都能自由改變會話隔離級別(甚至在事務(wù)的中間),或者為下一個事務(wù)設(shè)置隔離級別。

  • mysql命令

    start transaction;//開始事務(wù)
    ......
    sql語句
    ......
    commit;//提交事務(wù)
    rollback;//事務(wù)回滾
    

測試
在A客戶和B客戶端同時修改事務(wù)隔離級別。使用以下命令來設(shè)置隔離級別,使用session。特別注意:更改隔離級別之后測試的事務(wù)不能使用之前的事務(wù),之前的事務(wù)應(yīng)該commit或者rollback。在更改完隔離級別之后重新start事務(wù)測試。

set session transaction isolation level [read uncommitted | read committed | repeatable read | serializable]


  • 客戶端A和B隔離級別為read uncommitted
    1.客戶端A開啟事務(wù):

    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from y_login;
    +----+---------+---------+-----------+
    | id | phone   | islogin | logintime |
    +----+---------+---------+-----------+
    | 20 | 896     |       2 | NULL      |
    | 21 | 5623656 |    NULL | NULL      |
    | 22 | 666666  |    NULL | NULL      |
    +----+---------+---------+-----------+
    3 rows in set (0.00 sec)
    
    1. 客戶端b也同時開啟事務(wù):
    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from y_login;
    +----+---------+---------+-----------+
    | id | phone   | islogin | logintime |
    +----+---------+---------+-----------+
    | 20 | 896     |       2 | NULL      |
    | 21 | 5623656 |    NULL | NULL      |
    | 22 | 666666  |    NULL | NULL      |
    +----+---------+---------+-----------+
    3 rows in set (0.00 sec)
    

    3.客戶端A上修改id為22的數(shù)據(jù)

    mysql> update y_login set phone=888 where id=22;      
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql> select * from y_login;
    +----+---------+---------+-----------+
    | id | phone   | islogin | logintime |
    +----+---------+---------+-----------+
    | 20 | 896     |       2 | NULL      |
    | 21 | 5623656 |    NULL | NULL      |
    | 22 | 888     |    NULL | NULL      |
    +----+---------+---------+-----------+
    
    1. 在客戶端b上查詢id為22的數(shù)據(jù)

       mysql> select * from y_login;
       +----+---------+---------+-----------+
       | id | phone   | islogin | logintime |
       +----+---------+---------+-----------+
       | 20 | 896     |       2 | NULL      |
       | 21 | 5623656 |    NULL | NULL      |
       | 22 | 888     |    NULL | NULL      |
       +----+---------+---------+-----------+
      

    可以看到讀到了客戶端A未提交的事務(wù),這時就肯定造成了臟讀。用戶可以拿到這個未提交的phone數(shù)據(jù)做一些其他的相關(guān)操作,最后客戶端A如果rollback的話,關(guān)于phone的一系列操作都是錯誤的。

    5.在客戶端B上對id為22的數(shù)據(jù)進行寫操作

    mysql> update y_login set phone=999 where id=22;      
    ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
    

    可以看到由于客戶端A對這行數(shù)據(jù)進行了寫操作,所以這行數(shù)據(jù)加鎖了,這行數(shù)據(jù)可以讀,但是不能寫,所以事務(wù)中進行寫操作的行會加排他鎖(innodb中是行鎖,排他鎖簡稱寫鎖),就避免了第二類更新丟失的問題。將客戶端A的事務(wù)commit或者rollback后,客戶端A釋放排他鎖,客戶端B搶到排他鎖后可進行讀寫操作,客戶端A釋放時間為6.94s。

    mysql> update y_login set phone=999 where id=22;
    Query OK, 1 row affected (6.94 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    

    在客戶端 C上,沒有開啟事務(wù)正常執(zhí)行id為22的寫操作時,同樣被加鎖,只有客戶端A釋放排他鎖時,才能執(zhí)行id為22的寫操作。


  • 客戶端A和B隔離級別為read committed
    1.客戶端A開啟事務(wù):

    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from y_login;
    +----+---------+---------+-----------+
    | id | phone   | islogin | logintime |
    +----+---------+---------+-----------+
    | 20 | 896     |       2 | NULL      |
    | 21 | 5623656 |    NULL | NULL      |
    | 22 | 8823    |    NULL | NULL      |
    +----+---------+---------+-----------+
    3 rows in set (0.00 sec)
    
    1. 客戶端b也同時開啟事務(wù):
    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from y_login;
    +----+---------+---------+-----------+
    | id | phone   | islogin | logintime |
    +----+---------+---------+-----------+
    | 20 | 896     |       2 | NULL      |
    | 21 | 5623656 |    NULL | NULL      |
    | 22 | 8823    |    NULL | NULL      |
    +----+---------+---------+-----------+
    3 rows in set (0.00 sec)
    

    3.客戶端A上修改id為22的數(shù)據(jù)

    mysql> update y_login set phone=888 where id=22;      
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql> select * from y_login;
    +----+---------+---------+-----------+
    | id | phone   | islogin | logintime |
    +----+---------+---------+-----------+
    | 20 | 896     |       2 | NULL      |
    | 21 | 5623656 |    NULL | NULL      |
    | 22 | 888     |    NULL | NULL      |
    +----+---------+---------+-----------+
    
    1. 在客戶端B上查看數(shù)據(jù),id為22的phone依然是88823,不會讀取未提交的事務(wù)的數(shù)據(jù)。

       mysql> select * from y_login;
       +----+---------+---------+-----------+
       | id | phone   | islogin | logintime |
       +----+---------+---------+-----------+
       | 20 | 896     |       2 | NULL      |
       | 21 | 5623656 |    NULL | NULL      |
       | 22 | 88823   |    NULL | NULL      |
       +----+---------+---------+-----------+
      
    2. 在做個測試,在客戶端B上修改id為22的數(shù)據(jù)

       mysql> update y_login set phone=888 where id=22;
       ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
      

      可以看到由于客戶端A對這行數(shù)據(jù)進行了寫操作,所以這行數(shù)據(jù)加鎖了,這行數(shù)據(jù)可以讀,但是不能寫,所以事務(wù)中進行寫操作的行會加排他鎖,就避免了第二類更新丟失的問題。將客戶端A的事務(wù)commit或者rollback后,客戶端A釋放排他鎖,客戶端B搶到排他鎖后可進行讀寫操作,客戶端A釋放時間為18s。

       mysql> update y_login set phone=999 where id=22;
       Query OK, 1 row affected (18 sec)
       Rows matched: 1  Changed: 1  Warnings: 0
      

    在客戶端 C上,沒有開啟事務(wù)正常執(zhí)行id為22的寫操作時,同樣被加鎖,只有客戶端A釋放排他鎖時,才能執(zhí)行id為22的寫操作。

    1. 當客戶端A提交事務(wù),后查看客戶端B的數(shù)據(jù)

      mysql> select * from y_login;
      +----+---------+---------+-----------+
      | id | phone   | islogin | logintime |
      +----+---------+---------+-----------+
      | 20 | 896     |       2 | NULL      |
      | 21 | 5623656 |    NULL | NULL      |
      | 22 | 888     |    NULL | NULL      |
      +----+---------+---------+-----------+
      

    可以看到id為22的數(shù)據(jù)已經(jīng)更新,讀取的是事務(wù)提交后的數(shù)據(jù)。這樣就避免了臟讀,但是出現(xiàn)了不可重復(fù)讀的問題。


  • 客戶端A和B隔離級別設(shè)置為 repeatable read
    1.客戶端A開啟事務(wù):

    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from y_login;
    +----+---------+---------+-----------+
    | id | phone   | islogin | logintime |
    +----+---------+---------+-----------+
    | 20 | 896     |       2 | NULL      |
    | 21 | 5623656 |    NULL | NULL      |
    | 22 | 888     |    NULL | NULL      |
    +----+---------+---------+-----------+
    3 rows in set (0.00 sec)
    
    1. 客戶端b也同時開啟事務(wù):
    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from y_login;
    +----+---------+---------+-----------+
    | id | phone   | islogin | logintime |
    +----+---------+---------+-----------+
    | 20 | 896     |       2 | NULL      |
    | 21 | 5623656 |    NULL | NULL      |
    | 22 | 888     |    NULL | NULL      |
    +----+---------+---------+-----------+
    3 rows in set (0.00 sec)
    

    3.客戶端A上修改id為22的數(shù)據(jù)

    mysql> update y_login set phone=333 where id=22;      
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql> select * from y_login;
    +----+---------+---------+-----------+
    | id | phone   | islogin | logintime |
    +----+---------+---------+-----------+
    | 20 | 896     |       2 | NULL      |
    | 21 | 5623656 |    NULL | NULL      |
    | 22 | 333     |    NULL | NULL      |
    +----+---------+---------+-----------+
    
    1. 在客戶端B上查看數(shù)據(jù),id為22的phone依然是888,不會讀取未提交的事務(wù)的數(shù)據(jù)。

       mysql> select * from y_login;
       +----+---------+---------+-----------+
       | id | phone   | islogin | logintime |
       +----+---------+---------+-----------+
       | 20 | 896     |       2 | NULL      |
       | 21 | 5623656 |    NULL | NULL      |
       | 22 | 888     |    NULL | NULL      |
       +----+---------+---------+-----------+
      
    2. 在做個測試,在客戶端B上修改id為22的數(shù)據(jù)

       mysql> update y_login set phone=545 where id=22;
       ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
      

    可以看到由于客戶端A對這行數(shù)據(jù)進行了寫操作,所以這行數(shù)據(jù)加鎖了,這行數(shù)據(jù)可以讀,但是不能寫,所以事務(wù)中進行寫操作的行會加排他鎖,就避免了第二類更新丟失的問題。將客戶端A的事務(wù)commit或者rollback后,客戶端A釋放排他鎖,客戶端B搶到排他鎖后可進行讀寫操作,客戶端A釋放時間為18s。

        mysql> update y_login set phone=545 where id=22;
        Query OK, 1 row affected (18 sec)
        Rows matched: 1  Changed: 1  Warnings: 0
    

    在客戶端 C上,沒有開啟事務(wù)正常執(zhí)行id為22的寫操作時,同樣被加鎖,只有客戶端A釋放排他鎖時,才能執(zhí)行id為22的寫操作。

    1. 當客戶端A提交事務(wù),后查看客戶端B的數(shù)據(jù)

       mysql> select * from y_login;
       +----+---------+---------+-----------+
       | id | phone   | islogin | logintime |
       +----+---------+---------+-----------+
       | 20 | 896     |       2 | NULL      |
       | 21 | 5623656 |    NULL | NULL      |
       | 22 | 888     |    NULL | NULL      |
       +----+---------+---------+-----------+
      

    可以看到id為22的依然是888,并沒有因為客戶端A的提交而改變數(shù)據(jù)。所以不可重復(fù)讀問題解決。

    有的人說repeatable read級別可以解決幻讀,有的人說解決不了,其實是解決了一部分,解決讀幻讀,并沒有解決寫幻讀,請看下面的測試,也可以看這篇文章,對這一現(xiàn)在進行很好的測試。

    有一張表如下


    執(zhí)行流程如下

    發(fā)現(xiàn)事務(wù)1中的倆次select結(jié)果相同,且只有初始化的一條,后勤部,并沒有事務(wù)2中insert的數(shù)據(jù),這是因為MVCC的原因,mvcc查詢時只查詢創(chuàng)建版本小于或等于當前版本的原因,所以事務(wù)1中并沒有查到事務(wù)2中的數(shù)據(jù),那既然這樣串行化serializable級別讀貌似就沒啥意義了,帶著疑問繼續(xù)測試

測試前數(shù)據(jù)如下


在事務(wù)1中select查詢到1條數(shù)據(jù),但是在update時卻更新了2條數(shù)據(jù),出現(xiàn)了幻讀的現(xiàn)象。

這種結(jié)果告訴我們其實在MySQL可重復(fù)讀的隔離級別中并不是完全解決了幻讀的問題,而是解決了讀數(shù)據(jù)情況下的幻讀問題。而對于修改的操作依舊存在幻讀問題,就是說MVCC對于幻讀的解決時不徹底的。
為了解決寫幻的情況,serializable隔離級別就派上用場了,在serializable級別中的類似select * from dept這樣的語句也會加上共享鎖,且會加上間隙鎖(在其他隔離級別中此語句屬于快照讀,不加鎖),這樣就導(dǎo)致在事務(wù)2中的插入操作為阻塞狀態(tài),直到事務(wù)1中commit之后才會進行事務(wù)2中的update操作,進而解決了寫幻讀,可以看出serializable級別解決寫幻讀的關(guān)鍵是間隙鎖

  • 客戶端A和B設(shè)置隔離級別為serializable
    事務(wù)會將獲取的數(shù)據(jù)集加上一個共享鎖,且每條數(shù)據(jù)間都會加上間隙鎖,相關(guān)數(shù)據(jù)集的寫操作都不能進行,直到共享鎖釋放也就是事務(wù)提交。
    1. 在客戶端A開始事務(wù)并查詢

       mysql> start transaction;
       Query OK, 0 rows affected (0.00 sec)
      
       mysql> select * from y_login;
       +----+---------+---------+-----------+
       | id | phone   | islogin | logintime |
       +----+---------+---------+-----------+
       | 21 | 5623656 |    NULL | NULL      |
       | 23 | 54115   |    NULL | NULL      |
       | 24 | 541115  |    NULL | NULL      |
       +----+---------+---------+-----------+
      
    這時就在id為21到24的數(shù)據(jù)集加上了共享鎖和間隙鎖,更準確的說是把這個表里面的所有數(shù)據(jù)行加了共享鎖和間隙鎖,因為并沒有查詢條件,之前插入或刪除修改該表的數(shù)據(jù)都會提示加鎖,不能操作。
  1. 在客戶端B上,更新id為24數(shù)據(jù),由于共享鎖不能加排他鎖,所以報加鎖問題

     mysql> update y_login set phone=23 where id=24;
     ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
    
  2. 在客戶端上,新增一條記錄,由于每條記錄間加了間隙鎖,所以報加鎖問題。

     mysql> insert into y_login(phone) values(2442);
     ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
    

現(xiàn)在解決了寫幻讀和讀幻讀的的問題,但是效率很差,很多超時的現(xiàn)象出現(xiàn)。repeatable read隔離級別只能解決寫幻讀的問題。

  1. 在做個測試,在客戶端A查詢id小于24的數(shù)據(jù)集

     mysql> start transaction;
     Query OK, 0 rows affected (0.00 sec)
    
     mysql> select * from y_login where id<24;
     +----+---------+---------+-----------+
     | id | phone   | islogin | logintime |
     +----+---------+---------+-----------+
     | 21 | 5623656 |    NULL | NULL      |
     | 23 | 54115   |    NULL | NULL      |
     +----+---------+---------+-----------+
    

這里是將id小于24的數(shù)據(jù)集都加共享鎖,猜想如果插入一條數(shù)據(jù)id小于24會報加鎖問題,如果id大于24的話應(yīng)該可以插入。在客戶端B插入數(shù)據(jù)

    insert into y_login(id,phone) values(25,2442);
    Query OK, 1 row affected (0.01 sec)
    insert into y_login(id,phone) values(1,2442);
    ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

證明猜想正確。

思考

前提是innodb存儲引擎下,在myisam存儲引擎下是不支持事務(wù)的。在各個隔離級別下,事務(wù)如果沒提交,但是sql已經(jīng)執(zhí)行完后,會直接修改寫入數(shù)據(jù)庫文件嗎?還是必須在事務(wù)結(jié)束后才會寫到數(shù)據(jù)庫文件呢?

之前認為是事務(wù)開始后,執(zhí)行了sql語句后,數(shù)據(jù)庫就會立馬改變,最后數(shù)據(jù)庫在根據(jù)事務(wù)的結(jié)果進行rollback還是commit。一個不經(jīng)意的測試,結(jié)果并不是這樣,數(shù)據(jù)庫而是在事務(wù)結(jié)束后根據(jù)事務(wù)的狀態(tài)來判斷是否更新狀態(tài)。

  • 事務(wù)隔離級別 read uncommitted

    1. 在客戶端A開始事務(wù),此時數(shù)據(jù)庫中id為12的password為42222

       mysql> start transaction;
       Query OK, 0 rows affected (0.01 秒)
      
       mysql> select password from users where id=12;
       +----------+
       | password |
       +----------+
       | 42222    |
       +----------+
      
    2. 繼續(xù)在客戶端操作,更改id為12的password

       mysql> update users set password=333 where id=12;
       Query OK, 1 rows affected (0.01 秒)
      
    3. 在客戶端B上查詢,已經(jīng)變?yōu)?3

       mysql> select password from users where id=12;
       +----------+
       | password |
       +----------+
       | 333      |
       +----------+
      
    4. 查看數(shù)據(jù)庫,此時數(shù)據(jù)未改變333,仍然未事務(wù)開始時數(shù)據(jù)42222


      WX20190620-175004@2x.png
    5. 在客戶端A上事務(wù)commit后,數(shù)據(jù)改變?yōu)?33。

    測試剩余的隔離級別也是同樣情況,磁盤中的mysql文件只有在事務(wù)提交后才會相應(yīng)的改變,也就是在事務(wù)結(jié)束后才會io操作。至于sql為什么查詢出來的改變了?
    引用《高性能mysql》一說中所說,是因為事務(wù)日志,事務(wù)日志可以提高事務(wù)的效率,使用事務(wù)日志,存儲引擎在修改表數(shù)據(jù)的時候只需要修改并往內(nèi)存拷貝,事務(wù)提交后在將內(nèi)存的行記錄記錄到硬盤中的文件中,而不用每次將修改的數(shù)據(jù)本身持久化到磁盤中。事務(wù)日志持久之后,內(nèi)存中修改的數(shù)據(jù)可以慢慢刷新到磁盤中。如果數(shù)據(jù)的修改已經(jīng)記錄到事務(wù)日志并持久化,但數(shù)據(jù)還沒有同步到磁盤,此時系統(tǒng)崩潰的話,存儲引擎會在重啟后自動回復(fù)這部分修改的內(nèi)容。

    由此知道了,事務(wù)的執(zhí)行順序是start transaction,隨后寫入事務(wù)日志,如果rollback的話根據(jù)事務(wù)日志中的事務(wù)id進行回退,如果commit的話將事務(wù)日志中的內(nèi)容寫入到磁盤中進行持久化。

redo undo日志

說到事務(wù)日志就必須說一下redo undo日志,首先介紹一個這倆分別是什么

  • undo日志

用于存放數(shù)據(jù)修改被修改前的值,假設(shè)修改 tba 表中 id=2的行數(shù)據(jù),把Name=’B’ 修改為Name = ‘B2’ ,那么undo日志就會用來存放Name=’B’的記錄,如果這個修改出現(xiàn)異常,可以使用undo日志來實現(xiàn)回滾操作,保證事務(wù)的一致性,事務(wù)能夠回滾也正是因為undo日志的存在。

  • redo日志

當數(shù)據(jù)庫對數(shù)據(jù)做修改的時候,需要把數(shù)據(jù)頁從磁盤讀到buffer pool中,然后在buffer pool中進行修改,那么這個時候buffer pool中的數(shù)據(jù)頁就與磁盤上的數(shù)據(jù)頁內(nèi)容不一致,稱buffer pool的數(shù)據(jù)頁為dirty page 臟數(shù)據(jù),如果這個時候發(fā)生非正常的DB服務(wù)重啟,那么這些數(shù)據(jù)還沒在內(nèi)存,并沒有同步到磁盤文件中(注意,同步到磁盤文件是個隨機IO),也就是會發(fā)生數(shù)據(jù)丟失,如果這個時候,能夠在有一個文件,當buffer pool 中的data page變更結(jié)束后,把相應(yīng)修改記錄記錄到這個文件(注意,記錄日志是順序IO),那么當DB服務(wù)發(fā)生crash的情況,恢復(fù)DB的時候,也可以根據(jù)這個文件的記錄內(nèi)容,重新應(yīng)用到磁盤文件,數(shù)據(jù)保持一致。

這個文件就是redo log ,用于記數(shù)據(jù)修改后的記錄,順序記錄。它可以帶來這些好處:

  • 當buffer pool中的dirty page 還沒有刷新到磁盤的時候,發(fā)生crash,啟動服務(wù)后,可通過redo log 找到需要重新刷新到磁盤文件的記錄;
  • buffer pool中的數(shù)據(jù)直接flush到disk file,是一個隨機IO,效率較差,而把buffer pool中的數(shù)據(jù)記錄到redo log,是一個順序IO,可以提高事務(wù)提交的速度;

倆者區(qū)別可以看這邊文章,我總結(jié)下吧,undo日志是記錄數(shù)據(jù)修改前的數(shù)據(jù),使事務(wù)可以回滾,保證事務(wù)的一致性,而redo日志保存的數(shù)據(jù)修改后的數(shù)據(jù),保證了數(shù)據(jù)不丟失。在事務(wù)中redo 和undo日志都會寫入。

總結(jié)

在隔離性為read committed,repeatable read事務(wù)中會對寫操作的行加排他鎖,讀不加鎖(mvcc適用于這倆種隔離級別)。在read uncommitted隔離級別中寫操作不加排他鎖,數(shù)據(jù)之間會共享。在隔離性為serializable的事務(wù)中會對讀到的數(shù)據(jù)集加共享鎖也會對寫操作的行加排他鎖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.A simple master-to-slave replication is currently being...
    Kevin關(guān)大大閱讀 6,239評論 0 3
  • 本文實驗的測試環(huán)境:Windows 10+cmd+MySQL5.6.36+InnoDB 一、事務(wù)的基本要素(ACI...
    秋名山車神_f776閱讀 363評論 0 3
  • 什么是數(shù)據(jù)庫? 數(shù)據(jù)庫是存儲數(shù)據(jù)的集合的單獨的應(yīng)用程序。每個數(shù)據(jù)庫具有一個或多個不同的API,用于創(chuàng)建,訪問,管理...
    chen_000閱讀 4,143評論 0 19
  • 最近手上要求匹配日語各種片假名平假名的半角全角,第一次弄這個,各種尷尬,在網(wǎng)上查閱了很多資料,不忙了總結(jié)一下,還望...
    小小小小小小米閱讀 973評論 0 1
  • 黃金周里看了一部題為《李茶的姑媽》的喜劇電影,給我留下的印象頗深。 電影的最初鏡頭是一個披戴了白色婚紗的“女人”在...
    欽慕5158閱讀 283評論 0 2

友情鏈接更多精彩內(nèi)容