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