手撕 MySQL 事务,发生了什么?
??點擊上方?好好學java?,選擇?星標?公眾號
重磅資訊、干貨,第一時間送達 今日推薦:2 個月的面試親身經歷告訴大家,如何進入大廠?作者:flyman 鏈接:https://segmentfault.com/a/1190000022216920什么是事務?
數據庫事務( transaction)是訪問并可能操作各種數據項的一個數據庫操作序列,這些操作要么全部執行,要么全部不執行,是一個不可分割的工作單位。事務由開始和結束之間執行的全部數據庫操作組成。
這是百科的定義,也是我們在面試的時候最常回答的關鍵字。
可以這么說:事務是數據庫執行過程的一個邏輯單位,由一個有限的數據庫操作序列組成。
舉例來說,當我們購物下訂單時,有這么兩個操作(當然不止這倆):付款,減庫存。這兩個操作序列就是一個事務,他不能拆分執行,必須同時成功和失敗。
當我們面試時最常遇到的問題是什么?
或者不那么世俗的說——畢竟我們學習并不全是為了面試嘛,啊呸!不面試誰看那么多,腦子內存本來就不大。
小伙手撕MySQL事務,發生了什么?事務的4大特性
A (Atomicity) 原子性:事務的操作序列不可再拆分:這也是都成功都失敗的意思。
C (Consistent) 一致性:事務的執行使數據從一個狀態轉換為另一個狀態,但是對于整個數據的完整性保持穩定。
I(Isolation)隔離性:隔離性是當多個用戶并發訪問數據庫時,比如操作同一張表時,數據庫為每一個用戶開啟的事務,不能被其他事務的操作所干擾,多個并發事務之間要相互隔離。
D(Durable)持久性:久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。
關于ACID搶眼回答:
數據庫的每一個操作其實是一條日志。
原子性,在 InnoDB 里面是通過 undo log 來實現的,它記錄了數據修改之前的值(邏輯日志),一旦發生異常,就可以用 undo log 來實現回滾操作。
持久性怎么實現呢?數據庫崩潰恢復(crash-safe)是通過什么實現的?
是通過 redo log 和 double write 雙寫緩沖來實現的,我們操作數據的時候,會先寫到內存的buffer-pool中,同時記錄 redo log,如果在刷盤之前出現異常,在重啟后就可以讀取 redo log 的內容,寫入到磁盤,保證數據的持久性。
當然,恢復成功的前提是數據頁本身沒有被破壞,是完整的,如果數據頁本身損壞了,這個可以通過雙寫緩沖 (double write)保證。
說了這么多事務,你甚至可能在實際編程中并沒有手動操作過事務,那么如何手動開啟和關閉事務呢?
小伙手撕MySQL事務,發生了什么?小伙手撕MySQL事務,發生了什么?Mysql數據庫默認是開啟自動提交事務的,也就是說當你執行update/delete/insert操作時數據庫引擎會自動開啟一個事務,并且在操作完成后自動提交事務。
事務并發產生的問題
臟讀,讀到了其他事務還沒提交的數據
不可重復讀,讀到了其他事務提交之后的數據,數據變化了(update/delete)
幻讀,讀到了其他事務提交之后的數據,只有數據增加了行才叫幻讀(insert)
你可能會說,事務不是有隔離性嗎,為什么還有這么多問題?好了繼續往下看
事務隔離級別
Read Uncommited 未提交讀,顧名思義,可以讀到其他事務沒有提交的數據,會產生臟讀
事務操作時并不隔離其他的事務,比如以下操作:
小伙手撕MySQL事務,發生了什么?請點擊輸入圖片描述
而如果此時事務1 回滾了事務,事務2就讀到了臟數據;
這種隔離機制在數據庫實際使用中,顯然不合適,但有助于理解其他事務。
Read Commited 已提交讀,只能讀取其他事務已經提交的數據,不能讀取到其他事務未提交的數據,它解決了臟讀的問題, 但是會出現不可重復讀的問題。
事務只能讀取其他事務提交之后的數據,我們來模擬以下這個過程會出現的問題:
小伙手撕MySQL事務,發生了什么?可以看到,這種隔離級別會導致數據在一個事務兩次讀取的數據不一致,也就是不可重復讀;
Repeatable Read 可重復讀,它解決了不可重復讀的問題, 也就是在同一個事務里面多次讀取同樣的數據結果是一樣的,但是在這個級別下,沒有解決幻讀的問題。
這也是**最常用的事務隔離機制,他可以保證在一個事務里讀取的數據是一樣的,也就是數據的可重復讀。**你可能會問,數據庫是如何實現可重復讀的?這個問題會在之后解答,先來看看什么是幻讀。
一個事務前后兩次讀取數據數據不一致,是由于其他事務插入數據造成的,這種情況我們把它叫做幻讀。
比如:
小伙手撕MySQL事務,發生了什么?第二次查詢我們發現多了一條數據,這就叫幻讀。需要記住的是只有其他事務插入導致的數據不一致才叫幻讀。
Serializable(串行化),最后一個是串行的隔離級別,也是最嚴格的隔離級別,它要求事務必須排隊執行,解決了幻讀。但這大大影響了效率。也比較少用。
所以你會說,既然我們比較常用的是RR(可重復讀)的隔離級別會有幻讀的情況,那為什么還經常使用它?其實在MySQL的InnoDB引擎使用RR的隔離級別但配合鎖的機制已經避免了幻讀情況。
我們先來看一下MySQL是如何實現數據的可重復的。
有兩種方案。
LBCC (Lock Based Concurrency Control),意思是讀取數據的時候鎖定它,不允許其他事務操作,數據自然也不會變化。這種方案我們叫做基于鎖的并發控制簡稱LBCC。一個事務讀取的時候不允許其他時候修改,那就意味著不支持并發的讀寫操作,而我們的大多數應用都是讀多寫少的,這樣會極大地影響操作數據的效率。
MVCC (Multi Version Concurrency Control), 意思是多版本并發控制,另一種解決方案是如果要讓一個事務前后兩次讀取的數據保持一致, 那么我們可以在修改數據的時候給它建立一個備份或者叫快照,后面再來的事務讀取這個快照就行了。
MVCC的核心思想是:可以查到在當前事務開始之前已經存在的數據,即使它在后面被其他事務修改或者刪除了。而當前事務之后新增的數據,當前事務是查不到的。
那么問題來了,如何保證當前事務數據的一致性呢?也就是說怎么保證數據被其他事務修改和刪除或者新增了數據,而當前事務并不受影響呢?
讀取數據事務開始的時候,MySQL為事務創建了快照,也就是在事務內查詢的數據都是快照版本,這樣就可以保證數據的一致性。
那么快照又是如何實現的呢?
InnoDB 為每行記錄都實現了兩個隱藏字段:
DB_TRX_ID,6 字節:插入或更新行的最后一個事務的事務ID,事務編號是自動遞增的(我們把它理解為創建版本號,在數據新增或者修改為新數據的時候,記錄當前事務ID)
DB_ROLL_PTR,7 字節:回滾指針(我們把它理解為刪除版本號,數據被刪除或記錄為舊數據的時候,記錄當前事務 ID)。
我們把這兩個事務ID理解為版本號。
從插入數據開始,我們來看一下MySQL如何用這兩個版本號來隔離事務。
小伙手撕MySQL事務,發生了什么?此時又有一個事務進來,增加了一條數據并提交結束。
小伙手撕MySQL事務,發生了什么?MVCC 的查找規則1:只能查找創建時間小于等于當前事務 ID 的數據
小伙手撕MySQL事務,發生了什么?再次回到事務2查詢
小伙手撕MySQL事務,發生了什么?MVCC 的查找規則2: 能查找刪除時間大于當前事務id的數據,也就是在事務之后刪除的數據在當前事務依然能查得到。
事務5,嘗試修改數據
小伙手撕MySQL事務,發生了什么?此時回到事務2再次查詢數據
小伙手撕MySQL事務,發生了什么?按照查找規則:只能查找創建時間小于等于當前事務 ID 的數據,和刪除時間大于當前事 務 ID 的行(或未刪除)。
解釋:id為1的數據刪除版本大于當前事務2,創建版本小于事務2,依然可以查到,
id為2的數據創建版本為1的數據,小于事務2,刪除版本大于事務2,可以查到
id為3的數據創建版本為3大于事務2,查不到;
id為2的數據創建版本為5的數據,大于事務2,依然查不到;
所以,在事務2內,無論外部發生了什么翻天覆地的變化,事務2自始自終查到的都是自己的快照數據,保證了數據的一致性,一定要理解MVCC的核心思想。
之后會更新結合鎖機制,InnoDB是如何在RR級別解決幻讀的。
最后,再附上我歷時三個月總結的?Java 面試 + Java 后端技術學習指南,這是本人這幾年及春招的總結,目前,已經拿到了騰訊等大廠offer,拿去不謝,github 地址:https://github.com/OUYANGSIHAI/JavaInterview
這么辛苦總結,給個star好不好。?點擊閱讀原文,直達
總結
以上是生活随笔為你收集整理的手撕 MySQL 事务,发生了什么?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AssertionError: Path
- 下一篇: 用 Java 写一个植物大战僵尸简易版!