跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现
來源 |?阿丸筆記
提到MySQL的事務,我相信對MySQL有了解的同學都能聊上幾句,無論是面試求職,還是日常開發,MySQL的事務都跟我們息息相關。
而事務的ACID(即原子性Atomicity、一致性Consistency、隔離性Isolation、持久性Durability)可以說涵蓋了事務的全部知識點,所以,我們不僅要知道ACID是什么,還要了解ACID背后的實現,只有這樣,無論在日常開發還是面試求職,都能無往而不利。
為了大家更好的閱讀體驗,對ACID的深入分析將分為上下兩篇。
本篇為上篇,主要圍繞ACID中的I,也就是“隔離性”展開,從基本概念,到隔離性的實現,最后以一個實戰案例進行融會貫通。
嗯,看完全部內容你都能理,那跟面試官侃半小時隔離性就沒問題了。
事務隔離性的基本概念
1.1 什么是ACID中的Isolation,隔離性
Isolation,隔離性,也有人稱之為并發控制(concurrency control)。事務的隔離性要求每個事務讀寫的對象對其他事務都是相互隔離的,也就是這個事務提交前,這個事務的修改內容對其他事務都是不可見的。事務的隔離性,主要是解決不同事物之間的相互讀寫影響。
所謂的讀寫影響注意分為三種:
臟讀:讀到了別的事務尚未提交(commit)的變更,別人沒提交,我讀到了。
不可重復讀:別的事務提交了變更,被當前事務讀到了。然后導致本事務多次select的結果不一樣,讀到了別的事務提交的內容。
幻讀:也是讀到了別的事務提交的內容,但是跟上面的不同之處在于,讀到了原本不存在的記錄。
注意,不可重復讀,主要是讀到了別的事務update的內容。而幻讀,是讀到了別的事務insert的內容。
1.2 隔離性的隔離級別
為了解決事務隔離性的問題,數據庫一般會有不同的隔離級別來解決相應的讀寫影響。
讀未提交:一個事務B還沒提交,它的修改就被別的事務A讀到了。
讀已提交:一個事務B提交后,它的修改被其他事務A看到了。
可重復讀:一個事物B提交前和提交后,事務A都無法讀到事務B的變更。
串行化:對同一行記錄,當出現不同事物的讀寫沖突時,是通過串行化的方式解決的,后一個事務必須等前一個事務完成才能執行。
不同隔離級別能夠解決不同的隔離性問題。
需要注意的是,這是標準事務隔離級別的定義。在MySQL的innodb引擎中,在可重復讀級別下,通過mvcc解決了幻讀的問題,具體實現我們后面再講。
同時,需要注意的是,到目前為止,我們說的讀,都是”快照讀”,普通的select。后面我們還會提到“當前讀”,是不一樣的哦。
事務隔離性的實現
要實現事務的隔離性,需要了解兩個方面的內容,一個是鎖,一個是多版本并發控制(MVCC)。
2.1 事務的行鎖
InnoDB中,實現了兩種標準的行級鎖:
共享鎖(S Lock),也叫讀鎖,允許事務讀取一行數據。
排它鎖(X Lock),也叫寫鎖,允許事務刪除或者更新一行數據(注意,這里沒有提到插入哦,插入涉及到幻讀,可以看文章最后的說明)
普通select語句不會有任何鎖,那么如何獲得共享鎖和排它鎖呢?
Select … lock in share mode語句能夠獲得共享鎖
Select … for update(特殊的select,用mysql簡單實現分布式鎖經常用它)、Update、delete語句能夠獲得排它鎖
當一個事務A已經獲得了行r的共享鎖,那么另一個事務B可以立刻獲得行r的共享鎖,因為不會改變r的數值,這種叫做鎖兼容。
如果這時候有事務C希望獲得行r的排它鎖,那么就必須等待事務A和事務B釋放行r的共享鎖之后,才能獲得排它鎖,這種叫做鎖不兼容。
普通的select不會對行上鎖,而select…lock in share mode會上共享鎖,select…for update會上排它鎖。
對于普通的select的讀取方式,稱為”快照讀“,也叫”一致性非鎖定讀“。
對于帶鎖的select讀取,或者update tb set a = a+1(讀取a的當前值),稱為“當前讀”,也叫“一致性鎖定讀”。
如果在update、insert的時候,不能進行select,那么服務的并發訪問性能就太差了。因此,我們日常的查詢,都是“快照讀”,不會上鎖,只有在update\insert\“當前讀”的時候,才會上鎖。而為了解決“快照讀”的并發訪問問題,就引入了MVCC。
2.2 多版本并發控制MVCC
如果說上面的行鎖是一種悲觀鎖,那么MVCC就是一種樂觀鎖的實現方式,而且是一種很常用的樂觀鎖實現方式。
所謂多版本,就是一行記錄在數據庫中存儲了多個版本,每個版本以事務ID作為版本號。InnoDB 里面每個事務有一個唯一的事務 ID,是在事務開始的時候向InnoDB的事務系統申請的,并且按照申請順序嚴格遞增的。假如一行記錄被多個事務更新,那么,就會產生多個版本的記錄。
以某一行數據作為例子:
經過兩次事務的操作,value從22變成了19,同時,保留了三個事務id,15、25、30。
在每個記錄多版本的基礎上,需要利用“一致性視圖”,來做版本的可見性判斷。
這里,我們要區分MySQL里面的兩個”視圖”概念:
一個是view,通過語法create view … 實現,主要創建一個虛擬表,用來執行查詢語句。
一個是InnoDB用來實現mvcc的一致性視圖(consistent read view),純邏輯概念,沒有物理結構,定義了在事務期間,你能看到哪些版本的數據。
我們全文提到的“視圖”都是第二種,主要是支持InnoDB在“讀已提交”和“可重復讀”級別的并發訪問問題。
“讀未提及”級別下,沒有一致性視圖
“讀已提交”級別下,會在?每個SQL開始執行的時候?創建一致性視圖
“可重復讀”級別下,會在?每個事務開始的時候?創建一致性視圖
“串行化”級別下,直接通過加鎖避免并發問題
下面,我們簡單介紹一下創建一致性視圖的邏輯。
以“可重復讀”級別為例。
當一個事務開啟的時候,會向系統申請一個新事務id
此時,可能還有多個正在進行的其他事務沒有提交,因此在瞬時時刻,是有多個活躍的未提交事務id
將這些未提交的事務id組成一個數組,數組里面最小的事務id記錄為低水位,當前系統創建過的事務id的最大值+1記錄為高水位
這個數組array 和 高水位,就組成了“一致性視圖”。
有了一致性視圖后,我們就可以判斷一行數據的多版本可見性了,無論是“讀已提交”還是“可重復讀”級別,可見性判斷規則是一樣的,區別在于創建快照(一致性視圖)的時間。
在當前事務中,讀取其他某一行的記錄,對其中的版本號的可見性判斷有五種情況(建議自己跟著捋一捋,挺重要的):
如果版本號小于“低水位”,說明事務已經提交,那肯定 可見;
如果版本號大于“高水位”,說明這行數據的這個事務id版本是在快照后產生的,那肯定 不可見;
如果版本號在事務數組array中,說明這個事務還沒提交,所以 不可見;
如果版本號不在事務數組array中,且低于高水位,說明這個事務已經提交,所以 可見;
當然,無論什么時候,自己的事務id中的任何變化,都是可見的
可以看看下面這個例子,更容易理解。
系統創建過的事務id:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
事務A啟動,拍個快照
此時未提交的事務id有:7,8,9
一致性視圖:數組array[7,8,9] + 高水位16(15+1)
對于任意一行數據的可見性判斷:
小于7的,可見
大于16的,說明是快照后產生的,不可見
10-15,不在數組array中,說明已經提交了,可見
7,8,9在array中,說明未提交,不可見
兩個重要結論:
InnoDB 利用了“所有數據都有多個版本”的這個特性,實現了“秒級創建快照”的能力。
MVCC的實現,就是根據當前事務的事務id為依據創建“一致性視圖”,利用一致性視圖來判斷數據版本的可見性。
隔離性實戰
下面,我們來兩個實戰案例,將上面的基礎概念與實現融會貫通吧。
1)并發select&update 案例
id=1 的value初始為1。
我們看下,在不同隔離級別,Time5、Time7、Time9事務A查詢到的value 分布為多少。
“讀未提交”:2,2,2
“讀以提交”:1,2,2
“可重復讀”:1,1,2
串行化:1,1,2(注意,這里在事務A提交前,事務B都會阻塞,直到事務A提交后才能執行)
2)并發update案例
id=1 的value初始為1,在可重復讀級別:
我們看一下,你猜猜事務A和事務B讀取的value是多少?
答案是:1 和 3
可能會產生困惑,事務A在啟動后快照,所以讀到了1是正常的,但是事務2在啟動的時候快照了,然后在自己的事務中+1,怎么會讀到3而不是2呢?
原因很簡單,即使是在可重復讀的級別,事務?更新數據?的時候,只能用當前讀(想想也能理解,不然update就出現數據不一致了)。
如果當前的記錄的行鎖被其他事務占用的話,就需要進入鎖等待。而讀提交的邏輯和可重復讀的邏輯類似,它們最主要的區別是:在可重復讀隔離級別下,只需要在事務開始的時候創建一致性視圖,之后事務里的其他查詢都共用這個一致性視圖;在讀提交隔離級別下,每一個語句執行前都會重新算出一個新的視圖。
這里,我們需要注意的是事務的啟動時機。
begin/start transaction 命令并不是一個事務的起點,在執行到它們之后的第一個操作 InnoDB 表的語句,事務才真正啟動,一致性視圖是在執行第一個快照讀語句時創建的。
如果你想要馬上啟動一個事務,可以使用 start transaction with consistent snapshot 這個命令,一致性視圖是在執行 start transaction with consistent snapshot 時創建的。
關于幻讀
前文已經提到了,對于普通數據庫,需要到可串行化的隔離級別才能解決幻讀問題。
而對于InnoDB存儲引擎來說,在可重復讀級別下就能解決幻讀問題。
InnoDB存儲引擎有三種行鎖算法:
行鎖:當個行記錄上的鎖
間隙鎖:Gap Lock,鎖定一個范圍,但不包含記錄本身
Next-Key Lock:就是行鎖+間隙鎖,同時鎖上一個范圍,并且鎖定記錄本身
InnoDB就是通過Next-Key Lock解決了幻讀的問題。
好了,關于ACID的隔離性就說到這里,你有什么看法,歡迎在評論區和我們討論。
「AI大師課」是CSDN發起的“百萬人學AI”倡議下的重要組成部分,4月份AI大師課以線上技術峰會的形式推出,來自微軟、硅谷TigerGraph、北郵等產學界大咖就圖計算+機器學習,語音技術、新基建+AI、AI+醫療等主題展開分享,掃描下方二維碼免費報名,限時再送299元「2020AI開發者萬人大會」門票一張。
推薦閱讀:一文教你如何使用 MongoDB 和 HATEOAS 創建 REST Web 服務 一個數據科學家需要哪些核心工具包? AI 開發者不容錯過的 20 個機器學習和數據科學網站 馬云為什么救援世界? 1分鐘售出5萬張票!電影節搶票技術揭秘 BTC重現“自由落體”式暴跌,原來是受這幾個因素影響? 真香,朕在看了!總結
以上是生活随笔為你收集整理的跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 七大新品集中亮相,腾讯云AI大数据全线升
- 下一篇: 2019年程序员薪酬报告:平均年薪超70