Mysql事務實現原理
事務追求的目標:
- 可靠性
- 併發處理
可靠性
資料庫要保證當insert或update操作時丟擲異常,或者資料庫crash時需要保障資料庫資料的操作前後一致;想要保證這一點,我們需要知道我在修改前和修改後的狀態,於是Mysql引入了 undo log 和 redo log
併發處理
當有多個併發請求過來時,且其中有一個請求是對資料修改的操作,未來避免讀到髒資料,所以需要對事務之間的讀寫進行隔離,
實現Mysql事務功能的三個技術
- 日誌檔案(redo log 和 undo log)
- mysql 鎖技術
- MVCC 技術
redo log 和 undo log 介紹
1. redo log
redo log叫做重做日誌 ,用來實現事務的永續性 。該日誌檔案由兩部分組成:重做日誌緩衝和重做日誌檔案,重做日誌緩衝在記憶體中,後者在磁碟中;在事務的操作過程中,會將重做日誌寫入到緩衝中;事務提交後,會立刻將重做日誌緩衝中的資料寫入磁碟(即重做日誌檔案中)。
redo log 的作用
mysql 為了提升效能,並不是每次修改都實時寫入到磁碟中,而是先將資料寫入到Buffer Pool(緩衝池)中,快取起來,然後後臺執行緒在事務提交後非同步地去將緩衝池中的資料同步到磁碟中;以減少磁碟IO,加速讀寫。
可是這樣就引入了一個問題:如果在緩衝向磁碟同步過程中宕機了怎麼辦,這樣就會造成資料丟失,造成資料庫資料不可信的問題。
所以就引入redo log來記錄事務的修改資訊(記錄在快取中),並在事務提交時立刻寫入磁碟。系統宕機重啟後可以通過讀取redo log來恢復最新資料(如果還未提交就宕機,相當於事務未提交,資料丟失就是可接受的)
redo log總結
redo log 就是用來恢復資料的,用於保障已提交書屋的持久化特性的
2. undo log
undo log 叫做回滾日誌,用於記錄資料被修改前的資訊。和redo log說記錄的操作相反,undo log主要記錄的是資料的邏輯變化,未來發生錯誤時會滾到之前的資料。需要將之前的資料記錄下來,在發生錯誤時會滾。
事務中的每次寫入或修改資料之前,都會把原始資料備份到 undo log 中
undo log 的作用
undo log 用於回滾資料,用於保障未提交事務的原子性
3. Mysql鎖技術以及MVCC技術
當都是讀請求是沒必要做任何操作,但一旦有修改請求時必須有一種措施來進行併發控制,避免出現髒讀,或者(不可重複讀、幻讀)
讀寫鎖
解決上述問題很簡單,只要使用讀寫鎖的組合來對讀寫請求進行控制即可
共享鎖(shared lock),又叫做“讀鎖”
讀鎖可以共享,多個讀請求共享一把讀鎖讀資料,不會造成堵塞
排他鎖(exclusive lock),又叫做“寫鎖”
寫鎖會排斥其他所有獲取鎖的請求,一直阻塞,知道寫入完成釋放鎖
總結:
通過讀寫鎖,可以做到讀讀可以並行,但是不能做到寫讀,寫寫並行。事務的隔離性就是根據讀寫鎖來實現的!!
4. MVCC 技術
MVCC(MultiVersion Concurrency Control) 多版本控制
InnoDB的 MVCC ,是通過在每行記錄的後面儲存兩個隱藏的列來實現的。這兩個列, 一個儲存了行的建立時間,一個儲存了行的過期時間, 當然儲存的並不是實際的時間值,而是系統版本號。
MVCC在mysql中的實現依賴的是undo log與read view
- undo log 用於記錄某行資料的多個版本的資料(改一下一個版本,再改一下有一個版本)
- read view 用於判斷當前版本資料的可見性
5. 事務的實現
前面講的重做日誌,回滾日誌以及鎖技術就是實現事務的基礎。
- 事務的A原子性是通過 undo log 來實現的
- 事務的C永續性性是通過 redo log 來實現的
- 事務的I隔離性是通過 (讀寫鎖+MVCC)來實現的
- 而事務的 **D一致性 **是通過原子性,永續性,隔離性來實現的!!!
(1)原子性的實現
原子性可以概括為就是要實現要麼全部失敗,要麼全部成功。
資料庫的實現方式就是使用回滾操作,即利用undo log來進行回滾。
1.每條資料變更(insert/update/delete)操作都伴隨一條undo log的生成,並且回滾日誌必須先於資料持久化到磁碟上
2.所謂的回滾就是根據回滾日誌做逆向操作,比如delete的逆向操作為insert,insert的逆向操作為delete,update的逆向為update等。
(2)永續性的實現
事務一旦提交,其所作做的修改會永久儲存到資料庫中,此時即使系統崩潰修改的資料也不會丟失。
資料庫就是利用redo log 來實現出現宕機時的資料持久化的
既然redo log也需要儲存,也涉及磁碟IO為啥還用它?
(1)redo log 的儲存是順序儲存,而快取同步是隨機操作。
(2)快取同步是以資料頁為單位的,每次傳輸的資料大小大於redo log。
(3)隔離性的實現 —— 靠讀寫鎖和MCC實現
隔離性是事務ACID特性裡最複雜的一個。在SQL標準裡定義了四種隔離級別,每一種級別都規定一個事務中的修改,哪些是事務之間可見的,哪些是不可見的。
Mysql 隔離級別有以下四種(級別由低到高):
- READ UNCOMMITED (未提交讀)
- READ COMMITED (提交讀)
- REPEATABLE READ (可重複讀)
- SERIALIZABLE (可重複讀)
隔離性是要管理多個併發讀寫請求的訪問順序。 這種順序包括序列或者是並行
說明一點,寫請求不僅僅是指insert操作,又包括update操作。
READ UNCOMMITED
事務中的修改即使還沒提交,對其他事務是可見
讀操作不會加任何鎖,所以寫操作加寫鎖,在讀的過程中修改資料,會造成髒讀。好處是可以提升併發處理效能,能做到讀寫並行。
原因: 讀未加讀鎖,寫鎖只能做到阻塞讀鎖和寫鎖,導致未加鎖的事務可以訪問到事務還未提交的修改後的資料
READ COMMITED
一個事務的修改在他提交之前的所有修改,對其他事務都不可見
InnoDB在 READ COMMITTED時,寫使用排它鎖, 讀取資料不加鎖而是使用了MVCC機制。或者換句話說他採用了讀寫分離機制。
該級別會產生不可重讀以及幻讀問題。
不可重複讀: 同一行資料,前後兩次查詢不一致
幻讀: 前後兩次查詢的結果集,數量不一致
READ COMMITTED 級別下的MVCC機制有關係,在該隔離級別下每次 select的時候新生成一個版本號,所以每次select的時候讀的不是一個副本而是不同的副本。
讀取的是最新操作的寫事務在undo log 中的留下的快照
REPEATABLE READ
在一個事務內的多次讀取的結果是一樣的;避免髒讀、不可重複讀等查詢問題
MVCC的實現
讀不加鎖,但是讀取的時在undo log 中的同一份快照(永遠是這個快照);寫加寫鎖。
(4)一致性的實現
下面舉個例子:zhangsan 從銀行卡轉400到理財賬戶
start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日誌redo log balance=600 和 回滾 undo log
update bank set balance = balance - 400;
// 生成 重做日誌redo log amount=400 和 回滾 undo log
update finance set amount = amount + 400;
commit;
1.假如執行完 update bank set balance = balance - 400;
之發生異常了,銀行卡的錢也不能平白無辜的減少,而是回滾到最初狀態。
2.又或者事務提交之後,緩衝池還沒同步到磁碟的時候宕機了,這也是不能接受的,應該在重啟的時候恢復並持久化。
3.假如有併發事務請求的時候也應該做好事務之間的可見性問題,避免造成髒讀,不可重複讀,幻讀等。在涉及併發的情況下往往在效能和一致性之間做平衡,做一定的取捨,所以隔離性也是對一致性的一種破壞。