事務的本質核心就是鎖和併發。 事務只是讓傳統意義上難以理解的事情用一種更容易讓你們理解的方式來表述的方法。事務比傳統意義的鎖和併發更容易讓你們理解。數據庫
(圖1)編程
當鎖定Bob帳戶和鎖定Smith帳戶以後,只有一個請求(一個線程)能夠進入到裏面去操做帳戶。線程2和線程3發現有鎖,只能在外面等待。上面的三、四、5均可以用線程1來完成。只有線程1能夠看到他們的中間狀態(三、四、5)。線程2和線程3都看不到中間狀態。在一個事務單元結束以前,其餘的線程只能等待在鎖外的。因此線程2和線程3只能看到要麼Bob帳戶中有錢,要麼Smith帳戶中有錢。併發
假設在從Bob帳戶中減小100塊的步驟結束後,就解除鎖的狀態,當線程2和線程3訪問Bob的帳戶和Smith的帳戶,就會發現Bob和Smith的帳戶都爲0,就會出現數據不一致的狀況(圖2)。app
(圖2)分佈式
因此事務要保證的事情就是一致性,要麼Bom有100塊,要麼Smith有100塊(一致性)。Bob有100塊或者Smith有100塊的時候只要被線程2或線程3訪問到,就必須是持久的,不能在回退到以前的狀態(持久性)。這個過程就是一個事務單元。這個過程就能夠保證A(一致性)I(原子性)D(持久性)。性能
(圖3)spa
整個系統內會有三個事務單元,只有一個事務單元完成之後纔可讓其餘的事務單元進入到裏面拿到這把鎖。每個事務單元在運行的過程當中的時候,看到的數據不會出現中間狀態(Bob給Smith100塊,Bob減小了錢,而Smith沒有加上)。若是以這種方式考慮事務單元的關係,系統沒有辦法作到很清晰。事務單元的關係自己很是複雜。一些前輩就抽象了事務單元之間的關係(4.2章節)。.net
(圖4)線程
把這四種狀態映射到全部的事務單元之間的關係,都是這四種狀態的重複(引自:事務處理)。 日誌
(圖5)
優勢:不須要衝突控制。
缺點:慢速設備。若是一旦一個隊列裏的某一個請求很是慢,由於全部請求都不能並行,會致使系統性能很是低。
(圖6)
假設有3個事務單元。事務單元1鎖住了兩行記錄。事務單元2和3共享了一個事務記錄。只要保證事務單元數據沒有出現衝突,事務之間就不該該出現衝突。(好比:Bob給了Simth100塊,李雷給了韓梅梅100塊)。Bob給了Simth100塊,李雷給了韓梅梅100塊這兩個事務單元徹底沒有衝突。若是在徹底沒有衝突的狀況下,事務單元徹底能夠並行起來。所以
(圖7)
(圖8)
結論:若是事務單元之間有可能發生衝突,就讓它們串行。若是不發生衝突,徹底能夠並行。實現這種方式只要在一個事務單元加鎖就能夠實現。只容許一個線程訪問。這種加鎖的方式,其它事務單元天然而然不會在上一個事務單元尚未結束前就能夠訪問。系統就能夠經過加鎖的方式來實現併發。同時又能夠實現讀讀、寫讀、讀寫、讀讀。在保證數據一致的狀況下,有沒有更好的辦法實現更高的速度呢?
事務單元之間的關係有四種場景:讀寫、寫讀、讀讀、寫寫。若是能夠把它們分離開,就可讓讀徹底並行。讀和讀的場景徹底並行,而寫讀、讀寫、寫寫仍然串行。對於讀多寫少的狀況下,能夠更近一步的提高系統性能。傳統數據庫主流的作法就是寫和讀之間徹底分開(圖9)。
(圖9)
由於、讀和讀的場景徹底並行,而寫讀、讀寫、寫寫仍然串行的狀況下,纔會出現隔離性的不一樣的隔離級別。
(圖10)
把讀的鎖完全的去掉。讀的時候若是不加鎖,只有寫的時候加鎖(讀的時候若是有個新的鎖進來,就讓它進入)。系統的並行讀能夠進一步提高。代價就是第一次讀的時候,多是一條數據。第二次讀的是另外的數據。這個場景下能夠解決讀寫、讀讀的並行,但不能夠解決寫讀、寫寫的並行,寫寫、寫讀、仍然是串行的。由於每次寫的事務單元都是加鎖的。
總結:隔離性(I) 自己就是對於傳統數據庫的一致性的一種破壞。最強的一致性必定是讀寫放入隊列裏串行的。
當加了寫的操做的時候,系統徹底能夠併發讀。如今主流的數據庫都在使用這種方式。這種方式能夠作到寫讀不衝突、讀讀不衝突、讀寫不衝突。惟一衝突的就是寫和寫。這種方式併發度更高,實現起來更復雜。這個場景下能夠解決讀寫、讀讀寫讀、的並行,但不能夠寫寫的並行,寫寫仍然是串行的。(圖11)。
(圖11)
(圖12)
當發生寫的時候,數據庫就會記錄一個版本號。當讀的時候也會記錄一個版本號,當讀的版本號大於寫的版本號才能夠。
邏輯時間戳(只是保證前後順序):Oracle :SCN 。Innerdb : Trx_id 。
內存裏面維持一個自增號,每一次寫的時候就把自增號+1。這樣就能夠讀到數據自己的最新值。若是讀的時候在一個事務內讀的話,內存裏自增號也會+1。經過自增的ID號就能夠保證事務自己的前後順序。
(圖13)
當Bob的帳戶裏沒有100塊的時候,事務就須要回滾。須要記錄當前事務以前在作的全部操做的反向操做。這樣的狀況下。若是業務屬性出現問題,就能夠調用回滾的方式,來恢復到以前數據沒有發生到任何更改的狀態。
(圖14)
當一個事務單元作完第四步(從Bob的帳戶中減小100元)第5步(給Smith的帳戶中增長100元)尚未來得及作的時候,整個系統崩潰了。必須須要再恢復回來。在進行數據恢復的時候尚未從故障中徹底恢復回來的時候,系統不可以被外部的其餘應用訪問的。 數據庫系統自己對外暴露一個監聽,爲了防止數據庫出現故障。
(圖15)
相互等待,相互又維持了一個鎖。
一、儘量不死鎖(下降隔離級別:好比說讀不加鎖)。
二、碰撞檢測(終止一邊)(圖16)
(圖16)
三、等鎖超時
仍是這個圖(Bom給Smith100塊的事務單元)。假設Bob帳戶有100塊,Smith帳戶有沒有錢,這樣就能夠把一個事務單元分紅多個步驟:
第一個操做先檢查Bob帳戶有沒有100元,發現Bob有100塊,就會進入第二個操做(將Bob的錢取出100塊)。Bob的錢就爲0元。Smith的錢也是0。接着進入第三個步驟(將Bob的100塊加到Smith的帳戶中去)。Bob的錢就爲0元,Smith的錢爲100元。
任何系統內出現的故障,都有可能致使系統回滾。好比說在第二個版本會出現的問題是:若是Smith帳戶不存在的話,必須到回滾到第一個版本的狀態(Bob有100元,Smith有0元)。
若是在最後提交事務的時候超時了,事務就要回滾到第二步的狀態(Bob有0元,Smith有0元)
前面兩種回滾狀態都須要面對同一個問題,都須要知道回滾到哪裏去。數據庫就會加入回滾段,數據庫把這些回滾段記錄到單獨的地方去(如:日誌表)。在版本2的時候記錄一個回滾端(Bob有100元,Smith有0元)。若是第二個狀態須要回滾,只須要把undo的信息(Bob有100元,Smith有0元)直接回溯到這個帳戶上。
這就是原來事務處理自動化操做的一個逆向操做。
若是在第三個版本中,發現Commit沒有成功,就會先回滾到上一個版本(先把Smith的錢減掉)。接着再回滾到上一個版本(Bob有100元,Smith有0元)。這種操做就是原子性的保證(要麼所有成功,要麼所有失敗)。原子性操做不涉及到一致性相關問題的。原子性的語義只保證一個回滾段,這個回滾段能回滾到以前的版本。
若是有另外的一個事務進入到版本2的時候,把Smith的帳戶進行了修改(假設把Smith的錢加了300塊錢)。同時事務一出現了故障,進行回滾,回將Smth的錢改成0。這種狀況(在一致性不保證的狀況下)就會出現300塊錢丟失的問題。可是從原子性的定義來講,原子性不關心(care)這件事。
原子性只記錄了一個undo日誌回滾到以前的版本而已。
一致性的核心就是Can(happen before):
若是事務之間按照並列的關係來排列整個系統沒有任何一致性問題。
假如兩個事務同時發生的時候,就會有一致性問題(視點3)。:若是事務2出現問題以後進行回滾,兩個事務就會出現衝突的狀況(如:更新丟失、數據不一致問題)。
整個系統問題核心就是如何處理視點3(有讀的請求,也有寫的請求)(事務4)也就是如何處理不一樣事務以前的讀寫並行,就是處理一致性的部分。
一個事務單元要保證一個事務單元所有成功之後纔可見,這就是一致性的保證。若是說一個事務單元所有處理成功之後,下個事務才能進來,整個系統必定是一致性的。
可是這樣作的話系統的併發度明顯耗時。所以系統不的不選擇另一個概念,就是隔離性。
若是全部的事務都是(happy-before)的關係的時候絕對可以保證事務單元的強一致性。
若是利用這種方式進行操做的時候,全部的事務單元理論上說都是串行的(序列化)。序列化讀寫(排他鎖)串行就會很是嚴重的影響系統的性能。單位時間內只有一個事務單元可以進入。
性能差必定是認爲是不可取的。
有沒有更好的方式呢?:讀寫鎖:
一、可重複讀(Repeatable read):
它的核心是讀寫鎖:一個很重要的概念是:讀鎖能不能被寫鎖升級。假如說對一個事務單元加一個讀鎖的狀況下,若是有一個寫寫進來,以前的讀鎖要不要放開,讓這個寫進去。假如讀鎖不打開,不讓寫進去。只能作到讀讀並行。這種狀況只能作到讀讀並行,不能完美的提高系統性能的。
二、讀以提交(Read Committed):
就會出現幻讀和不可重複讀爲何會產生。讀已提交也是一個讀寫鎖,不同的是
讀鎖能夠被寫鎖升級。若是一個新寫鎖進來的時候,就能夠將原來的讀鎖升級爲寫鎖。 就會有一種新的模式:讀寫也能夠並行。這樣不少請求就能夠往前提了。每一次將請求往前提的過程就能夠認爲都是增長的並行的狀況。
在讀已提交的時候爲何會出現不可重複讀。?
讀寫並行的話會出現下面的場景:第一次讀取完成之後,讀到一條數據(版本號爲1),由於第二次寫是並行的,因此第二次寫會更新到這個數據。因而下個事務的讀在進來的時候 就會發現原來事務新的數據已經被更改過了。第一次讀數據和第二次讀的數據以前數據讀的版本是不一樣的。因而就會出現不可重複讀。由於讀鎖能夠升級爲寫鎖,就會出現同一個事務內的兩次讀可能會讀到不一樣版本的問題。
三、讀未提交:只加寫鎖,讀不加鎖。
讀讀並行、讀寫並行、寫讀也能夠並行。
所有的寫是串行的,全部的讀均可以並行。這種方式帶來的問題。
可能讀到寫過程當中的數據。可能會讀到內部沒有提交完成的數據。由於讀沒有加鎖。會讀到中間狀態的數據。
總結:
就是由於有讀寫鎖,而後這些讀寫鎖各類各樣的組合就會出現不一樣的隔離級別。由於有讀寫鎖,讀能不能升級,又被分出來兩個隔離級別:讀以提交,讀未提交。
隔離性小結:
四個不一樣的隔離級別是SQL92定義的標準的隔離性。
除了那四種標準的隔離級別以外,還有一種新的隔離級別出現:快照隔離級別(MVCC)。核心思路:無鎖編程。Copy on Write.
快照讀的核心方法(剛剛都是用鎖來實現的):
事務開始以前的版本讓你先讀到。
每一次的寫都要落到磁盤上,保證數據的持久性。事務完成以後,該事務對數據庫的所作的更改便持久的保存在數據庫之中。
可是,如何保證數據不丟?:RAID特性:若是將數據只寫到1塊A磁盤,若是A磁盤壞了,數據仍然不會丟失呢?就在Copy一份數據到另一個磁盤上。
一、磁盤的物理損壞
二、
事務:多個不一樣的命令組裝到一塊兒的過程。
任何看起來相對簡單的東西,背後其實要考慮的因素,遠遠超過你如今所想象的。
一、業務屬性不匹配,須要回滾
二、系統的DOWN機:計算機就是個打字機。
若是走到第一步,當Lock的時候DOWN機了。
事務原子性操做只有一個標記:commit。若是commit以前全部的請求都要回滾。當commit完成的時候,以後的請求必須正常的提交完成而且必須可見。
數據庫重啓後進入recovery模式。會將提交後的事務單元繼續完成提交。未提交事務單元回滾。在這期間,整個系統是不能被訪問的。只有當全部recovery結束之後,將沒有提交的返回回去。將提交後的繼續完成。在這操做結束之後纔會真正開始提供外部訪問。(recovery過程也相似於原子操做,也必須保證ACID,只是實現方法不同)
若是recovery(恢復)的時候又掛了,在重啓須要繼續recovery。所以在recovery的時候也要記錄日誌,保證數據不會丟失。
一、減小鎖的覆蓋範圍。(Myisam表鎖 -->Innodb行鎖)。將一個大鎖變爲多個小鎖。
二、增長鎖上可並行的線程數。
三、選擇正確的鎖類型:
一、讀寫比例比較高的狀況 MVCC
二、
三、
四、調優
事務單元擴展
死鎖擴展 - U鎖
讀鎖。寫鎖分離
MVCC拾遺
一、分佈式事務的目標
像傳統單機事務同樣的操做方式。可按需無限擴展
二、分佈式事務與單機事務的相同點和不一樣點
三、傳統數據庫的分佈式事務
四、分佈式事務面臨的問題
二、分佈式事務流行的解決方案 (Google Spanner、DRDS/TDDL)
4、參考連接: