Mysql事務隨筆

1、什麼是事務

  數據庫中的概念,按我我的理解:可以保證一組任務所有執行成功或者所有執行失敗的這麼個機制,叫事務html

  事務是數據庫中重要概念,若是沒有這種保障機制,數據庫中的數據就是不安全的(就是沒法保證數據的正確性)mysql

  在數據庫中,一組任務,就是放在一塊兒執行的多條sqlsql

2、ACID保證數據安全

  因此如何才能保證數據安全呢?前人總結了以下四點數據庫

  1.原子性,一組任務所有執行成功或者所有執行失敗.這是基礎,由於咱們一組操做通常是有順序的,有互相依賴關係的,要同步的,不能A表修改了,B表修改失敗,這樣數據就不一樣步了,也就是不正確了安全

  2.一致性,就是咱們的一組操做應該合轍(合乎邏輯,合乎道理),常見例子就是互相轉帳的例子:A轉給C  10塊,B轉給C  20塊  ,C最後手裏必定要多  30塊,這樣就是保證了一致性,這樣數據才正確多線程

  3.隔離性,隔離性在併發事務中才能體現做用,每一個單獨的事務要保證數據正確性,那麼同時併發的多個單獨的事務最後的結果,也應該保證最後數據正確併發

  4.持久性,就是咱們所作的操做必須被持久的保存下來,不能說事務正確的結束了,可是數據庫沒有被修改,這時數據也是不正確的.oracle

  因此保證數據正確性,就要保證數據開始是正確的,過程當中每一步操做都是正確的(原子性,隔離性),最後結果是正確的(一致性),並且結果被正確的保留下來(持久性)運維

  咱們給這些特性總結了一個名字,叫事務,因此不少不理解事務的童鞋,能夠不要去理解事務是什麼,而應該反過來理解什麼是事務(長成這樣的就是事務)工具

要保證事務,須要理解的知識點以下:

  兩do一點:redo日誌,undo日誌,checkpoint

  MVCC(ReadView)

  事務隔離級別

  savepoint

  鎖

  MDL鎖

  這裏我只是給本身留的隨筆,因此不會詳細講解,可是會留入口

3、事務流程

大體分這麼三條線

  第一條紅線:成功結果,事務正常執行,開啓事務直到提交事務,刷新到硬盤上(只有進入硬盤纔算持久化完畢,但並非說數據要刷入硬盤,而是redo日誌進入硬盤,便可保證持久性)

  第二條橘線和第三條綠線:都是失敗結果,緣由能夠是報異常,或者手動回滾數據,致使事務失敗,完成回滾(使用undo日誌回滾數據)

  只有當事務處於提交的或者停止的狀態時,一個事務的生命週期纔算是結束了。對於已經提交的事務來講,該事務對數據庫所作的修改將永久生效(redo日誌的做用),對於處於停止狀態的事務,該事務對數據庫所作的全部修改都會被回滾到沒執行該事務以前的狀態(undo日誌的做用)。

 

事務的開啓與提交與回滾操做

  前提: 數據庫默認事務自動提交,因此默認每一條sql的執行都是一個單獨的事務

 

      涉及到的系統參數爲autocommit ,ON和OFF

      查看    show variables like 'autocommit' 

      修改    SET autocommit = OFF;  -- 關閉自動提交事務

      通常的,若是須要向數據庫導入大量數據,因爲sql執行默認單條sql爲一次事務,若是有10條sql,就會分別開啓事務提交事務10次,因此能夠先關閉事務自動提交,本身手動開一次,執行sql,提交事務

 

 

  1.開啓事務:兩種命令

    BEGIN [WORK]   (BEGIN  等價於  BEGIN WORK , 因此work能夠寫能夠不寫)

    START TRANSACTION [READ ONLY | READ WRITE | WITH CONSISTENT SNAPSHOT];

    兩種語法區別:第二種能夠指定事務是隻讀模式(事務中不能有寫操做的sql),讀寫模式(事務中可讀可寫,默認的)和一致性讀(一致性讀,又稱爲快照讀。使用的是MVCC機制讀取undo中的已經提交的數據。因此它的讀取是非阻塞的)

    一致性讀參看 https://www.cnblogs.com/digdeep/p/4947694.html

     若是咱們不顯式指定事務的訪問模式,那麼該事務的訪問模式就是讀寫模式

  2.提交事務:一種命令

     COMMIT [WORK]     (COMMIT 等價於  COMMIT WORK , 因此work能夠寫能夠不寫)

  3.回滾事務:一種命令

    ROLLBACK [WORK (ROLLBACK 等價於  ROLLBACK WORK , 因此work能夠寫能夠不寫)

  4.回滾到savepoint

    SAVEPOINT [自定義savepoint的name];   -- 設置保存點

    ROLLBACK TO [自定義savepoint的name]   -- 回滾到以前的某個保存點(只能在當前狀態向前回滾,不能向後回滾)

上面說的是手動提交事務,mysql中還有一些自動會提交事務的命令

  手動提交事務使用commit,可是有些語句會自動提交當前事務,稱之爲隱式提交

  1.頭一個就是開啓自動提交事務,當前sql的事務會隱式提交

   2.DDL會隱式提交事務

    1. 若是A事務對T表有DML事務,未提交以前A事務執行了對T表表結構的修改,會自動提交A事務,而後執行DDL.
    2. 若是A事務對T表有DML事務,B事務修改非A表表結構,會將B事務自動提交,但不會影響到A事務,A事務依然沒有提交,程序正常運行,新起一個C事務依然讀不到A事務未提交的數據
    3. 若是A事務對T表有DML事務,B事務修改T表表結構,因爲修改同一張表,按道理是要有鎖保護的,不然A事務改字段B事務改表結構不就衝突了麼,但mysql5.1以前確實是沒有因此B事務能夠直接執行DDL操做,而且致使A事務出錯,可是在mysql5.5以後引入了一個MDL鎖,這樣就會讓事務按順序執行,個人mysql版本爲8.0,因此我看到的效果是B事務的DDL一直等待(由於先開始A事務,並且A事務並未提交).直到A事務提交後,B事務的DDL纔會執行.這個操做基於MDL鎖(參看  http://www.cnblogs.com/zengkefu/p/5690385.html)

      結論:DDL只會隱式提交本身當前的事務,不會操做其餘事務,若是衝突,則等待,基於MDL鎖(若是影響到了其餘事務(其它事務中有當前DDL的表),會等待直到其餘事務提交後才能執行DDL操做.)

          最後一句話:不要在程序運行過程當中隨便DDL,由於若是DDL會鎖全表,若是表中數據量大,就會卡住別的事務,影響生產環境

        但若是在生產環境中修改表結構怎麼辦

        這裏留兩個博客,我本身沒嘗試過,以後再深研究

         http://www.cnblogs.com/wangtao_20/p/3504395.html(靈感來源)

         https://zhang.ge/5134.html (版本較新)  在線ddl工具(直接甩鍋運維了[手動偷笑])(這個博客我的感受很詳細,因此本身留了一份截圖,以防以後404 

                                                     (傳送門: https://www.cnblogs.com/fast-bullet/p/10848561.html)

  3.BEGIN命令    

    開始事務的操做會隱式提交當前所在事務,而後另開一個事務

  4.使用LOCK TABLES、UNLOCK TABLES等關於鎖定的語句也會隱式的提交前邊語句所屬的事務

  5.好比咱們使用LOAD DATA語句來批量往數據庫中導入數據時,也會隱式的提交前邊語句所屬的事務。

  6.關於MySQL複製的一些語句

     使用START SLAVESTOP SLAVERESET SLAVECHANGE MASTER TO等語句時也會隱式的提交前邊語句所屬的事務。

     使用ANALYZE TABLECACHE INDEXCHECK TABLEFLUSH LOAD INDEX INTO CACHEOPTIMIZE TABLEREPAIR TABLERESET等語句也會隱式的提交前邊語句所屬的事務。

 5、redo日誌

  爲了將數據持久的保存下來,同時沒保存以前還不能更改數據庫,因此咱們須要將咱們所作的全部操做筆記一下,以後須要還原數據(這裏的還原不等於回滾,應該是還原到他本來的樣子,即操做完的樣子),咱們只須要按照筆記一步一步操做就好了.這個筆記就是redo日誌(重作日誌)

  redo日誌:重作日誌,事務中包括多條sql,一條sql可能會對數據庫的基礎結構(如:B+樹,數據頁,索引,隱藏列等)產生操做,咱們稱之爲MTR(mini transaction),每一個MTR都會記錄成一條至多條redo日誌,mtr至關於底層最小的一次原子性操做,因此咱們redo日誌通常是多條才能表示一次原子操做

  這樣的話,多條redo日誌組成一個MTR,多個MTR組成一條sql,多條sql組成一次事務,只要redo日誌存在,就能將當前事務永久的保存下來(持久性)

  redo日誌會按順序紀錄當前事務操做,保證數據持久化,若是一個事務中只有一條操做,就只記錄一條redo日誌,若是一個事務有多條操做,就會紀錄多條日誌,咱們上面說了,可能多條日誌纔是一個原子性操做,可是程序怎麼知道日誌是從哪到哪算是一組呢?mysql中每條redo日誌佔用8個字節,其中7個字節用來存儲日誌內容,最左邊的第八個位置用來作標記,若是是單條日誌則爲1,若是是多條日誌則爲0,這樣掃描到1說明當前爲一個原子操做,數據恢復便可,若是是多條,會在全部操做記錄完以後再加一個類型爲MLOG_MULTI_REC_END類型的redo日誌,掃描到這個日誌,就知道當前操做完成,纔會恢復以前一組redo日誌,不然直接丟棄,保證未提交數據不被恢復.

   如今咱們瞭解了redo日誌的做用,mysql的持久化依賴於redo日誌,redo日誌是否被記錄到硬盤中是持久化實現的關鍵,咱們知道刷盤操做很慢,因此咱們的redo日誌一開始會記錄在一個叫log buffer的緩衝區中,那麼何時會將緩衝中的日誌刷到磁盤呢?

  1.當內存不足

  2.事務提交時會刷到硬盤中(這樣就能保證持久化,mysql中提供了設置,也能夠設置事務提交時不持久化)

  3.mysql後臺有一個線程,大約每秒都會刷新一次log buffer中的redo日誌到磁盤保證數據持久化  

  這個redo日誌會被存放在mysql的data下的ib_logfile[數字]文件下

 

 6、undo日誌

  可是咱們有時候可能須要回滾操做,好比事務中間出錯,或者手動rollback,這個時候咱們不能用redo日誌來操做,不然咱們一條一條redo要執行到哪年纔算完啊,因而有了undo日誌,這個日誌用來備份,好比你把張三改爲李四,undo日誌會先將張三保存下來,而後再改爲李四,這樣你想要回滾的時候,我一下就知道李四以前是張三,由於我作了備份,直接還原便可,(能夠說備份,也能夠理解成當前數據快照,或者說會記錄一條若是你要回滾,須要作什麼樣的操做)

  你插入一條記錄時,至少要把這條記錄的主鍵值記下來,以後回滾的時候只須要把這個主鍵值對應的記錄刪掉就行了。

  你刪除了一條記錄,至少要把這條記錄中的內容都記下來,這樣以後回滾時再把由這些內容組成的記錄插入到表中就行了。

  你修改了一條記錄,至少要把修改這條記錄前的舊值都記錄下來,這樣以後回滾時再把這條記錄更新爲舊值就行了。

這樣事務一旦出問題,之間按照undo回滾便可

  通常的,全部寫庫操做都會放在事務中執行,每一個事務又有本身的一個id,undo日誌會按順序記錄數據內容和事務id,方便往後回滾,由於不能由於A事務回滾而把B事務已提交的操做撤銷,互相不能有影響(隔離性)

7、事務隔離性

  多個事務在併發時,可能產生以下問題

  1.髒寫(丟失更新)

  所謂髒寫,就是A事務修改了一條數據還未提交,B事務也去修改這個數據,而後A事務提交了,發現不是本身想要的結果,本身改的數據沒了,這就是丟失更新.

  這個問題不須要考慮,由於無論數據庫哪一個隔離級別,當兩個事務A和B嘗試去更新同一條數據時,假定A先更新數據,會對更新的數據行記錄加上排他鎖(也叫寫鎖,悲觀鎖),除非事務A提交或終止從而釋放排他鎖,不然事務B都是沒法更新數據的。加鎖排隊修改.避免髒寫

   2.髒讀

  所謂髒讀,就是指事務A讀到了事務B尚未提交的數據,好比數據庫中有個數據100,事務A開啓事務,此時切換到事務B,事務B開啓事務將100-1,還未提交,此時切換回事務A,事務A讀取到了99,由於事務B還未提交,就讓A讀到了,咱們說A事務發生了髒讀.爲何這是個問題呢,由於事務是能夠回滾的,萬一B回滾了事務,那麼A讀到的99實際上是不存在的,就可能會出現問題

  3.不可重複讀(虛讀)

  所謂不可重複讀,就是指在一個事務裏面讀取了兩次某個數據,讀出來的數據不一致。以銀行取錢爲例,事務A開啓事務-->查出銀行卡餘額爲1000元,此時切換到事務B事務B開啓事務-->事務B取走100元-->提交,數據庫裏面餘額變爲900元,此時切換回事務A,事務A再查一次查出帳戶餘額爲900元,這樣對事務A而言,在同一個事務內兩次讀取帳戶餘額數據不一致,這就是不可重複讀。

  4.幻讀

  所謂幻讀,就是指在一個事務兩次查詢的操做中發現了多了或者少了數據。好比學生信息,事務A查詢全部大於20歲的學生信息,此時切換到事務B,事務B開啓事務-->事務B插入了一條學生數據,年齡25,此時切換回事務A,事務A再次查詢的時候發現多了一條數據,這就是幻讀,幻讀出現的前提是併發的事務中有事務發生了插入、刪除操做。

  正是由於多線程的緣由,當某一個正在寫數據時,另外一個線程讀取的數據是不許確的,因此須要進行同步處理.

  mysql利用鎖和MVCC(多版本併發控制)來解決上面所說的問題,爲了方便設置,數據庫規定了以下四個隔離級別,不一樣的隔離級別解決不一樣的問題

  

這個圖是隨便找了一張,連接 https://www.erlo.vip/share/2/26041.html

 

  通常生產中不太在乎幻讀的問題,因此生產環境通常設置隔離級別爲不可重複讀(RC),oracle默認就是RC,mysql默認級別爲RR

  那麼mysql如何解決這些問題的呢,方案以下

8、MVCC的基礎

  MVCC  多版本併發控制.  

  不解決任何問題隔離級別就是RU,不解決問題因此不考慮,以後RC和RR都是對於讀的併發優化,解決併發讀的問題主要依靠MVCC機制和鎖

  其中由於鎖機制是一種預防性的,讀會阻塞寫,寫也會阻塞讀,當鎖定粒度較大,時間較長是併發性能就不會太好;而MVCC是一種後驗性的,讀不阻塞寫,寫也不阻塞讀,等到提交的時候才檢驗是否有衝突,因爲沒有鎖,因此讀寫不會相互阻塞,從而大大提高了併發性能。

  因此MVCC性能更加優秀,MySQL在 read committed ,Repeatable Read 兩個級別下都會使用到MVCC, 而且只在這兩個級別下使用。

  MVCC利用readview和undo日誌來實現

undo

  這個MVCC和undo日誌有什麼關係呢,爲啥要用undo實現?由於undo日誌爲了方便回滾,會按順序記錄數據內容和事務id,這樣我以前的狀態其實都在undo中,A事務操做數據會給undo加一條日誌,B事務操做也會給undo加一條日誌,對於數據庫自己來講,如今是什麼狀態數據庫本身是明明白白的,可是因爲A事務沒有提交,因此B事務在讀數據的時候,應該讀取A事務提交以前的樣式,好比A將張三改爲李四,這時候B讀數據的時候,應該讀到張三,由於李四還未持久化,那麼這個張三在哪記錄着呢,就在undo日誌上,因此想要讀事務以前的數據,就得靠undo日誌.對同一條數據所作的屢次操做會產生多條undo日誌,而這些undo日誌串起來的鏈條咱們成爲版本鏈(這個鏈表頭就是當前的數據,不是說只記錄歷史,還包括當前數據),你的事務只能讀到以前版本的數據,未提交的數據就讀不到了,這樣就保證了重複讀

readview

  對於使用RU隔離級別的事務來講,因爲能夠讀到未提交事務修改過的記錄,因此直接讀取記錄的最新版本就行了;對於使用SERIALIZABLE隔離級別的事務來講,設計InnoDB的大叔規定使用加鎖的方式來訪問記錄,因此也跟MVCC不要緊;對於使用READ COMMITTED和REPEATABLE READ隔離級別的事務來講,都必須保證讀到已經提交了的事務修改過的記錄,也就是說假如另外一個事務已經修改了記錄可是還沒有提交,是不能直接讀取最新版本的記錄的,核心問題就是:須要判斷一下版本鏈中的哪一個版本是當前事務可見的。爲此提出了一個ReadView的概念.

  readview其實就相似與一個查詢快照

  1.若是使用READ COMMITTED隔離級別的事務,在每次查詢開始時都會生成一個獨立的ReadView。假如A事務開啓->A事務查詢(此時有一個快照)  ,  B事務開啓->B事務修改內容(記錄在了undo日誌中) ->B事務提交, A事務繼續第二次查詢(因爲又生出一個readview,因此這個readview長得和剛纔那個不同,是很正常的,這個readview能讀到最新的數據,是由於B事務已經提交,A是能夠直接讀取undo日誌上的新數據的)

  2.若是使用REPEATABLE READ ,會在第一次讀取數據時生成一個ReadView,以後每次查詢都複用這個快照.假如A事務開啓->A事務查詢(此時有一個快照)  ,  B事務開啓->B事務修改內容(記錄在了undo日誌中) ->B事務提交, A事務繼續第二次查詢(查詢剛纔那個快照)

   從上邊的描述中咱們能夠看出來,所謂的MVCC(Multi-Version Concurrency Control ,多版本併發控制)指的就是在使用READ COMMITTD、REPEATABLE READ這兩種隔離級別的事務在執行普通的SEELCT操做時訪問記錄的版本鏈的過程,這樣子可使不一樣事務的讀-寫、寫-讀操做併發執行,從而提高系統性能。READ COMMITTD、REPEATABLE READ這兩個隔離級別的一個很大不一樣就是:生成ReadView的時機不一樣,READ COMMITTD在每一次進行普通SELECT操做前都會生成一個ReadView,而REPEATABLE READ只在第一次進行普通SELECT操做前生成一個ReadView,以後的查詢操做都重複使用這個ReadView就行了。

   這樣就保證了不可重複讀的問題

 小結

  MVCC讀不影響寫,寫不影響讀,實現高效率的可重複讀(參考連接 https://www.imooc.com/article/17289)

  1.讀不影響寫:事務以排他鎖的形式修改原始數據,讀時不加鎖,由於 MySQL 在事務隔離級別Read committed 、Repeatable Read下,InnoDB 存儲引擎採用非鎖定性一致讀--即讀取不佔用和等待表上的鎖。即採用的是MVCC中一致性非鎖定讀模式。因讀時不加鎖,因此不會阻塞其餘事物在相同記錄上加 X鎖來更改這行記錄。
  2.寫不影響讀:事務以排他鎖的形式修改原始數據,當讀取的行正在執行 delete 或者 update 操做,這時讀取操做不會所以去等待行上鎖的釋放。相反地,InnoDB 存儲引擎會去讀取行的一個快照數據(readview)。

 

 purge工做線程

  題外話,正式因爲有MVCC這種機制,因此刪除操做執行的時候(還未提交事務),是不會刪除掉數據的,而是打一個刪除標記,這樣MVCC纔有機會讀取到以前的readview,那何時真正刪除這個數據呢?就須要用到purge工做線程了.相似於一個垃圾回收器.

 

網上不少帖子說防止幻讀是使用了鎖和MVCC,可是還有一部分人說MVCC不能防止幻讀,這裏給出一個訂閱號文章,能夠本身去看,關鍵點我複製到我這了

  https://mp.weixin.qq.com/s/wSlNZcQkax-2KZCNEHOYLA 

  MVCC不能禁止幻讀的原理

  T1第一次執行普通的SELECT語句時生成了一個ReadView,以後T2向hero表中新插入了一條記錄便提交了,ReadView並不能阻止T1執行UPDATE或者DELETE語句來對改動這個新插入的記錄(由於T2已經提交,改動該記錄並不會形成阻塞),可是這樣一來這條新記錄的trx_id隱藏列就變成了T1的事務id,以後T1中再使用普通的SELECT語句去查詢這條記錄時就能夠看到這條記錄了,也就把這條記錄返回給客戶端了。由於這個特殊現象的存在,你也能夠認爲InnoDB中的MVCC並不能完徹底全的禁止幻讀。

9、鎖

 

10、寫在後面

  參考文章 : 掘金小冊中的<<MySql是怎樣運行的:從根上理解MySql> >

  做者 文章寫的仍是很詳實的,經過閱讀他的這本小冊,寫了上邊的這篇隨筆,這本小冊讀完讓我對mysql中的一些原理又略知了三四,可是本人對於小冊中不少細節還未能吸取,因此上面的文字描述並非相似於教學同樣教讀者怎麼作,權當本身的一篇隨筆,記錄一些關鍵知識點,簡單串聯了一下,若是有緣能看到這篇文章,建議能夠去我提供的連接或者本身搜索其中知識點,已得到更加清晰的瞭解.

  這篇文章能起到拋磚引玉的做用,足矣

相關文章
相關標籤/搜索