聊一聊 MySQL 中的事務及其實現原理
說到資料庫,那就一定會聊到事務,事務也是面試中常問的問題,我們先來一個面試場景:
面試官:"事務的四大特性是什麼?"
我:"ACID,即原子性(Atomicity)、隔離性(Isolation)、永續性(Durability)、一致性(Consistency)!"
面試官:"在 MySQL 資料庫的 InnoDB 引擎是怎麼實現這四大特性的?"
我:"這個...這個....,還真沒有了解過哎"
面試官:"那我們就先這個吧,先回去吧,我們會通知你的~"
這可能是比較常見的面試場景了,你也許回答到了事務的四大特性,但是不一定知道他的實現原理。今天我們就來一起打卡事務的四大特性和實現原理,對於原理的實現,這篇文章只是粗略的介紹一下,更多的細節可以關注我後續的文章。
資料庫的事務有四大特性:原子性、隔離性、永久性、一致性,下面將介紹這四大特性的定義和在 InnoDB 引擎中是怎麼實現的。
原子性
定義
一次操作是不可分割的,要麼全部成功,要麼全部失敗。比如我們的轉賬操作,不允許出款方成功,收款方失敗這種情況,要麼都成功,要麼多失敗,不可能出現中間狀態。
實現
InnoDB 引擎使用 undo log(歸滾日誌)來保證原子性操作,你對資料庫的每一條資料的改動(INSERT、DELETE、UPDATE)都會被記錄到 undo log 中,比如以下這些操作:
- 你插入一條記錄時,至少要把這條記錄的主鍵值記下來,之後回滾的時候只需要把這個主鍵值對應的記錄刪掉就好了。
- 你刪除了一條記錄,至少要把這條記錄中的內容都記下來,這樣之後回滾時再把由這些內容組成的記錄插入到表中就好了。
- 你修改了一條記錄,至少要把修改這條記錄前的舊值都記錄下來,這樣之後回滾時再把這條記錄更新為舊值就好了。
當事務執行失敗或者呼叫了 rollback 方法時,就會觸發回滾事件,利用 undo log 中記錄將資料回滾到修改之前的樣子。
更多關於 undo log 的資訊,後面再單獨開一篇文章打卡。
隔離性
定義
多個事務併發執行的時候,事務內部的操作與其他事務是隔離的,併發執行的各個事務之間不能互相干擾。
實現
隔離性可能會引入髒讀(dirty read)、不可重複讀(non-repeatable read)、幻讀(phantom read)等問題,為了解決這些問題就引入了“隔離級別”的概念。
SQL 標準的事務隔離級別包括:讀未提交(read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和序列化(serializable):
- 讀未提交:一個事務還沒提交時,它做的變更就能被別的事務看到。
- 讀提交:一個事務提交之後,它做的變更才會被其他事務看到。
- 可重複讀: 一個事務執行過程中看到的資料,總是跟這個事務在啟動時看到的資料是一致的。當然在可重複讀隔離級別下,未提交變更對其他事務也是不可見的。
- 序列化: 顧名思義是對於同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。
SQL標準中規定,針對不同的隔離級別,併發事務可以發生不同嚴重程度的問題,具體情況如下:
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
讀未提交 |
可能 | 可能 | 可能 |
讀提交 |
不可能 | 可能 | 可能 |
可重複讀 |
不可能 | 不可能 | 可能 |
序列化 |
不可能 | 不可能 | 不可能 |
上面就是幾種隔離級別可能出現的併發問題,但是有必要說一下,你隔離得越嚴實,效率就會越低。
InnoDB 引擎是如何保證隔離性的?利用鎖和 MVCC 機制。這裡簡單的介紹一下 MVCC 機制,也叫多版本併發控制,在使用 READ COMMITTD、REPEATABLE READ 這兩種隔離級別的事務下,每條記錄在更新的時候都會同時記錄一條回滾操作,就會形成一個版本鏈,在執行普通的 SELECT 操作時訪問記錄的版本鏈的過程,這樣子可以使不同事務的讀-寫、寫-讀操作併發執行,從而提升系統性能。
永續性
定義
事務一旦提交,它對資料庫的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
實現
要保證永續性很簡單,就是每次事務提交的時候,都將資料刷磁碟上,這樣一定保證了安全性,但是要知道如果每次事務提交都將資料寫入到磁碟的話,頻繁的 IO 操作,成本太高,資料庫的效能極低,所以這種方式不可取。
InnoDB 引擎是怎麼解決的?InnoDB 引擎引入了一箇中間層來解決這個永續性的問題,我們把這個叫做 redo log(歸檔日子)。
為什麼要引入 redo log?redo log 可以保證持久化又可以保證資料庫的效能,相比於直接刷盤,redo log 有以下兩個優勢:
- redo log體積小,畢竟只記錄了哪一頁修改了啥,因此體積小,刷盤快。
- redo log是一直往末尾進行追加,屬於順序IO。效率顯然比隨機IO來的快。
InnoDB 引擎是怎麼做的?當有一條記錄需要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log 裡面,並更新記憶體,這個時候更新就算完成了。當資料庫宕機重啟的時候,會將 redo log 中的內容恢復到資料庫中,再根據 undo log和 binlog 內容決定回滾資料還是提交資料。
更多 redo log,後面我打算單獨寫一篇文章。
一致性
定義
一致性簡單一點說就是資料執行前後都要處於一種合法的狀態,比如身份證號不能重複,性別只能是男或者女,高考的分數只能在0~750之間,紅綠燈只有3種顏色,房價不能為負的等等, 只有符合這些約束的資料才是有效的,比如有個小孩兒跟你說他高考考了1000分,你一聽就知道他胡扯呢。資料庫世界只是現實世界的一個對映,現實世界中存在的約束當然也要在資料庫世界中有所體現。如果資料庫中的資料全部符合現實世界中的約束(all defined rules),我們說這些資料就是一致的,或者說符合一致性的。
實現
要保證資料庫的資料一致性,要在以下兩個方面做努力:
- 利用資料庫的一些特性來保證部分一致性需求:比如宣告某個列為
NOT NULL
來拒絕NULL
值得插入等。 - 絕大部分還是需要我們程式設計師在編寫業務程式碼得時候來保證。
以上就是我今天要分享的內容,希望這篇文章對你的學習或者工作有所幫助,感謝您的閱讀,如果您覺得文章不錯歡迎點贊+轉發,感謝。
最後
目前網際網路上很多大佬都有 MySQL 相關文章,如有雷同,請多多包涵了。原創不易,碼字不易,還希望大家多多支援。若文中有所錯誤之處,還望提出,謝謝。
歡迎掃碼關注微信公眾號:「平頭哥的技術博文」,和平頭哥一起學習,一起進步。