1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        詳解事務(wù)、隔離級(jí)別、悲觀鎖和樂(lè)觀鎖

        共 5087字,需瀏覽 11分鐘

         ·

        2021-05-13 17:50

        今天,我們來(lái)聊數(shù)據(jù)庫(kù)事務(wù)ACID、隔離級(jí)別、悲觀鎖和樂(lè)觀鎖。無(wú)論是在工作中,還是在筆試面試中,數(shù)據(jù)庫(kù)相關(guān)的問(wèn)題,總是繞不開(kāi),不會(huì)的話,很容易歇菜,你懂的。

        數(shù)據(jù)庫(kù)事務(wù)場(chǎng)景

        在銀行系統(tǒng)中,數(shù)據(jù)庫(kù)事務(wù)是必須的。在電商系統(tǒng)中,也是如此。

        來(lái)看下A給B匯款100元的例子,可以看到,A賬戶扣款100元,此時(shí)如果進(jìn)程崩潰或者機(jī)器掉電,那么這100元就沒(méi)有加到B的賬戶中,自然會(huì)導(dǎo)致用戶的強(qiáng)烈投訴:


        如果先給B賬戶加錢(qián),然后給A賬戶扣錢(qián),會(huì)怎樣呢?可以看到,此時(shí)如果進(jìn)程崩潰或者機(jī)器掉電,銀行白白給B加了100元,而沒(méi)有扣減A的100元,只怕銀行會(huì)虧得沒(méi)褲子穿:

              

        墨菲定律說(shuō):凡是會(huì)出錯(cuò)的事,一定會(huì)出錯(cuò)。 而且,一旦發(fā)生,將造成較大危害。所以,在軟件設(shè)計(jì)上,有必要考慮這種異常。進(jìn)程崩潰,機(jī)房掉電,網(wǎng)絡(luò)抖動(dòng),硬件損壞,都應(yīng)該被視為常態(tài),都應(yīng)該被考慮到。

        如果要在應(yīng)用層處理這些異常問(wèn)題,將極為困難,甚至幾乎不可能。做過(guò)軟件開(kāi)發(fā)的朋友應(yīng)該知道,很多時(shí)候,如果異常問(wèn)題處理得不妥當(dāng),將要投入大量時(shí)間分析和補(bǔ)救,且不一定能補(bǔ)救回來(lái)。

        所以,有必要引入數(shù)據(jù)庫(kù)事務(wù)。所謂事務(wù),就是一組SQL操作,它們不可分割,不能被打斷,要么都成功,要么都失敗。具體地說(shuō),就是要滿足ACID性質(zhì)。

        引入事務(wù)之后,應(yīng)用層再也不用擔(dān)心上述異常了,因?yàn)閿?shù)據(jù)庫(kù)已經(jīng)為我們處理得很好了。很多書(shū)籍把ACID放在一起敘述,我認(rèn)為有點(diǎn)扯,因?yàn)樗麄儾⒉徽弧T谖铱磥?lái),C是AID的最終目的。下面,我們來(lái)看下ACID性質(zhì)。



        Atomicity(原子性)

        古希臘哲學(xué)家德謨克利特認(rèn)為,原子是構(gòu)成世界萬(wàn)物的單元,且不可分割:
        所以,原子性這個(gè)詞的含義就是不可分割。以上述的步驟一和步驟二為例,它們是一個(gè)整體,不可分割,要么同時(shí)成功,要么同時(shí)失敗。
        那么具體怎樣去實(shí)現(xiàn)原子性呢?有興趣的朋友可以了解下undo log, 在此不展開(kāi)敘述。我們不是DBA, 不需要精通數(shù)據(jù)庫(kù)的眾多具體細(xì)節(jié),但是,至少要知道大概的原理和可行性,這可以為我們解決類(lèi)似問(wèn)題提供思路和參考。

        Consistency(一致性)

        一致性是我們最終的目的,籠統(tǒng)地說(shuō),一致性就是要確保數(shù)據(jù)是正確無(wú)誤的。所謂valid data, 其實(shí)就是正確無(wú)誤的data:
        原子性沒(méi)法完全保證一致性,因?yàn)樵诙鄠€(gè)事務(wù)操作數(shù)據(jù)庫(kù)時(shí),還需要涉及到隔離性。

        Isolation(隔離性)

        隔離性,就是要隔離不同事務(wù),隔離性是本文的重點(diǎn),我們會(huì)針對(duì)不同的隔離級(jí)別進(jìn)行介紹,先來(lái)看一眼:
        需要強(qiáng)調(diào)的是,每種存儲(chǔ)引擎的實(shí)現(xiàn)不盡一致,在可重復(fù)讀隔離級(jí)別下,有的朋友在進(jìn)行驗(yàn)證時(shí),并未出現(xiàn)所謂的幻讀,這是因?yàn)椋?/span>
        • InnoDB通過(guò)MVCC部分地解決了幻讀問(wèn)題:a. 針對(duì)select不會(huì)有幻讀;b. 針對(duì)select for update會(huì)有幻讀。

        • 其它很多數(shù)據(jù)庫(kù)引擎,還是存在幻讀問(wèn)題。
        關(guān)于InnoDB是否存在幻讀問(wèn)題,我們將在本文的實(shí)驗(yàn)部分進(jìn)行驗(yàn)證。

        Durability(持久性)

        持久性的意思是,一旦事務(wù)提交,它對(duì)數(shù)據(jù)庫(kù)的變更是永久性的。實(shí)際上,事務(wù)提交后,最后不一定會(huì)落地到數(shù)據(jù)庫(kù)中(比如落地時(shí)機(jī)器斷電了),那怎么保證一定要落地成功呢?
        這就涉及到redo log了,我們也不需要具體知道redo log的細(xì)節(jié),但是,我們從邏輯上可以縷清:redo log要記錄什么?redo log為什么能保證持久性?
        很多時(shí)候,就是這樣,對(duì)于不太相關(guān)的東西,可以不精通,但至少要了解大概邏輯和思路。這樣才能說(shuō)服自己,才不會(huì)有一種玄乎其玄的感覺(jué)。

        接下來(lái),我們看這個(gè)問(wèn)題:客戶端A的事務(wù),是否應(yīng)該看到客戶端B的事務(wù)所作的修改?這就涉及到數(shù)據(jù)庫(kù)事務(wù)的隔離級(jí)別。
        在本文中,如下圖示都是基于我的實(shí)際驗(yàn)證。建議有興趣的朋友一起動(dòng)手,感受一下。
        說(shuō)明:事務(wù)A和事務(wù)B位于兩個(gè)不同的終端窗口,對(duì)應(yīng)兩個(gè)不同的進(jìn)程,在改變隔離級(jí)別時(shí),僅改A的隔離級(jí)別來(lái)進(jìn)行驗(yàn)證。

        1.讀未提交

        我們來(lái)看看讀未提交的場(chǎng)景:
        可見(jiàn),設(shè)置讀未提交后,事務(wù)B在未提交時(shí),事務(wù)A讀出了a=10,  這是臟數(shù)據(jù)(B事務(wù)被回滾了),這就是所謂的“臟讀”。

        2.讀已提交

        我們來(lái)看看讀已提交的場(chǎng)景
        可見(jiàn),設(shè)置讀已提交后,事務(wù)B在未提交時(shí),事務(wù)A讀出了a=0, 在事務(wù)B提交后,又讀出了a=10,  出現(xiàn)了“不可重復(fù)讀”。

        3. 可重復(fù)讀

        我們來(lái)看看可重復(fù)讀的場(chǎng)景
        可以看到,看事務(wù)A內(nèi),讀取的值具有前后不變的特點(diǎn),這就是“可重復(fù)讀”。只有當(dāng)事務(wù)A提交后,才能讀出a=10. 在MySql中,默認(rèn)的隔離級(jí)別就是可重復(fù)讀。

        接下來(lái),我們看一個(gè)魔幻現(xiàn)象:
        在B事務(wù)提交后,A事務(wù)執(zhí)行select ... where a = 100時(shí),發(fā)現(xiàn)還是無(wú)記錄,可見(jiàn)此時(shí)并未產(chǎn)生“幻讀”。但是,如果用select for update, 則出現(xiàn)了“幻讀”現(xiàn)象。
        可見(jiàn),在InnoDB可重復(fù)讀的隔離級(jí)別中,并未完全解決“幻讀”問(wèn)題,而是解決了讀數(shù)據(jù)情況下的“幻讀”問(wèn)題,而對(duì)于修改的操作依然存在“幻讀”問(wèn)題。

        4.串行化

        我們來(lái)看看串行化的場(chǎng)景
        可以看到,即使對(duì)于讀操作,也會(huì)加鎖,一個(gè)事務(wù)要等待另一個(gè)事務(wù)完成。串行化是完全的隔離級(jí)別,會(huì)導(dǎo)致大量超時(shí)和鎖競(jìng)爭(zhēng)問(wèn)題,在高并發(fā)場(chǎng)景中,較少用到串行化。在SQLite中,默認(rèn)的隔離級(jí)別就是串行化。
             

        丟失更新問(wèn)題

        有了這些隔離級(jí)別,就萬(wàn)事大吉了嗎? 當(dāng)然不是。以MySql為例,在默認(rèn)隔離級(jí)別下,會(huì)有丟失更新的問(wèn)題。
        領(lǐng)導(dǎo)A給你加了30元的雞腿,領(lǐng)導(dǎo)B給你加了40元的雞腿,最終結(jié)果發(fā)現(xiàn),只有40元雞腿,顯然,這是不合理的:
        怎么解決這種問(wèn)題呢?可以考慮引入悲觀鎖或樂(lè)觀鎖。

        悲觀鎖

        所謂悲觀鎖,就是持悲觀態(tài)度,認(rèn)為一定會(huì)有沖突,所以提前加強(qiáng)保護(hù)。悲觀鎖可以用select for update來(lái)實(shí)現(xiàn),之前項(xiàng)目中就經(jīng)常這樣玩,但后來(lái)重構(gòu)了代碼,統(tǒng)一優(yōu)化成了分布式鎖。
        使用分布式鎖, 代碼示意如下(如下使用方法有問(wèn)題):
        func proc() {money := queryMoneyFromDb()  begin lock     begin transaction         money += req.Money         setToDb(money)     end transaction  end lock}
        上述代碼的使用是有問(wèn)題的,想一下為什么?
        當(dāng)兩個(gè)進(jìn)程都讀取money=0后,進(jìn)程A獲取鎖,并且執(zhí)行完畢后,money=30,然后進(jìn)程B獲取鎖,執(zhí)行完畢后,顯然可知,最后的結(jié)果是money=40,仍然存在丟失更新的問(wèn)題。
        曾經(jīng)在項(xiàng)目中,就出現(xiàn)過(guò)這種錯(cuò)誤,導(dǎo)致了低概率的金額不匹配,比較難發(fā)現(xiàn)問(wèn)題,最后還是通過(guò)對(duì)賬發(fā)現(xiàn)了,然后查出上述錯(cuò)誤的用法。
        正確使用悲觀鎖代碼示意如下:
        func proc() {    begin lock      begin transaction        money := queryMoneyFromDb()        money += req.Money        setToDb(money)      end transaction    end lock}

        樂(lè)觀鎖

        所謂樂(lè)觀鎖,就是抱有很樂(lè)觀的態(tài)度,也就是假定不會(huì)存在數(shù)據(jù)沖突(即使有沖突也不怕,樂(lè)觀得很)。具體實(shí)現(xiàn)時(shí),可以在數(shù)據(jù)上打一個(gè)version標(biāo)記,基于version進(jìn)行控制,代碼示意如下:
        func proc() {   begin transaction      select * from T where user_id = 123456  // 假設(shè)查到的version為100      update T set money = xxx, version = version + 1 where user_id = 123456 and version = 100;   end transaction}
        分析一下:進(jìn)程A和進(jìn)程B都讀到了version=100的數(shù)據(jù),進(jìn)程A在加完30元后,同時(shí)讓version變成了101;此時(shí)進(jìn)程B去執(zhí)行,突然發(fā)現(xiàn)不滿足where version=100這個(gè)條件,所以更新失敗,這是合理的,符合預(yù)期,寧可執(zhí)行失敗,也不能產(chǎn)生數(shù)據(jù)錯(cuò)誤。
        這里有一個(gè)極為微妙的問(wèn)題:在MySql可重復(fù)讀隔離級(jí)別下,當(dāng)進(jìn)程A的update執(zhí)行成功并且提交事務(wù)后,version變?yōu)榱?01, 但是在進(jìn)程B看來(lái),version還是100(可重復(fù)讀),  為什么B在執(zhí)行update的時(shí)候,在where version=100條件下又無(wú)法真正執(zhí)行update呢?
        要注意,可重復(fù)讀是針對(duì)select而言的,而不是select for update或者update之類(lèi)的操作,當(dāng)A進(jìn)程事務(wù)提交后,B進(jìn)程事務(wù)看到的情況如下:
        mysql> select * from user;+----+-------+---------+| id | money | version |+----+-------+---------+|  1 |     0 |     100 |+----+-------+---------+1 row in set (0.00 sec)
        mysql> select * from user for update;+----+-------+---------+| id | money | version |+----+-------+---------+|  1 |    30 |     101 |+----+-------+---------+1 row in set (0.25 sec)
        mysql> select * from user;+----+-------+---------+| id | money | version |+----+-------+---------+|  1 |     0 |     100 |+----+-------+---------+1 row in set (0.00 sec)
        可見(jiàn),對(duì)B事務(wù)而言,用select看,看不到B事務(wù)的更新,這滿足事務(wù)的可重復(fù)讀。但是,當(dāng)使用select for update時(shí),能看到B事務(wù)的更新。
        所以,當(dāng)B事務(wù)使用update嘗試更新where  version=100的記錄時(shí),發(fā)現(xiàn)更新失敗,這是我們期望的結(jié)果,寧可執(zhí)行失敗,也不能產(chǎn)生數(shù)據(jù)錯(cuò)誤。針對(duì)這種失敗,可以采用多次重試。

        至于悲觀鎖和樂(lè)觀鎖的選擇,還是要依賴(lài)于具體業(yè)務(wù)。數(shù)據(jù)的一致性如此重要,可千萬(wàn)別把用戶的錢(qián)給算錯(cuò)了。
        對(duì)于頻繁寫(xiě)沖突的業(yè)務(wù),用樂(lè)觀鎖肯定是不太好的,重試操作會(huì)增加各種開(kāi)銷(xiāo),此時(shí)可以考慮使用悲觀鎖。對(duì)于寫(xiě)沖突較少發(fā)生的場(chǎng)景,那樂(lè)觀鎖就非常適合了。

        ·················· END ··················

        點(diǎn)擊關(guān)注公眾號(hào),免費(fèi)領(lǐng)學(xué)習(xí)資料

        你好,我是濤哥,CSDN排名第一。
        自學(xué)計(jì)算機(jī),畢業(yè)后就職華為騰訊。
        從事軟件開(kāi)發(fā),期待與你一起成長(zhǎng)。

        文 章 類(lèi) 別
        1.編程之路    2.面試刷題
        3.職場(chǎng)進(jìn)階    4.雜文薈萃
           
        推 薦 閱 讀
        萬(wàn)字攻略,詳解騰訊面試
        出去之后,好好做人,華為兩年,苦樂(lè)參半
        點(diǎn)“贊”和“在看”哦
        瀏覽 87
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            双龙h啊~嗯啊h巨大漫画 | 日韩成人一区二区三区影院 | 韩国三级《瑜伽教练》在线观看 | 日韩国产专区 | 毛片美女网站 | 张津瑜国产在线观看 | 久久五月综合 | 张淑芬和兴云弄雨的故事简介 | 婷婷三级片 | 天天草天天 |