事務(wù)理論及其實(shí)現(xiàn)方案百度資源分享
一、用戶匯款場景#
用戶 A 的賬戶存有 2200 元,用戶 B 的賬戶存有 1600 元?,F(xiàn)在用戶 A 給用戶 B 匯款 200 元,正確的執(zhí)行步驟為:
A 用戶:A存款 = 2200 - 200
B 用戶:B存款 = 1600 + 200
如果上面的匯款步驟正確執(zhí)行完,那么 A 用戶現(xiàn)在的存款數(shù)是 2000 元, B 用戶現(xiàn)在的存款數(shù)是 1800。
如果上面的匯款步驟沒有正確執(zhí)行完呢?比如遇到了異常情況, A 用戶存款數(shù)已經(jīng)扣去了 200, B 用戶的存款數(shù)卻沒有加上 200。那么現(xiàn)在 A 用戶存款數(shù)是 2000 元,B 用戶的存儲(chǔ)數(shù)還是 1600 元。此時(shí),A 用戶存款數(shù)和 B 用戶存款數(shù)都出現(xiàn)了錯(cuò)誤。
A 用戶匯款了存款數(shù)減少了,B 用戶卻沒有收到匯款。這種情況一定會(huì)給用戶造成困擾,不得不打電話去銀行詢問出了啥事情?
為什么會(huì)出現(xiàn)這種情況?
在匯款操作的步驟中,有 2 個(gè)步驟:
A 用戶匯款 2. B 用戶收款
日常理解來看這 2 個(gè)步驟沒多大問題,但是在計(jì)算機(jī)軟件執(zhí)行操作中來看,A 扣款和 B 收款是2個(gè)不同動(dòng)作,所以 A 扣款動(dòng)作可能成功也可能失敗,B 同樣如此。因?yàn)槌绦蚝途W(wǎng)絡(luò)有可能出現(xiàn)各種異常情況。
這種分步驟的日常操作,在軟件中我們需要把它們當(dāng)作一個(gè)整體來操作,操作結(jié)果是要么都成功,要么都失敗。
上面匯款來說,都成功就是,A 扣款減少和 B 收款增加
都成功。都失敗就是,A 扣款減少和 B 收款增加
都失敗,A 回滾到原來存款數(shù),B 存款數(shù)不增不減。(A 的存款數(shù) + B 的存款數(shù))= 總資金數(shù),總資金數(shù)在匯款前后是不變的。
A扣款操作和B收款操作當(dāng)作是一個(gè)整體操作,一個(gè)不可分割的原子操作。在計(jì)算機(jī)軟件中,我們把這種操作叫做事務(wù)。
二、什么是事務(wù)#
上面討論的場景已經(jīng)引出了什么是事務(wù)?
事務(wù)是將程序中的多個(gè)操作(比如多個(gè)讀、寫等)"糅合"在一起成為一個(gè)整體的邏輯操作單元,整個(gè)邏輯操作單元中的所有讀寫操作是一個(gè)執(zhí)行的整體,整體被視為一個(gè)不可分割的操作,這個(gè)就稱為事務(wù)。事務(wù)操作要么成功,要么失敗(終止或回滾)。
如果事務(wù)操作失敗了,不需要擔(dān)心事務(wù)里的部分操作失敗的情況,部分失敗后可回滾,恢復(fù)到原來數(shù)據(jù)狀態(tài)。
三、事務(wù)的 ACID 特性#
通常說到數(shù)據(jù)庫事務(wù)時(shí),都會(huì)提到 ACID 這 4 個(gè)特性。這 4 個(gè)特性是 TheoHarder 和 Andreas Reuter 于1983年為精確描述數(shù)據(jù)庫的容錯(cuò)機(jī)制而定義的。
但各家數(shù)據(jù)庫系統(tǒng)實(shí)現(xiàn)的 ACID 又不盡相同。有些系統(tǒng)說自己提供事務(wù)時(shí)或”兼容ACID“時(shí),其實(shí)我們無法確信它究竟提供了什么樣的保證,需要仔細(xì)查看該系統(tǒng)文檔或代碼才知曉細(xì)節(jié)。
InnoDB 默認(rèn)事務(wù)隔離級(jí)別是可重復(fù)讀,不滿足隔離性;Oracle 默認(rèn)的事務(wù)隔離級(jí)別為讀已提交,不滿足隔離性。因?yàn)橛袝r(shí)完全滿足就可能導(dǎo)致性能問題,有一個(gè)取舍平衡。
下面看看 ACID 具體含義:
C(Consistency) 一致性:當(dāng)事務(wù)開始和結(jié)束時(shí),數(shù)據(jù)處于一致的狀態(tài)。比如上面匯款場景,總資金數(shù)(數(shù)據(jù))在匯款前后是不變的,保持了前后一致。
有的人說一致性是 ACID 的目的,只要保證了原子性、隔離性、持久性,也就保證了數(shù)據(jù)的一致性。
數(shù)據(jù)庫提供了某些一致性的約束,比如主鍵 ID,唯一索引等。對于業(yè)務(wù)數(shù)據(jù)一致性,數(shù)據(jù)庫并沒有提供很好的約束,業(yè)務(wù)數(shù)據(jù)的一致性需要應(yīng)用程序來保證。
A(Atomicity) 原子性:原子通常指不可分割的最小粒度物質(zhì)。事務(wù)中對數(shù)據(jù)的所有寫操作像一個(gè)單一的操作一樣執(zhí)行,操作是不可分割的,要么都成功,要么都失敗(終止或回滾)。
ACID 中的原子性并不是關(guān)于多個(gè)操作的并發(fā)性,它沒有描述多個(gè)線程訪問相同數(shù)據(jù)會(huì)發(fā)生什么情況,這種情況是由 ACID 中的隔離性定義。原子性描述的是客戶端發(fā)起一個(gè)包含多個(gè)寫操作請求時(shí)可能發(fā)生的情況,比如一部分寫入后,發(fā)生系統(tǒng)故障,包括進(jìn)程崩潰,網(wǎng)絡(luò)中斷,磁盤滿了等情況;這里原子性是把多個(gè)寫操作作為一個(gè)原子操作,作為一個(gè)整體,萬一遇到故障導(dǎo)致沒能最終提交,事務(wù)會(huì)終止,數(shù)據(jù)庫必須丟棄或撤銷局部完成的更改。
I(Isolation) 隔離性:在處理程序時(shí),事務(wù)保證了各種數(shù)據(jù)操作相互獨(dú)立性,事務(wù)的中間狀態(tài)對其它事務(wù)是不可見的。
這里的隔離性意味著并發(fā)執(zhí)行多個(gè)事務(wù)是相互獨(dú)立、相互隔離的。經(jīng)典數(shù)據(jù)庫教材把隔離性定義為可串行化(一個(gè)一個(gè)的執(zhí)行)。但是在實(shí)踐中,串行化有心性能問題。比如 Oracel 11g,聲稱有“串行化”功能,但它本質(zhì)是快照隔離,比串行化更弱的保證。
D(Durability) 持久性:事務(wù)應(yīng)該保證所有成功提交的數(shù)據(jù)都能被持久化,即使發(fā)生故障,也不會(huì)丟失數(shù)據(jù)。
數(shù)據(jù)庫會(huì)保障一旦事務(wù)提交成功,即使硬件故障或數(shù)據(jù)庫崩潰,事務(wù)所寫入的數(shù)據(jù)也不會(huì)丟失。
四、MySQL中的事務(wù)#
MySQL事務(wù)介紹#
事務(wù)的概念其實(shí)最早是從數(shù)據(jù)庫系統(tǒng)中來的。
事務(wù)處理系統(tǒng)使應(yīng)用程序員能夠集中精力去編寫業(yè)務(wù)代碼,而不必關(guān)心事務(wù)管理的各種細(xì)節(jié)。
在 MySQL 等關(guān)系型數(shù)據(jù)庫中,事務(wù)是保證數(shù)據(jù)狀態(tài)一致性的一個(gè)重要手段。
在 MySQL 中,一個(gè)事務(wù)可能包含多條 SQL 語句的操作,這些語句作為一個(gè)整體要么都執(zhí)行成功,要么都執(zhí)行失敗。
MySQL 支持事務(wù)的存儲(chǔ)引擎有 InnoDB、NDB Cluster 等,其中 InnoDB 的使用最為廣泛,MyISAM 存儲(chǔ)引擎不支持事務(wù)。MySQL 服務(wù)層并不實(shí)現(xiàn)事務(wù)。
MySQL 事務(wù)處理使用下面語句:
START TRANSACTION/BEGIN :開啟一個(gè)事務(wù)
ROLLBACK : 回滾一個(gè)事務(wù)
COMMIT :提交事務(wù)
當(dāng)然我們也可以設(shè)置 MySQL 事務(wù)自動(dòng)提交模式:
SET AUTOCOMMIT=0?禁止自動(dòng)提交
SET AUTOCOMMIT=1?開啟自動(dòng)提交
MySQL事務(wù)ACID實(shí)現(xiàn)#
在 MySQL 中,分析下 InnoDB 存儲(chǔ)引擎中 ACID 的實(shí)現(xiàn)。
隔離性
隔離性是通過不同鎖機(jī)制、MVCC(多版本并發(fā)控制)來實(shí)現(xiàn)事務(wù)間的隔離,實(shí)現(xiàn)安全并發(fā)。MVCC 解決了不可重復(fù)讀,或者實(shí)現(xiàn)了可重復(fù)讀。還有的使用快照或結(jié)合快照。
MySQL 中有 4 種隔離級(jí)別,分別是?READ UNCOMMITTED(讀未提交)、READ COMMITTED(讀已提交)、REPEATABLE READ(可重復(fù)讀)、SERIALIZABLE(串行化)。
事務(wù)隔離級(jí)別是要解決什么問題?
臟讀
臟讀是指讀到了其他事務(wù)未提交的數(shù)據(jù)。未提交的數(shù)據(jù)意味著數(shù)據(jù)可能會(huì)回滾,也就是最終可能不會(huì)存到數(shù)據(jù)庫里,不存在的數(shù)據(jù),這就是臟讀。
可重復(fù)讀
在一個(gè)事務(wù)內(nèi),最開始讀到的數(shù)據(jù),和事務(wù)結(jié)束前任意時(shí)候讀到的同一批數(shù)據(jù)是一致的。這個(gè)通常針對數(shù)據(jù)更新操作。
不可重復(fù)讀
與上面的可重復(fù)讀形成對比,在同一事務(wù)內(nèi),不同的時(shí)刻讀到的同一批數(shù)據(jù)可能不一樣,因?yàn)檫@批數(shù)據(jù)可能會(huì)受到其它事務(wù)的影響。比如事務(wù)更改了這批數(shù)據(jù)并提交了。這個(gè)通常針對更新操作。
幻讀
幻讀是針對數(shù)據(jù)插入操作。
比如有 2 個(gè)事務(wù) A 和 B,事務(wù) A 對一批數(shù)據(jù)作了更改,但是未提交,此時(shí)事務(wù) B 插入了與事務(wù) A 更改前的數(shù)據(jù)記錄相同的記錄,并且事務(wù) B 先于 事務(wù) A 提交了,此時(shí),在事務(wù) A 中查詢,會(huì)發(fā)現(xiàn)剛剛更改的數(shù)據(jù)未起作用,看起來沒有修改過,但真實(shí)情況是,這是事務(wù) B 插入進(jìn)來的數(shù)據(jù),這種情況感覺讓用戶產(chǎn)生了幻覺,這就是幻讀。
事務(wù)隔離就是為解決上面提到的臟讀、不可重復(fù)讀、幻讀這 3 個(gè)問題, 下表對 4 種隔離級(jí)別對解決這 3 個(gè)問題,可以和不可以:
隔離級(jí)別臟讀不可重復(fù)讀幻讀讀未提交可能可能可能讀已提交不可能可能可能可重復(fù)讀不可能不可能可能串行化不可能不可能不可能
上圖說明:
可能?- 可能出現(xiàn)問題。比如 讀未提交 隔離級(jí)別可能出現(xiàn)臟讀問題。
不可能?- 表示不可能出現(xiàn)問題。比如 讀已提交 隔離級(jí)別不可能出現(xiàn)臟讀問題。
串行化的隔離級(jí)別最高,可以解決所有這 3 個(gè)問題 - 臟讀、不可重復(fù)讀、幻讀。其它隔離級(jí)別只能解決部分問題,甚至有的隔離級(jí)別都不能解決。
為下文作準(zhǔn)備:InnoDB 存儲(chǔ)引擎提供了兩種事務(wù)日志:redo log(重做日志)和 undo log(回滾日志)。MySQL Server 提供了 binlog日志。
原子性
undo log 保證事務(wù)的原子性。它記錄了事務(wù)開始前需要回滾的一些信息。事務(wù)失敗需要回滾時(shí),可以從 undo log 日志撤銷已經(jīng)執(zhí)行的 SQL,回滾就是一個(gè)反向操作,事務(wù)提交是正向操作 ->,回滾就是反向操作 <-。
持久性
持久性就是事務(wù)操作最終要持久化到數(shù)據(jù)庫中,持久性是由 內(nèi)存+redo log 來保證的,MySQL 的 InnoDB 存儲(chǔ)引擎,在修改數(shù)據(jù)的時(shí)候,會(huì)同時(shí)在內(nèi)存和 redo log 中記錄這次操作,宕機(jī)的時(shí)候可以從 redo log 中恢復(fù)數(shù)據(jù)。
redo 日志記錄了事務(wù) commit 后的數(shù)據(jù),用來恢復(fù)未寫入 data file 的已成功事務(wù)更新的數(shù)據(jù)。
如果上面原子性、隔離性、持久性都實(shí)現(xiàn)了,那么一致性也就實(shí)現(xiàn)了。
一個(gè)問題:redo log 和 undo log 區(qū)別是什么?
undo log 記錄了事務(wù)開始前的數(shù)據(jù)狀態(tài),記錄的是更新之前的值
redo log 記錄了事務(wù)完成后的數(shù)據(jù)狀態(tài),記錄的是更新之后的值
事務(wù)提交之前發(fā)生了崩潰,重啟后會(huì)通過 undo log 回滾事務(wù);事務(wù)提交之后發(fā)生了崩潰,重啟后會(huì)通過 redo log 恢復(fù)事務(wù)。
來看張圖:

上面就是對 MySQL 中 AID 的實(shí)現(xiàn)原理簡單介紹分析。