1. 前言
MySQL 中事務(wù)(Transaction)的定義是對于一個或者多個 SQL 語句,要么全部執(zhí)行成功,要么一個都不執(zhí)行成功。在實(shí)際應(yīng)用場景中,有很多需要事務(wù)的場景,例如在電商網(wǎng)站,顧客下單、付款以及商品扣減庫存就應(yīng)該在一個事務(wù)中執(zhí)行,如果不能保證事務(wù)特性,就可能出現(xiàn)用戶已經(jīng)下單并且成功付款,但是在扣減庫存邏輯出現(xiàn)異常,發(fā)貨失敗的情況。所以事務(wù)中的某個環(huán)節(jié)出現(xiàn)異常,之前執(zhí)行的所有 SQL 語句都應(yīng)該回滾。
2. 事務(wù)
2.1 事務(wù) ACID 特性
面試官提問: MySQL 中事務(wù)的特性是什么?
題目解析:
ACID 是衡量事務(wù)的 4 個維度,分別的定義是:
(1)原子性(Atomic,簡寫 A):原子性要求事務(wù)是一個不可分割的執(zhí)行單位,如果一個事務(wù)包含多條 SQL 語句,要么所有的 SQL 都執(zhí)行成功,要么所有的 SQL 都執(zhí)行失敗,不存在兩者之間的中間狀態(tài)。如果事務(wù)中的任意一條 SQL 執(zhí)行失敗,那么已執(zhí)行成功的也需要回滾。MySQL 中 InnoDB 引擎利用 undo log 實(shí)現(xiàn)原子性,undo log 記錄了所有已執(zhí)行的 SQL 記錄,如果事務(wù)執(zhí)行失敗調(diào)用了 rollback 語句,那么使用 undo log 的記錄回滾已執(zhí)行的 SQL。
(2)持久性(Durability,簡寫 D):持久性要求事務(wù)一旦提交(commit),對數(shù)據(jù)庫的改變就應(yīng)該是永久的,其他的操作不會對已提交的事務(wù)有影響。InnoDB 引擎中使用 redo log 實(shí)現(xiàn)持久性,如果 MySQl 服務(wù)器宕機(jī),那么在重啟時可以讀取 redo log 中的記錄恢復(fù)數(shù)據(jù)庫。
(3)隔離性(Isolation,簡寫 I):要求一個事務(wù)的執(zhí)行不受到其他并發(fā)執(zhí)行事務(wù)的影響。
(4)一致性(Consistency,簡寫 C):事務(wù)將數(shù)據(jù)庫從一種狀態(tài)轉(zhuǎn)換到另一種狀態(tài),但是兩種狀態(tài)從數(shù)據(jù)上是一致的。例如用戶下單扣庫存讓庫存減少了一個單位,那么在訂單中就會增加一個單位的商品,庫存和訂單中的商品數(shù)量和是不會改變的。
2.2 事務(wù)隔離級別
面試官提問:ACID 特性中的隔離性在 MySQL 中的具體定義是什么?
題目解析:
MySQL 提供了 4 種事務(wù)隔離級別,分別是:
(1)讀未提交(Read Uncommitted):所有事務(wù)可以看到其他事務(wù)未提交的執(zhí)行結(jié)果;
(2)讀已提交(Read Committed):所有事務(wù)只能看到其他事務(wù)已提交的執(zhí)行結(jié)果;
(3)可重復(fù)讀(Repeatable Read):MySQL 默認(rèn)的隔離級別,所有事務(wù)能看到其他事務(wù)已提交后的修改后數(shù)據(jù),但是如果第一次讀取到這個修改后的數(shù)據(jù),如果其他事務(wù)繼續(xù)修改了數(shù)據(jù)并且提交,這個事務(wù)讀到的也是第一次讀到的值,不會讀到修改后的新值。
(4)串行化(Serializable):最高隔離級別,可以理解為讓所有并發(fā)執(zhí)行的事務(wù)都進(jìn)入隊(duì)列,挨個串行執(zhí)行,永遠(yuǎn)不可能發(fā)生沖突。
我們關(guān)注事務(wù),關(guān)注點(diǎn)在于不同事務(wù)的并發(fā)沖突,而且重點(diǎn)在于讀寫操作。對于同一條數(shù)據(jù),在執(zhí)行并發(fā)事務(wù)時可能會產(chǎn)生讀寫上的問題,有三種:
(1)臟讀(Dirty Read):如果事務(wù) A 更新了一份數(shù)據(jù),比如將記錄 a 更新為記錄 b,那么在事務(wù) B 中讀取到的記錄是 b,此時事務(wù) A 進(jìn)行了回滾操作,記錄 b 回滾為記錄 a,那么事務(wù) B 讀到的記錄 b 則是非法數(shù)據(jù)。
(2)不可重復(fù)讀(Non-Repeatable Read):如果事務(wù) A 更新了一份數(shù)據(jù),比如將記錄 a 更新為記錄 b,那么在事務(wù) B 中讀取到的記錄是 b,此時事務(wù) A 繼續(xù)將記錄 b 更新為記錄 c,那么事務(wù) B 第二次讀到的記錄是 c,兩次讀取的結(jié)果不同。
(3)幻讀(Phantom Read):如果事務(wù) B 查詢到了幾行數(shù)據(jù),此時事務(wù) A 又插入了幾行新數(shù)據(jù),那么事務(wù) B 會讀到多出來的幾行數(shù)據(jù),讀到了上次讀取沒出現(xiàn)的數(shù)據(jù)。
4 種隔離級別對應(yīng)的問題應(yīng)對能力如下表:
隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|
讀未提交 | ? | ? | ? |
讀已提交 | ? | ? | ? |
可重復(fù)讀 | ? | ? | ? |
串行化 | ? | ? | ? |
從解決問題的能力上看,串行化能解決所有的并發(fā)讀寫問題,但是串行執(zhí)行效率太低,比如在電商網(wǎng)站的秒殺商品下單流程,就會導(dǎo)致所有的用戶需要等某一個用戶執(zhí)行完下單操作后才能繼續(xù)搶購,不具有實(shí)戰(zhàn)意義。MySQL 默認(rèn)的隔離級別是可重復(fù)讀,這個級別能解決臟讀和不可重復(fù)讀的問題,效率上相對比較快。讀未提交的執(zhí)行效率最高,但是數(shù)據(jù)的一致性保障最差, 一般不會在實(shí)戰(zhàn)中使用。
在 MySQL 客戶端執(zhí)行 show variables like 'transaction_isolation';
語句可查看隔離級別:
3. 小結(jié)
本小結(jié)概括了事務(wù)的 ACID 特性以及 4 種隔離級別的定義,候選人可以自行使用小樣本數(shù)據(jù)測試 MySQL 不同隔離級別下事務(wù)的讀寫區(qū)別。