一育八、為什么需要事務(wù)实檀?
舉個(gè)例子:
A 和 B 賬戶各有 50 元余額,現(xiàn)在 A 要給 B 轉(zhuǎn)賬 10 元,刨除掉具體的業(yè)務(wù)邏輯和工作流程豺旬,僅考慮數(shù)據(jù)庫(kù)層面的操作:A 賬戶-10,B 賬戶+10 。
如果沒(méi)有事務(wù),就只能讓兩個(gè) update 操作串行執(zhí)行:
// 代碼片段1
run("update account set balance=balance-10 where account_id=A");
run("update account set balance=balance+10 where account_id=B");
如圖1北启,對(duì)于這個(gè)轉(zhuǎn)賬操作场钉,我們的期望是:
- 代碼片段1執(zhí)行前,數(shù)據(jù)庫(kù)數(shù)據(jù):A 余額 50,B 余額 50。
- 代碼片段1執(zhí)行后,數(shù)據(jù)庫(kù)數(shù)據(jù):A 余額 40,B 余額 60。
但是,如果執(zhí)行完 A-10 后,系統(tǒng)崩潰了,B+10 沒(méi)能得到執(zhí)行。則:
- 代碼片段1執(zhí)行前须蜗,數(shù)據(jù)庫(kù)數(shù)據(jù):A 余額 50菱农,B 余額50的妖。
- 代碼片段1執(zhí)行后嫂粟,數(shù)據(jù)庫(kù)數(shù)據(jù):A 余額 40娇未,B 余額50。
- 在此場(chǎng)景下星虹,對(duì) A 來(lái)說(shuō)忘蟹,這個(gè)轉(zhuǎn)賬操作是成功了的,畢竟錢(qián)都扣掉了搁凸,但 B 卻沒(méi)有收到對(duì)應(yīng)的款項(xiàng)媚值,也就是說(shuō)從 A 賬戶扣掉的 10 塊錢(qián)憑空消失了。這在實(shí)際業(yè)務(wù)中是不可接受的护糖。
要解決這個(gè)問(wèn)題褥芒,就必須有一套機(jī)制,來(lái)保證 A-10 和 B+10 這兩個(gè)操作同時(shí)成功嫡良,或同時(shí)失敗锰扶,以確保兩個(gè) update 操作執(zhí)行前后的數(shù)據(jù)的一致性、正確性和完整性寝受。
這套機(jī)制坷牛,就是“事務(wù)”。
二很澄、事務(wù)定義
數(shù)據(jù)庫(kù)事務(wù) 是可以作為一個(gè)完整的邏輯工作單元來(lái)執(zhí)行的不可分割的一組操作京闰,這些操作要么全部執(zhí)行,要么全部不執(zhí)行甩苛。
- 在關(guān)系型數(shù)據(jù)庫(kù)中蹂楣,事務(wù)可以是一條sql語(yǔ)句,也可以是多條sql語(yǔ)句讯蒲。
- 在此基礎(chǔ)之上痊土,為了保證這一組操作執(zhí)行結(jié)果的正確性和有效性,事務(wù)又附加了一些條件墨林,就是ACID了赁酝。
ACID 的關(guān)注點(diǎn)和對(duì)事務(wù)的要求如下:
一致?tīng)顟B(tài)犯祠,是指數(shù)據(jù)處于一種語(yǔ)義上的有意義且正確的狀態(tài)。
隔離性還有其他的稱呼酌呆,如:并發(fā)控制衡载、可串行化、鎖等肪笋。
三、事務(wù)并發(fā)問(wèn)題和隔離模式
并發(fā)訪問(wèn)場(chǎng)景下度迂,若沒(méi)有采取必要的隔離措施藤乙,會(huì)存在一些讀寫(xiě)問(wèn)題,包括:
- 3 類數(shù)據(jù)讀問(wèn)題:臟讀惭墓、不可重復(fù)讀和幻讀坛梁。
- 2 類數(shù)據(jù)更新問(wèn)題:第一類丟失更新、第二類丟失更新腊凶。
數(shù)據(jù)庫(kù)提供不同級(jí)別的事務(wù)隔離模式划咐,解決部分或全部上述的讀寫(xiě)問(wèn)題,SQL 規(guī)范定義了四種級(jí)別的隔離模式(級(jí)別由低到高):
- Read Uncommitted(讀未提交):最低的隔離級(jí)別钧萍,什么都不需要做褐缠,一個(gè)事務(wù)可以讀到另一個(gè)事務(wù)未提交的結(jié)果。所有的并發(fā)事務(wù)問(wèn)題都會(huì)發(fā)生风瘦。
- Read Committed(讀已提交):只有在事務(wù)提交后队魏,其更新結(jié)果才會(huì)被其他事務(wù)看見(jiàn)⊥蛏Γ可以解決臟讀問(wèn)題胡桨。
- Repeated Read(可重復(fù)讀):在一個(gè)事務(wù)中,對(duì)于同一份數(shù)據(jù)的讀取結(jié)果總是相同的瞬雹,無(wú)論是否有其他事務(wù)對(duì)這份數(shù)據(jù)進(jìn)行操作昧谊,以及這個(gè)事務(wù)是否提交⌒锇疲可以解決臟讀呢诬、不可重復(fù)讀。
- Serialization(串行化):事務(wù)串行化執(zhí)行胖缤,最高隔離級(jí)別馅巷,犧牲系統(tǒng)的并發(fā)性,將所有事務(wù)串行執(zhí)行草姻〉鲡可以解決并發(fā)事務(wù)的所有問(wèn)題。
事務(wù)的隔離級(jí)別和數(shù)據(jù)庫(kù)并發(fā)性是成反比的撩独,隔離級(jí)別越高敞曹,并發(fā)性越低账月。
四、ACID的實(shí)現(xiàn)技術(shù)
ACID 的實(shí)現(xiàn)技術(shù)包括:并發(fā)控制澳迫、日志管理局齿、備份恢復(fù)、鎖管理橄登、MVCC等內(nèi)容抓歼。
其中,并發(fā)控制和日志技術(shù)是核心拢锹。
4.1 并發(fā)控制技術(shù)
并發(fā)控制技術(shù)是實(shí)現(xiàn)原子性谣妻、一致性和隔離性的重要技術(shù)之一。并發(fā)控制的本質(zhì)就是要對(duì)并發(fā)的事務(wù)實(shí)現(xiàn)正確又高效的調(diào)度卒稳。
從實(shí)現(xiàn)思想的角度看蹋半,并發(fā)控制技術(shù)分兩類:
- 樂(lè)觀并發(fā)控制,Optimistic Concurrency Control充坑,OOC减江,事后檢查。
- 悲觀并發(fā)控制捻爷,Pessimistic Concurrency Control辈灼,PCC,提前預(yù)防也榄。
從實(shí)現(xiàn)技術(shù)角度茵休,并發(fā)控制機(jī)制有如下幾類:
- 基于鎖的并發(fā)控制機(jī)制
基于鎖的并發(fā)控制機(jī)制是最常見(jiàn)的一種并發(fā)控制機(jī)制,事務(wù)中可能涉及到的一些鎖的概念如下圖:
- 基于時(shí)間戳/數(shù)據(jù)版本的并發(fā)控制
基于數(shù)據(jù)版本的并發(fā)訪問(wèn)控制手蝎,是通過(guò)給數(shù)據(jù)表加一個(gè)版本號(hào)或時(shí)間戳字段實(shí)現(xiàn)榕莺。
- 當(dāng)讀取數(shù)據(jù)時(shí),將 version字段的值一同讀出棵介,數(shù)據(jù)每更新一次钉鸯,對(duì)此 version 值加一。
- 當(dāng)提交更新的時(shí)候邮辽,判斷當(dāng)前版本信息與第一次取出來(lái)的版本值大小唠雕,如果數(shù)據(jù)庫(kù)表當(dāng)前版本號(hào)與第一次取出來(lái)的 version 值相等,則予以更新吨述,否則認(rèn)為是過(guò)期數(shù)據(jù)岩睁,拒絕更新。
基于時(shí)間戳的并發(fā)控制類似揣云,把版本號(hào)換成時(shí)間戳就行了捕儒。
- 基于MVCC的并發(fā)控制
MVCC(Multi-Version Concurrent Control),即多版本并發(fā)控制協(xié)議,是個(gè)行級(jí)鎖的變種刘莹,它在普通讀情況下避免了加鎖操作阎毅,因此開(kāi)銷更低,同時(shí)在保證數(shù)據(jù)一致性的前提下点弯,提供一種高并發(fā)的訪問(wèn)性能扇调。
雖然不同數(shù)據(jù)庫(kù)或數(shù)據(jù)庫(kù)引擎對(duì) MVCC 的實(shí)現(xiàn)不同,但通常都是實(shí)現(xiàn)非阻塞讀抢肛,對(duì)于寫(xiě)操作只鎖定必要的行:
- 第一種實(shí)現(xiàn)方式:將數(shù)據(jù)記錄的多個(gè)版本保存在數(shù)據(jù)庫(kù)中狼钮,當(dāng)這些不同版本數(shù)據(jù)不再需要時(shí),垃圾回收器回收這些記錄捡絮“疚撸——PostgreSQL 和 Firebird/Interbase 采用。
- 第二種實(shí)現(xiàn)方式:只在數(shù)據(jù)庫(kù)保存最新版本的數(shù)據(jù)锦援,但是會(huì)在使用undo時(shí)動(dòng)態(tài)重構(gòu)舊版本數(shù)據(jù)猛蔽“颍——Oracle 和 MySQL/InnoDB 采用灵寺。
4.2 日志技術(shù)
數(shù)據(jù)庫(kù)的日志可以大體分為3類:binlog区岗、redo log、undo log叮称。
其中,binlog是Server層記錄的日志藐鹤, redo log 和 undo log 是數(shù)據(jù)庫(kù)存儲(chǔ)引擎層的日志瓤檐。
大部分關(guān)系型數(shù)據(jù)庫(kù)系統(tǒng)是通過(guò) redo log 和 undo log 來(lái)實(shí)現(xiàn)事務(wù)的原子性娱节、一致性和持久性,同時(shí)也用于支持?jǐn)?shù)據(jù)備份和恢復(fù):
- redo log肄满,記錄數(shù)據(jù)被修改后的值谴古,可以用來(lái)恢復(fù)未寫(xiě)入 data file 的已成功事務(wù)更新的數(shù)據(jù)。redo log 又包括:redo log buffer 和 redo log file稠歉,一個(gè)寫(xiě)內(nèi)存,一個(gè)寫(xiě)硬盤(pán)带饱。
- undo log,記錄數(shù)據(jù)被修改前的值纠炮,可以用來(lái)在事務(wù)失敗時(shí)進(jìn)行 rollback。
舉個(gè)例子恢口,假設(shè) A=1且 B=2,某事務(wù) T 要做 A=3 和 B=4因妇,則
- 事務(wù)簡(jiǎn)化過(guò)程:
1.start
2.A=1——>undo log
3.set A=3
4.A=3——>redo log buffer
5.B=2——>undo log
6.set B=4
7.B=4——>redo log buffer
8.redo log buffer——>redo log file
9.commit
- undo 和 redo 日志:
// undo日志:
<T,A,1>
<T,B,2>
// redo日志:
<T,A,3>
<T,B,4>
- 數(shù)據(jù)恢復(fù)(重做猿诸、撤銷)
若執(zhí)行 9 時(shí)出現(xiàn)系統(tǒng)異常,則下次啟動(dòng)時(shí)可以通過(guò) redo log 重做該事務(wù)梳虽。
若執(zhí)行 6 時(shí)出現(xiàn)異常,則可以通過(guò) undo log 撤銷已經(jīng)做過(guò)的修改谷炸。 - undo/redo日志
也有把 undo 和 redo 結(jié)合起來(lái)的做法禀挫,叫做 Undo/Redo 日志,在前面中的例子
Undo/Redo 日志為:
<T, A, 1, 3>
<T, B, 2, 4>