在Mysql中,事務是如何實現的呢?


  hello大家好,我是一個愛看底層的小碼,對於每一個學習mysql數據庫的同學來說,事務都是一個繞不開的話題,簡單的說來事務是指訪問並可能更新數據庫中各項數據項的一個程序執行單元。事務的四個特征無非就是老生常談的原子性,一致性,隔離性和持久性。可是如果真的去深究事務的實現原理,你們真的理解嗎?

  在我看來,一個正常的事務要達到怎么樣的效果呢?或者說哪些是它最重要的點呢?無非是可靠和並發處理

  詳細的說,可靠就是指數據庫在執行crud操作(主要是c和u)時拋出異常或者數據庫crash(崩潰)時需要保障數據庫操作前后都是一致的。所以必須要知道在修改前后數據庫所處的狀態,所以這也是undo/redo log存在的意義(關於回退日志和重做日志的詳細說明我可以再寫一篇文章~~)。

  並發處理從字面意思就可以理解,就是當多個並發的請求過來時,假如其中存在一個請求是對數據進行修改操作,那么這就會對數據造成影響,為了避免讀取到臟數據(這也是對數據庫進行並發操作時的一種數據不一致的情況),這時候就需要對事務之間進行隔離,實現這個就得用Mysql的隔離級別。這其中牽扯到的技術其實有三個,分別是日志文件,mysql鎖技術和MVCC。下面我就介紹一下MySQL鎖技術和MVCC基礎,然后講講MySQL事務的實現原理和MySQL時如何保證事務的原子性和隔離性的。(至於日志文件,我想單獨寫一篇文章和bin log放在一起講)。

  1.MySQL鎖技術:當MySQL數據庫接收到多個用戶的請求時,假使其中一種請求是有修改請求時,就需要有一種措施來實現並發控制,要不非常容易出現數據不一致的情況——讀寫鎖。想要解決這個問題,就需要將兩種鎖的組合來將讀寫請求分發控制。這兩種鎖就叫做讀寫鎖,其中讀鎖又叫共享鎖:讀鎖是可以共享的,也就是多個讀請求可以共享一把鎖讀數據,不會造成請求的阻塞。寫鎖又叫做排他鎖,寫鎖會排斥其他所有想要獲取鎖的請求,並將這些請求阻塞,知道寫入完成后釋放鎖。當這兩種鎖組合起來時,兼容性如下面所示:

  通過讀寫鎖的這個機制,就可以非常完美的實現事務的隔離,這個下面再說。

  2.MVCC基礎:MVCC(MultiVersion Concurrency Control)叫做多版本並發控制。在小編這個被病毒封在家的冬天,在《高性能MySQL》這本書上看到過一段對於MVCC很詳細的解釋:“InnoDB(MySQL數據庫的引擎之一)的MVCC,是通過在每行記錄的后面保存兩個隱藏的列來實現的,其中一個列保存行的創建時間,一個列保存了行的過期時間,當然存儲的並不是實際的時間值,而是系統的版本號。”簡單說來實現是想就是通過數據的多版本來做到讀寫分離的操作,從而就可以做到不加鎖讀進而做到讀寫並行。而MySQL在mysql實現依賴的就是undo log和read view。其中undo log中有記錄某行數據的多個版本數據,read view可以用來判斷當前版本數據的可見性,關於讀寫鎖在提交讀級別下的應用如下圖所示:

 

   說了這么多,其實都是為了最后講出事務是如何實現的。上面講的鎖技術和MVCC機制其實都是事務實現的基礎,其中事務的原子性通過undo log‘來實現,事務的持久性是通過redo log來實現,事務的隔離性是通過讀寫鎖和MVCC實現,而事務的一致性就是原子性持久性和隔離性一起實現的。所以總結起來,事務就是為了保障可靠,一致。那么原子性,持久性,隔離性和隔離性具體是如何實現的呢?

  首先就是原子性,原子性的定義就不和大家說了,簡單理解就是要不全部成功,要不全部失敗。不能部分執行,那MySQL是怎么實現的呢?通過undo log將發生錯誤異常或者顯示時,執行rollback語句把數據還原到原先的模樣。接下來看一下undo log在實現事務原子性時怎么發揮作用的

  undo log 的生成:假設有兩個表 bank和finance,表中原始數據如下面圖所示:

 

  若對表進行更新操作時,比如從賬戶里取出500到理財賬戶上,這時就會生成如下的undo log。

  這樣每一次數據的變化都會引起undo log的的產生,這樣每一次數據的變化(cud)都會伴隨一條undo log的生成,並且在日志生成之后會先行持久化到磁盤之上,比數據持久化早一些。而回滾操作,就是靠着undo log日志中記載的操作變化進行逆向操作,添加數據變為刪除,刪除數據變為添加,修改數據變為修改成日志上記載的數據。那么大家知道為什么要先寫日志后寫數據庫?--- 稍后做解釋

  根據undo log 進行回滾:為了做到同時成功或者失敗,當系統發生錯誤或者執行rollback操作時需要根據undo log 進行回滾。也就是要還原到原來的狀態,undo log記錄了數據被修改前的信息以及新增和被刪除的數據信息,根據undo log生成回滾語句,比如如果在回滾日志里有新增數據記錄,則生成刪除該條的語句,其他類型的以此類推。

  持久性的實現:事務一旦提交之后,所做的修改將永久的保存在數據庫里面,即便數據庫出現crash的現象也不會丟失相應的數據。MySQL數據庫的表數據都是存在磁盤上,因此想要存取數據都要經歷磁盤IO,但是這樣是非常消耗性能的。所以為了提高性能,InnoDB提供了緩沖池(buffer pool),緩沖池里面包含了磁盤數據頁的映射,這樣就可以當作緩存來使用。當用戶進行讀數據操作時,MySQL會先從緩沖池里讀取,如果沒有該數據的話在從磁盤讀取數據放入緩沖池;當用戶進行寫數據操作時,數據同樣會先寫入緩沖池當中,之后緩沖池里面的數據才會定期同步到磁盤當中去。但是這樣雖然看着方面,也提高了性能,但是加入出現電腦死機或者斷電的輕緩,就有可能丟失相應數據,而這時就是redo log出現的時候了

   或許看完圖片很多人都疑惑,為什么redo log也需要存儲並且同樣涉及到磁盤IO,為什么還要用它呢?這是因為首先redo log的存儲是順序存儲,但緩存同步是隨機操作。同樣緩存同步是以數據頁為單位的,每次傳輸的數據大小要比redo log大。

  隔離性的實現:如果算實現難易程度的話,隔離性應該是最難實現的一個,不知道大家還記不記得SQL的四個隔離級別,每一個級別都規定了一個事務中出現修改時,哪些是事務之間可見的,哪些是事務之間不可見的。級別越低的隔離級別可以執行更高的並發,但是同時實現的復雜度和開銷也就越大,MySQL的四個隔離級別從低到高分開是:readuncommited(未提交讀),readcommited(提交讀),repeatableread(可重復讀),serializable(可重復讀)。隔離性也因為這四個級別和原子性及持久性有很大區別,原子性和一致性總的來說是為了給數據提供可靠的保障,比如死機之后數據如何恢復,已經數據出錯之后進行數據回滾等等,而隔離性則是為了管理當多個並發讀寫請求訪問時,合理的安排好每個請求的順序,這種順序包括了串行和並行兩種,所以隔離性可以看作是關於數據性能和可靠之間的沒有硝煙的戰爭,所以很容易就能得到當數據可靠性高時,並發性能自然也就低了比如serializable,可靠性低的,並發性能也就高了,比如readunconmmited。所以只有真正掌握了四個隔離級別的優缺點,才能夠正確理解隔離性。

  readunconmmited:在未提交讀的隔離級別下,事務中的修改即使還沒提交,對其他事務也是可見的,這樣就很容易造成讀臟數據的現象。因為讀請求是不會添加任何鎖的,所以假如寫操作在讀的過程中修改數據,就會出現數據不一致的情況,但是這種隔離級別讀操作不能排斥寫請求,也就提高了並發處理的性能,從而做到讀寫並行。

  readcommited:在提交讀的隔離級別下,一個事務的修改在他提交之前對其他事務都是不可兼得。其他事務只可以讀到已提交的修改數據變化,在很多場景下這種邏輯是可以接受的。InnoDB在readcommited中使用了MVCC機制,或者換句話就是讀寫分離機制。但是在這個級別下會產生不可重復讀(在一個事務中多次讀取的結果不一樣)的問題,為什么會產生這種問題呢?這個和MVCC的機制有關系,在這個隔離級別下每次進行select操作時都會在其生成一個新的版本號,這樣每次select讀到的不是一個副本而是不同的副本。這樣在每次select之間假使有其他事物更新我們讀取的數據並提交后,就出現了不可重復讀的現象。

  repeatableread(MySQL默認隔離級別):在一個事務中多次讀取的數據結果是一樣的,這種級別可以有效的避免臟讀及不可重復讀等查詢問題,MySQL有兩種機制都可以達到這種隔離級別分別是用讀寫鎖:

  這樣為什么能夠實現重復讀呢?是因為只要沒釋放讀鎖,在次讀的時候都可以讀到第一次讀到的數據,這樣雖然實現簡單,但是卻無法實現讀寫並行。另外一種是MVCC實現:

  這樣為什么可以重復讀呢?是因為多次讀取到的數據只生成了一個版本,這樣自然也就可以讀到相同的數據,這樣可以實現讀寫並行,但是實現起來復雜度極高。

  serializable:這種序列化讀級別下理解起來最簡單實現起來也最簡單,但是除了不會造成數據不一致的問題,就沒有其他任何的優點了。

  一致性的實現:對於一個數據庫來說,總是從一種狀態轉移到另外一種狀態:比如我要從一個銀行卡轉出400塊錢到另外一個理財的賬戶中

start transaction;
select balance from bank where name="wyf";
//生成重做日志balance=600
update bank set balance=balance-400;
//生成重做日志amount=400
update finance set amount=amount+400;
commit;

  假如執行完 update bank set balance = balance - 400;之后數據庫發生了異常了,銀行肯定不允許客戶錢平白無故的減少,就可以回滾到最初的狀態。又或者事務提交之后,緩沖池還沒同步到磁盤的時候死機了了,這也是不能接受的,應該在重啟的時候恢復並持久化。假如有並發事務請求的時候也應該做好事務之間的可見性問題,避免造成臟讀,不可重復讀,幻讀等。在涉及並發的情況下往往在性能和一致性之間做平衡,做一定的取舍,所以從這個角度看,隔離性也是對一致性的一種逆向削弱。

 最后,總的說來實現事務采取了哪些技術以及思想?原子性使用 undo log ,從而達到回滾;持久性:用 redo log,從而達到故障后恢復;隔離性使用鎖以及MVCC,運用的優化思想有讀寫分離,讀讀並行,讀寫並行;一致性:通過回滾,以及恢復,和在並發環境下的隔離做到一致性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM