MySQL 是如何實(shí)現(xiàn) ACID 的?
閱讀本文大概需要 8 分鐘。
來自:https://llc687.top/131.html
本文主要探討MySQL InnoDB 引擎下ACID的實(shí)現(xiàn)原理,對于諸如什么是事務(wù),隔離級別的含義等基礎(chǔ)知識不做過多闡述。
ACID
(Atomicity)原子性:?事務(wù)是最小的執(zhí)行單位,不允許分割。原子性確保動(dòng)作要么全部完成,要么完全不起作用; (Consistency)一致性:?執(zhí)行事務(wù)前后,數(shù)據(jù)保持一致; (Isolation)隔離性:?并發(fā)訪問數(shù)據(jù)庫時(shí),一個(gè)事務(wù)不被其他事務(wù)所干擾。 (Durability)持久性:?一個(gè)事務(wù)被提交之后。對數(shù)據(jù)庫中數(shù)據(jù)的改變是持久的,即使數(shù)據(jù)庫發(fā)生故障。
隔離性
| 隔離級別 | 說明 |
|---|---|
| 讀未提交 | 一個(gè)事務(wù)還沒提交時(shí),它做的變更就能被別的事務(wù)看到 |
| 讀提交 | 一個(gè)事務(wù)提交之后,它做的變更才會(huì)被其他事務(wù)看到 |
| 可重復(fù)讀 | 一個(gè)事務(wù)中,對同一份數(shù)據(jù)的讀取結(jié)果總是相同的,無論是否有其他事務(wù)對這份數(shù)據(jù)進(jìn)行操作,以及這個(gè)事務(wù)是否提交。InnoDB默認(rèn)級別。 |
| 串行化 | 事務(wù)串行化執(zhí)行,每次讀都需要獲得表級共享鎖,讀寫相互都會(huì)阻塞,隔離級別最高,犧牲系統(tǒng)并發(fā)性。 |
| 隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
|---|---|---|---|
| 讀未提交 | 可以出現(xiàn) | 可以出現(xiàn) | 可以出現(xiàn) |
| 讀提交 | 不允許出現(xiàn) | 可以出現(xiàn) | 可以出現(xiàn) |
| 可重復(fù)讀 | 不允許出現(xiàn) | 不允許出現(xiàn) | 可以出現(xiàn) |
| 序列化 | 不允許出現(xiàn) | 不允許出現(xiàn) | 不允許出現(xiàn) |
鎖
粒度
行鎖的種類
共享鎖:讀鎖,允許其他事務(wù)再加S鎖,不允許其他事務(wù)再加X鎖,即其他事務(wù)只讀不可寫。 select...lock in share mode?加鎖。排它鎖:寫鎖,不允許其他事務(wù)再加S鎖或者X鎖。 insert、update、delete、for update加鎖。
行鎖的實(shí)現(xiàn)算法
Record Lock
Gap Lock
Next-Key Lock
鎖之于隔離性
MVCC
版本鏈
DATA_TRX_ID:數(shù)據(jù)行版本號。用來標(biāo)識最近對本行記錄做修改的事務(wù) id。 DATA_ROLL_PTR:指向該行回滾段的指針。該行記錄上所有舊版本,在? undo log?中都通過鏈表的形式組織。
undo log : 記錄數(shù)據(jù)被修改之前的日志,后面會(huì)詳細(xì)說。

ReadView
trx_ids:?當(dāng)前系統(tǒng)活躍(未提交)事務(wù)版本號集合。 low_limit_id:?創(chuàng)建當(dāng)前 read view 時(shí)“當(dāng)前系統(tǒng)最大事務(wù)版本號+1”。 up_limit_id:?創(chuàng)建當(dāng)前read view 時(shí)“系統(tǒng)正處于活躍事務(wù)最小版本號” creator_trx_id:?創(chuàng)建當(dāng)前read view的事務(wù)版本號;

開始查詢
DATA_TRX_ID
DATA_TRX_ID >= low_limit_id:
說明該數(shù)據(jù)是在當(dāng)前read view 創(chuàng)建后才產(chǎn)生的,數(shù)據(jù)不顯示。
不顯示怎么辦,根據(jù)?DATA_ROLL_PTR 從 undo log 中找到歷史版本,找不到就空。 up_limit_id
?<low_limit_id :就要看隔離級別了。

RR 級別的幻讀
| 事物 1 | 事物 2 |
|---|---|
| begin | begin |
| select * from dept | |
| - | insert into dept(name) values("A") |
| - | commit |
| update dept set name="B" | |
| commit |
id??name
1???A
2???B
id??name
1???B
2???B
原子性
對于每個(gè) insert,回滾時(shí)會(huì)執(zhí)行 delete; 對于每個(gè) delete,回滾時(shí)會(huì)執(zhí)行insert; 對于每個(gè) update,回滾時(shí)會(huì)執(zhí)行一個(gè)相反的 update,把數(shù)據(jù)改回去。
持久性
一條SQL更新語句怎么運(yùn)行
redo log
大小固定,循環(huán)寫 crash-safe
Buffer Pool
當(dāng)讀取數(shù)據(jù)時(shí),會(huì)先從Buffer Pool中讀取,如果Buffer Pool中沒有,則從磁盤讀取后放入Buffer Pool; 當(dāng)向數(shù)據(jù)庫寫入數(shù)據(jù)時(shí),會(huì)首先寫入Buffer Pool,Buffer Pool中修改的數(shù)據(jù)會(huì)定期刷新到磁盤中。
刷臟頁是隨機(jī) IO,redo log 順序 IO 刷臟頁以Page為單位,一個(gè)Page上的修改整頁都要寫;而redo log 只包含真正需要寫入的,無效 IO 減少。
binlog
層次:redo log 是 innoDB 引擎特有的,server 層的叫 binlog(歸檔日志) 內(nèi)容:redolog 是物理日志,記錄“在某個(gè)數(shù)據(jù)頁上做了什么修改”;binlog 是邏輯日志,是語句的原始邏輯,如“給 ID=2 這一行的 c 字段加 1 ” 寫入:redolog 循環(huán)寫且寫入時(shí)機(jī)較多,binlog 追加且在事務(wù)提交時(shí)寫入
binlog 和 redo log
update T set c=c+1 where ID=2;執(zhí)行器先找引擎取 ID=2 這一行。ID 是主鍵,直接用樹搜索找到。如果 ID = 2 這一行所在數(shù)據(jù)頁就在內(nèi)存中,就直接返回給執(zhí)行器;否則,需要先從磁盤讀入內(nèi)存,再返回。 執(zhí)行器拿到引擎給的行數(shù)據(jù),把這個(gè)值加上 1,N+1,得到新的一行數(shù)據(jù),再調(diào)用引擎接口寫入這行新數(shù)據(jù)。 引擎將這行新數(shù)據(jù)更新到內(nèi)存中,同時(shí)將這個(gè)更新操作記錄到 redo log 里面,此時(shí) redo log 處于 prepare 狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,隨時(shí)可以提交事務(wù)。 執(zhí)行器生成這個(gè)操作的 binlog,并把 binlog 寫入磁盤。 執(zhí)行器調(diào)用引擎的提交事務(wù)接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態(tài),更新完成
先 redo 后 bin : binlog 丟失,少了一次更新,恢復(fù)后仍是0。 先 bin 后 redo : 多了一次事務(wù),恢復(fù)后是1。
一致性
總結(jié)
https://learnku.com/articles/39212
https://www.cnblogs.com/rjzheng/p/10841031.html
https://www.cnblogs.com/kismetv/p/10331633.html
推薦閱讀:
被通知一個(gè)月后離職,我改了重要項(xiàng)目里的代碼注釋
Java實(shí)現(xiàn)10萬+并發(fā)去重,持續(xù)優(yōu)化!
內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬并發(fā)、消息隊(duì)列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper......等技術(shù)棧!
?戳閱讀原文領(lǐng)取!? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??朕已閱?
評論
圖片
表情

