久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

MySQL 是如何實(shí)現(xiàn) ACID 的?

 昵稱10087950 2022-09-29 發(fā)布于江蘇

點(diǎn)擊關(guān)注公眾號,,Java干貨及時送達(dá)圖片

來源:https:///131.html

本文主要探討MySQL InnoDB 引擎下ACID的實(shí)現(xiàn)原理,,對于諸如什么是事務(wù),,隔離級別的含義等基礎(chǔ)知識不做過多闡述。

ACID

MySQL 作為一個關(guān)系型數(shù)據(jù)庫,,以最常見的 InnoDB 引擎來說,,是如何保證 ACID 的。

  • (Atomicity原子性: 事務(wù)是最小的執(zhí)行單位,,不允許分割,。原子性確保動作要么全部完成,,要么完全不起作用;
  • (Consistency)一致性: 執(zhí)行事務(wù)前后,,數(shù)據(jù)保持一致,;
  • (Isolation)隔離性: 并發(fā)訪問數(shù)據(jù)庫時,一個事務(wù)不被其他事務(wù)所干擾,。
  • (Durability)持久性: 一個事務(wù)被提交之后,。對數(shù)據(jù)庫中數(shù)據(jù)的改變是持久的,即使數(shù)據(jù)庫發(fā)生故障,。

隔離性

先說說隔離性,,首先是四種隔離級別。

隔離級別說明
讀未提交一個事務(wù)還沒提交時,,它做的變更就能被別的事務(wù)看到
讀提交一個事務(wù)提交之后,,它做的變更才會被其他事務(wù)看到
可重復(fù)讀一個事務(wù)中,對同一份數(shù)據(jù)的讀取結(jié)果總是相同的,,無論是否有其他事務(wù)對這份數(shù)據(jù)進(jìn)行操作,,以及這個事務(wù)是否提交,。InnoDB默認(rèn)級別,。
串行化事務(wù)串行化執(zhí)行,每次讀都需要獲得表級共享鎖,,讀寫相互都會阻塞,,隔離級別最高,犧牲系統(tǒng)并發(fā)性,。

不同的隔離級別是為了解決不同的問題,。也就是臟讀、幻讀,、不可重復(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)

那么不同的隔離級別,隔離性是如何實(shí)現(xiàn)的,,為什么不同事物間能夠互不干擾,?答案是 鎖 和 MVCC。

先來說說鎖,, MySQL 有多少鎖,。

粒度

從粒度上來說就是表鎖、頁鎖,、行鎖,。表鎖有意向共享鎖、意向排他鎖,、自增鎖等,。行鎖是在引擎層由各個引擎自己實(shí)現(xiàn)的,。但并不是所有的引擎都支持行鎖,比如 MyISAM 引擎就不支持行鎖,。

行鎖的種類

在 InnoDB 事務(wù)中,,行鎖通過給索引上的索引項加鎖來實(shí)現(xiàn)。這意味著只有通過索引條件檢索數(shù)據(jù),,InnoDB才使用行級鎖,,否則將使用表鎖。行級鎖定同樣分為兩種類型:共享鎖排他鎖,,以及加鎖前需要先獲得的意向共享鎖和意向排他鎖,。

  • 共享鎖:讀鎖,允許其他事務(wù)再加S鎖,,不允許其他事務(wù)再加X鎖,,即其他事務(wù)只讀不可寫。select...lock in share mode 加鎖,。
  • 排它鎖:寫鎖,,不允許其他事務(wù)再加S鎖或者X鎖。insert,、update,、delete、for update加鎖,。

行鎖是在需要的時候才加的,,但并不是不需要了就立刻釋放,而是要等到事務(wù)結(jié)束時才釋放,。這個就是兩階段鎖協(xié)議,。

行鎖的實(shí)現(xiàn)算法

Record Lock

單個行記錄上的鎖,總是會去鎖住索引記錄,。

Gap Lock

間隙鎖,,想一下幻讀的原因,其實(shí)就是行鎖只能鎖住行,,但新插入記錄這個動作,,要更新的是記錄之間的“間隙”。所以加入間隙鎖來解決幻讀,。

Next-Key Lock

Gap Lock + Record Lock, 左開又閉,。

鎖之于隔離性

大致介紹了下鎖,可以看到,。有了鎖,,當(dāng)某事務(wù)正在寫數(shù)據(jù)時,其他事務(wù)獲取不到寫鎖,,就無法寫數(shù)據(jù),,一定程度上保證了事務(wù)間的隔離,。但前面說,加了寫鎖,,為什么其他事務(wù)也能讀數(shù)據(jù)呢,,不是獲取不到讀鎖嗎

MVCC

前面說到,,有了鎖,,當(dāng)前事務(wù)沒有寫鎖就不能修改數(shù)據(jù),但還是能讀的,,而且讀的時候,,即使該行數(shù)據(jù)其他事務(wù)已修改且提交,還是可以重復(fù)讀到同樣的值,。這就是MVCC,,多版本的并發(fā)控制,Multi-Version Concurrency Control,。

版本鏈

Innodb 中行記錄的存儲格式,,有一些額外的字段:DATA_TRX_ID和DATA_ROLL_PTR

  • DATA_TRX_ID:數(shù)據(jù)行版本號,。用來標(biāo)識最近對本行記錄做修改的事務(wù) id,。
  • DATA_ROLL_PTR:指向該行回滾段的指針。該行記錄上所有舊版本,,在 undo log 中都通過鏈表的形式組織,。

undo log : 記錄數(shù)據(jù)被修改之前的日志,,后面會詳細(xì)說,。

ReadView

在每一條 SQL 開始的時候被創(chuàng)建,有幾個重要屬性:

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

開始查詢

現(xiàn)在開始查詢,,一個 select 過來了,,找到了一行數(shù)據(jù)。

  • DATA_TRX_ID <up_limit_id :說明數(shù)據(jù)在當(dāng)前事務(wù)之前就存在了,,顯示,。

  • 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 <DATA_TRX_ID <low_limit_id :就要看隔離級別了,。

點(diǎn)擊關(guān)注公眾號,,Java干貨及時送達(dá)

RR 級別的幻讀

有了鎖和 MVCC , 事務(wù)的隔離性得到解決,。這里要引申一下,,默認(rèn)的 RR 的級別,解決了幻讀嗎,?幻讀通常針對的是 INSERT, 不可重復(fù)度則針對 UPDATE ,。

事物 1事物 2
beginbegin
select * from dept
-insert into dept(name) values("A")
-commit
update dept set name="B"
commit

我們期望是

id  name
1   A
2   B

實(shí)際卻是

id  name
1   B
2   B

其實(shí)在 MySQL 可重復(fù)讀的隔離級別中并不是完全解決了幻讀的問題,而是解決了讀數(shù)據(jù)情況下的幻讀問題,。而對于修改的操作依舊存在幻讀問題,,就是說 MVCC 對于幻讀的解決時不徹底的。另外,,MySQL 系列面試題和答案全部整理好了,,微信搜索Java技術(shù)棧,在后臺發(fā)送:面試,,可以在線閱讀,。

原子性

接著說說原子性。前文有提到 undo log ,,回滾日志,。隔離性的MVCC其實(shí)就是依靠它來實(shí)現(xiàn)的,原子性也是,。實(shí)現(xiàn)原子性的關(guān)鍵,,是當(dāng)事務(wù)回滾時能夠撤銷所有已經(jīng)成功執(zhí)行的sql語句。

最新 Java 核心技術(shù)推薦:https://github.com/javastacks/javastack

當(dāng)事務(wù)對數(shù)據(jù)庫進(jìn)行修改時,InnoDB會生成對應(yīng)的 undo log,;如果事務(wù)執(zhí)行失敗或調(diào)用了 rollback,,導(dǎo)致事務(wù)需要回滾,便可以利用 undo log 中的信息將數(shù)據(jù)回滾到修改之前的樣子,。undo log 屬于邏輯日志,,它記錄的是sql執(zhí)行相關(guān)的信息。當(dāng)發(fā)生回滾時,,InnoDB 會根據(jù) undo log 的內(nèi)容做與之前相反的工作:

  • 對于每個 insert,,回滾時會執(zhí)行 delete;
  • 對于每個 delete,,回滾時會執(zhí)行insert,;
  • 對于每個 update,回滾時會執(zhí)行一個相反的 update,,把數(shù)據(jù)改回去,。

以update操作為例:當(dāng)事務(wù)執(zhí)行update時,其生成的undo log中會包含被修改行的主鍵(以便知道修改了哪些行),、修改了哪些列,、這些列在修改前后的值等信息,回滾時便可以使用這些信息將數(shù)據(jù)還原到update之前的狀態(tài),。

持久性

Innnodb有很多 log,,持久性靠的是 redo log。

一條SQL更新語句怎么運(yùn)行

持久性肯定和寫有關(guān),,MySQL 里經(jīng)常說到的 WAL 技術(shù),,WAL 的全稱是 Write-Ahead Logging,它的關(guān)鍵點(diǎn)就是先寫日志,,再寫磁盤,。就像小店做生意,有個粉板,,有個賬本,,來客了先寫粉板,,等不忙的時候再寫賬本,。點(diǎn)擊在線刷題,看看你可以打多少分了,。

redo log

redo log 就是這個粉板,,當(dāng)有一條記錄要更新時,InnoDB 引擎就會先把記錄寫到 redo log(并更新內(nèi)存),,這個時候更新就算完成了,。在適當(dāng)?shù)臅r候,將這個操作記錄更新到磁盤里面,而這個更新往往是在系統(tǒng)比較空閑的時候做,,這就像打烊以后掌柜做的事,。

redo log 有兩個特點(diǎn):

  • 大小固定,循環(huán)寫
  • crash-safe

對于redo log 是有兩階段的:commit 和 prepare 如果不使用“兩階段提交”,,數(shù)據(jù)庫的狀態(tài)就有可能和用它的日志恢復(fù)出來的庫的狀態(tài)不一致. 好了,,先到這里,看看另一個,。

Buffer Pool

InnoDB還提供了緩存,,Buffer Pool 中包含了磁盤中部分?jǐn)?shù)據(jù)頁的映射,作為訪問數(shù)據(jù)庫的緩沖:

  • 當(dāng)讀取數(shù)據(jù)時,,會先從Buffer Pool中讀取,,如果Buffer Pool中沒有,則從磁盤讀取后放入Buffer Pool,;
  • 當(dāng)向數(shù)據(jù)庫寫入數(shù)據(jù)時,,會首先寫入Buffer Pool,Buffer Pool中修改的數(shù)據(jù)會定期刷新到磁盤中,。

Buffer Pool 的使用大大提高了讀寫數(shù)據(jù)的效率,,但是也帶了新的問題:如果MySQL宕機(jī),而此時 Buffer Pool 中修改的數(shù)據(jù)還沒有刷新到磁盤,,就會導(dǎo)致數(shù)據(jù)的丟失,,事務(wù)的持久性無法保證。

所以加入了 redo log,。當(dāng)數(shù)據(jù)修改時,,除了修改Buffer Pool中的數(shù)據(jù),還會在redo log記錄這次操作,;

當(dāng)事務(wù)提交時,,會調(diào)用fsync接口對redo log進(jìn)行刷盤。

如果MySQL宕機(jī),,重啟時可以讀取redo log中的數(shù)據(jù),,對數(shù)據(jù)庫進(jìn)行恢復(fù)。

redo log采用的是WAL(Write-ahead logging,,預(yù)寫式日志),,所有修改先寫入日志,再更新到Buffer Pool,,保證了數(shù)據(jù)不會因MySQL宕機(jī)而丟失,,從而滿足了持久性要求。而且這樣做還有兩個優(yōu)點(diǎn):

  • 刷臟頁是隨機(jī) IO,,redo log 順序 IO
  • 刷臟頁以Page為單位,,一個Page上的修改整頁都要寫,;而redo log 只包含真正需要寫入的,無效 IO 減少,。

binlog

說到這,,可能會疑問還有個 bin log 也是寫操作并用于數(shù)據(jù)的恢復(fù),有啥區(qū)別呢,。

binlog 和 redo log

對于語句 update T set c=c+1 where ID=2;

  1. 執(zhí)行器先找引擎取 ID=2 這一行。ID 是主鍵,,直接用樹搜索找到,。如果 ID = 2 這一行所在數(shù)據(jù)頁就在內(nèi)存中,就直接返回給執(zhí)行器,;否則,,需要先從磁盤讀入內(nèi)存,再返回,。
  2. 執(zhí)行器拿到引擎給的行數(shù)據(jù),把這個值加上 1,,N+1,,得到新的一行數(shù)據(jù),再調(diào)用引擎接口寫入這行新數(shù)據(jù),。
  3. 引擎將這行新數(shù)據(jù)更新到內(nèi)存中,,同時將這個更新操作記錄到 redo log 里面,,此時 redo log 處于 prepare 狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,,隨時可以提交事務(wù),。
  4. 執(zhí)行器生成這個操作的 binlog,并把 binlog 寫入磁盤,。
  5. 執(zhí)行器調(diào)用引擎的提交事務(wù)接口,,引擎把剛剛寫入的 redo log 改成提交(commit)狀態(tài),更新完成

為什么先寫 redo log 呢 ,?

  • 先 redo 后 bin : binlog 丟失,,少了一次更新,恢復(fù)后仍是0,。
  • 先 bin 后 redo : 多了一次事務(wù),,恢復(fù)后是1。

一致性

一致性是事務(wù)追求的最終目標(biāo),,前問所訴的原子性,、持久性和隔離性,其實(shí)都是為了保證數(shù)據(jù)庫狀態(tài)的一致性,。當(dāng)然,,上文都是數(shù)據(jù)庫層面的保障,一致性的實(shí)現(xiàn)也需要應(yīng)用層面進(jìn)行保障,。

也就是你的業(yè)務(wù),,比如購買操作只扣除用戶的余額,不減庫存,,肯定無法保證狀態(tài)的一致,。

總結(jié)

MySQL 都很熟, ACID 也知道是個啥,,但 MySQL 的 ACID 怎么實(shí)現(xiàn)的,?

有時候,就像你知道了有 undo log,、redo log 但可能并不太清楚為什么有,,當(dāng)知道了設(shè)計的目的,了解起來就會更加清晰了,。另外,,關(guān)注公眾號Java技術(shù)棧,在后臺回復(fù):面試,可以獲取我整理的 Java/ MySQL 系列面試題和答案,,非常齊全,。

參考:

https://zhuanlan.zhihu.com/p/52977862
https:///articles/39212
https://www.cnblogs.com/rjzheng/p/10841031.html
https://www.cnblogs.com/kismetv/p/10331633.html



    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多