MySQL事務(wù)保證數(shù)據(jù)一致性的核心講解
前言
在后端開發(fā)中,你是否遇到過這樣的糟心場景?用戶支付成功,余額扣了但訂單卻沒生成;批量導(dǎo)入數(shù)據(jù)時(shí)突然斷電,重啟后部分?jǐn)?shù)據(jù)重復(fù)、部分?jǐn)?shù)據(jù)丟失……這些“數(shù)據(jù)翻車”的背后,幾乎都藏著一個(gè)共同的漏洞——沒用好 MySQL 事務(wù)。
如果把數(shù)據(jù)庫比作一個(gè)繁忙的倉庫,那數(shù)據(jù)操作就是工人搬運(yùn)貨物的過程。要是搬運(yùn)到一半突然停電,貨物扔在半路、貨架空空如也,整個(gè)倉庫就會陷入混亂。而 MySQL 事務(wù),就是給這個(gè)搬運(yùn)過程裝了一道“安全鎖”:要么把所有貨物完好地搬到位,要么一旦出問題就退回原點(diǎn),絕不留下半拉子工程。今天我們就扒透事務(wù)的底層邏輯,教你用它把數(shù)據(jù)安全牢牢握在手里。
一、事務(wù)的概念以及使用場景
事務(wù)把一組SQL語句打包成為一個(gè)整體,在這組SQL的執(zhí)行過程中,要么全部成功,要么全部失敗。這組SQL語句可以是一條也可以是多條。
以一個(gè)轉(zhuǎn)賬例子為例:

初始狀態(tài):

先進(jìn)行張三余額減少100
UPDATE bank_account set balance = balance - 100 where name = '張三';

在進(jìn)行李四余額增加100
UPDATE bank_account set balance = balance + 100 where name = '李四';

如果轉(zhuǎn)賬成功,應(yīng)該有以下結(jié)果:
- 張三的賬戶余額減少 100 ,變成 900 ,李四的賬戶余額增加了 100 ,變成 1100 ,不能出現(xiàn)張三的余額減少而李四的余額沒有增加的情況;
- 張三和李四在發(fā)生轉(zhuǎn)賬前后的總額不變,也就是說轉(zhuǎn)賬前張三和李四的余額總數(shù)為1000+1000=2000 ,轉(zhuǎn)賬后他們的余額總數(shù)為 900+1100=2000 ;
- 轉(zhuǎn)賬后的余額結(jié)果應(yīng)當(dāng)保存到存儲介質(zhì)中,以便以后讀??;
- 還有一點(diǎn)需要要注意,在轉(zhuǎn)賬的處理過程中張三和李四的余額不能因其他的轉(zhuǎn)賬事件而受到干擾;
這四點(diǎn)在事務(wù)的整個(gè)執(zhí)行過程中必須要得到保證,這也就是事務(wù)的 ACID 特性
二、事務(wù)的ACID特性
數(shù)據(jù)安全的“四梁八柱”
事務(wù)的ACID特性指的是 Atomicity (原子性), Consistency (一致性), Isolation (隔離性) 和 Durability (持久性)。
1. Atomicity (原子性)
一個(gè)事務(wù)中的所有操作,要么全部成功,要么全部失敗,不會出現(xiàn)只執(zhí)行了一半的情況,如果事務(wù)在執(zhí)行過程中發(fā)生錯誤,會回滾( Rollback )到事務(wù)開始前的狀態(tài),就像這個(gè)事務(wù)從來沒有執(zhí)行過一樣;
2. Consistency (一致性)
在事務(wù)開始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫的完整性不會被破壞。這表示寫入的數(shù)據(jù)必須完全符合所有的預(yù)設(shè)規(guī)則,包括數(shù)據(jù)的精度、關(guān)聯(lián)性以及關(guān)于事務(wù)執(zhí)行過程中服務(wù)器崩潰后如何恢復(fù);
3. Isolation (隔離性)
數(shù)據(jù)庫允許多個(gè)并發(fā)事務(wù)同時(shí)對數(shù)據(jù)進(jìn)行讀寫和修改,隔離性可以防止多個(gè)事務(wù)并發(fā)執(zhí)?時(shí)由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致。事務(wù)可以指定不同的隔離級別,以權(quán)衡在不同的應(yīng)?場景下數(shù)據(jù)庫性能和安全;
4. Durability (持久性)
事務(wù)處理結(jié)束后,對數(shù)據(jù)的修改將永久的寫入存儲介質(zhì),即便系統(tǒng)故障也不會丟失
三、為什么要使用事務(wù)
事務(wù)具備的ACID特性,是我們使用事務(wù)的原因,在我們?nèi)粘5臉I(yè)務(wù)場景中有大量的需求要用事務(wù)來保證。支持事務(wù)的數(shù)據(jù)庫能夠簡化我們的編程模型, 不需要我們?nèi)タ紤]各種各樣的潛在錯誤和并發(fā)問題,在使用事務(wù)過程中,要么提交,要么回滾,不用去考慮網(wǎng)絡(luò)異常,服務(wù)器宕機(jī)等其他因素,因此我們經(jīng)常接觸的事務(wù)本質(zhì)上是數(shù)據(jù)庫對 ACID 模型的一個(gè)實(shí)現(xiàn),是為應(yīng)用層服務(wù)的。
四、那么該如何使用事務(wù)呢
要使用事務(wù)那么數(shù)據(jù)庫就要支持事務(wù),在MySQL中支持事務(wù)的存儲引擎是InnoDB,可以通過show engines; 語句查看:

語法:
開始一個(gè)新的事務(wù)
START TRANSACTION; # 或 BEGIN;
提交當(dāng)前事務(wù),并對更改持久化保存
COMMIT;
回滾當(dāng)前事務(wù),取消其更改
ROLLBACK;
• START TRANSACTION 或 BEGIN 開始?個(gè)新的事務(wù);
• COMMIT 提交當(dāng)前事務(wù),并對更改持久化保存;
• ROLLBACK 回滾當(dāng)前事務(wù),取消其更改;
• 無論提交還是回滾,事務(wù)都會關(guān)閉
開啟一個(gè)事務(wù),執(zhí)行修改后回滾
開啟事務(wù)

表中數(shù)據(jù)

執(zhí)行sql使張三余額減少100

執(zhí)行sql使李四余額增加100

發(fā)現(xiàn)此時(shí)的余額相比之前已經(jīng)修改了
執(zhí)行回滾操作

發(fā)現(xiàn)回到了初始狀態(tài)值
開啟?個(gè)事務(wù),執(zhí)行修改后提交
開啟事務(wù),先查看表中的初始值

再次執(zhí)行張三減100,李四加100操作


進(jìn)行事務(wù)提交

再查詢發(fā)現(xiàn)數(shù)據(jù)已被修改,說明數(shù)據(jù)已經(jīng)持久化到磁盤

在事務(wù)執(zhí)行的過程中設(shè)置保存點(diǎn),回滾時(shí)指定保存點(diǎn)可以把數(shù)據(jù)恢復(fù)到保存點(diǎn)的狀態(tài)
在次進(jìn)行開啟事務(wù)并對張三減100,李四加100操作

我們來設(shè)置保存點(diǎn),看看最終效果如何
SAVEPOINT savepoint1;
對張三減100,李四加100操作

設(shè)置第?個(gè)保存點(diǎn)
SAVEPOINT savepoint2;
插入一條新記錄
insert into bank_account values (null, '王五', 1000);

回滾到第二個(gè)保存點(diǎn)
ROLLBACK TO savepoint2;
發(fā)現(xiàn)回滾到了插入王五數(shù)據(jù)前

回滾到第?個(gè)保存點(diǎn)

當(dāng)回滾時(shí)不指定保存點(diǎn),直接回滾到事務(wù)開始時(shí)的原始狀態(tài),事務(wù)關(guān)閉

自動/手動提交事務(wù)
查看當(dāng)前事務(wù)是否自動提交可以使?以下語句
show variables like 'autocommit';

設(shè)置事務(wù)自動提交
mysql> SET AUTOCOMMIT=1; 方式一
mysql> SET AUTOCOMMIT=ON; 方式二
設(shè)置事務(wù)手動提交
mysql> SET AUTOCOMMIT=0; 方式一
mysql> SET AUTOCOMMIT=OFF;方式二
•只要使用START TRANSACTION 或 BEGIN 開啟事務(wù),必須要通過 COMMIT 提交才會持久化,與是否設(shè)置 SET autocommit 無關(guān)。
• 手動提交模式下,不用顯示開啟事務(wù),執(zhí)行修改操作后,提交或回滾事務(wù)時(shí)直接使用 commit或 rollback
• 已提交的事務(wù)不能回滾
五、進(jìn)階
隔離級別——解決“并發(fā)事務(wù)”的沖突
MySQL服務(wù)可以同時(shí)被多個(gè)客戶端訪問,每個(gè)客戶端執(zhí)行的DML語句以事務(wù)為基本單位,那么不同的客戶端在對同一張表中的同一條數(shù)據(jù)進(jìn)行修改的時(shí)候就可能出現(xiàn)相互影響的情況,為了保證不同的事務(wù)之間在執(zhí)行的過程中不受影響,那么事務(wù)之間就需要要相互隔離,這種特性就是隔離性。
隔離性是為了解決“多個(gè)事務(wù)同時(shí)執(zhí)行”的沖突。但“隔離”的程度不同,性能和安全性也會有差異。MySQL 提供了 4 種隔離級別,從低到高分別是:
| 隔離級別 | 可能出現(xiàn)的問題 | 適用場景 |
|---|---|---|
| 讀未提交(Read Uncommitted) | 臟讀、不可重復(fù)讀、幻讀 | 幾乎不用(安全性太低) |
| 讀已提交(Read Committed) | 不可重復(fù)讀、幻讀 | 大多數(shù)互聯(lián)網(wǎng)場景(如電商訂單查詢) |
| 可重復(fù)讀(Repeatable Read) | 幻讀(MySQL 已通過間隙鎖解決) | 金融、支付等對數(shù)據(jù)一致性要求高的場景 |
| 串行化(Serializable) | 無(完全隔離) | 極少用(性能極低,適合單線程操作) |
事務(wù)的隔離級別分為全局作用域和會話作用域,查看不同作用域事務(wù)的隔離級別,可以使用以下的方式:
全局作用域
SELECT @@GLOBAL.transaction_isolation;
會話作用域
SELECT @@SESSION.transaction_isolation;
可以看到默認(rèn)的事務(wù)隔離級別是REPEATABLE-READ(可重復(fù)讀)

設(shè)置事務(wù)的隔離級別和訪問模式,可以使用以下語法:
通過GLOBAL|SESSION分別指定不同作用域的事務(wù)隔離級別
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level|access_mode;
# 隔離級別
level: {
REPEATABLE READ # 可重復(fù)讀
| READ COMMITTED # 讀已提交
| READ UNCOMMITTED # 讀未提交
| SERIALIZABLE # 串?化
}
# 訪問模式
access_mode: {
READ WRITE # 表?事務(wù)可以對數(shù)據(jù)進(jìn)?讀寫
| READ ONLY
}
# ?式? SET GLOBAL transaction_isolation = 'SERIALIZABLE'; # 注意使?SET語法時(shí)有空格要?"-"代替 SET SESSION transaction_isolation = 'REPEATABLE-READ'; # ?式? SET @@GLOBAL.transaction_isolation='SERIALIZABLE'; # 注意使?SET語法時(shí)有空格要?"-"代替 SET @@SESSION.transaction_isolation='REPEATABLE-READ';
不同隔離級別存在的問題:
READ UNCOMMITTED - 讀未提交與臟讀
出現(xiàn)在事務(wù)的 READ UNCOMMITTED 隔離級別下,由于在讀取數(shù)據(jù)時(shí)不做任何限制,所以并發(fā)性能很高,但是會出現(xiàn)大量的數(shù)據(jù)安全問題,比如在事務(wù)A中執(zhí)行了?條 INSERT 語句,在沒有執(zhí)行COMMIT 的情況下,會在事務(wù)B中被讀取到,此時(shí)如果事務(wù)A執(zhí)行回滾操作,那么事務(wù)B中讀取到事務(wù)A寫入的數(shù)據(jù)將沒有意義,我們把這個(gè)理象叫做 “臟讀”.
臟讀:事務(wù)A修改了工資(但沒提交),事務(wù)B讀到了這個(gè)“未提交的修改”;之后事務(wù)A回滾,事務(wù)B讀到的就是“臟數(shù)據(jù)”(比如A把工資從5000改成8000,B看到8000,A回滾后,B以為工資還是8000)。
READ COMMITTED - 讀已提交與不可重復(fù)讀
為了解決臟讀問題,可以把事務(wù)的隔離級別設(shè)置為 READ COMMITTED ,這時(shí)事務(wù)只能讀到了其他事務(wù)提交之后的數(shù)據(jù),但會出現(xiàn)不可重復(fù)讀的問題,比如事務(wù)A先對某條數(shù)據(jù)進(jìn)?了查詢,之后事務(wù)B對這條數(shù)據(jù)進(jìn)行了修改,并且提交( COMMIT )事務(wù),事務(wù)A再對這條數(shù)據(jù)進(jìn)行查詢時(shí),得到了事務(wù)B修改之后的結(jié)果,這導(dǎo)致了事務(wù)A在同一個(gè)事務(wù)中以相同的條件查詢得到了不同的值,這個(gè)現(xiàn)象要"不可重復(fù)讀"。
不可重復(fù)讀:事務(wù)B第一次查工資是5000,事務(wù)A修改工資為8000并提交;事務(wù)B再次查工資,變成了8000——同一事務(wù)內(nèi),兩次讀的結(jié)果不一致。
REPEATABLE READ - 可重復(fù)讀與幻讀
為了解決不可重復(fù)讀問題,可以把事務(wù)的隔離級別設(shè)置為 REPEATABLE READ ,這時(shí)同一個(gè)事務(wù)中讀取的數(shù)據(jù)在任何時(shí)候都是相同的結(jié)果,但還會出現(xiàn)?個(gè)問題,事務(wù)A查詢了一個(gè)區(qū)間的記錄得到結(jié)果集A,事務(wù)B向這個(gè)區(qū)間的間隙中寫入了一條記錄并提交,事務(wù)A再查詢這個(gè)區(qū)間的結(jié)果集時(shí)會查到事務(wù)B新寫入的記錄得到結(jié)果集B,兩次查詢的結(jié)果集不一致,這個(gè)現(xiàn)象就是"幻讀"。
幻讀:事務(wù)B查詢“工資>5000的員工數(shù)”是10人,事務(wù)A新增了一個(gè)工資6000的員工并提交;事務(wù)B再次查詢,結(jié)果變成11人——像出現(xiàn)了“幻覺”。
注意:隔離級別越高,數(shù)據(jù)越安全,但并發(fā)性能越低。比如“串行化”會把事務(wù)變成“排隊(duì)執(zhí)行”,雖然不會有任何沖突,但在高并發(fā)場景(如秒殺)下,會直接導(dǎo)致系統(tǒng)卡死——所以千萬別盲目追求“最高隔離級別”。

六、避坑指南
這些事務(wù)“陷阱”一定要避開
即使理解了 ACID 和隔離級別,實(shí)際開發(fā)中還是容易踩坑。分享 3 個(gè)最常見的陷阱:
1. 陷阱1
把“非事務(wù)安全表”當(dāng)事務(wù)表用
MySQL 中,只有 InnoDB 引擎支持事務(wù),MyISAM、MEMORY 等引擎不支持事務(wù)。如果你的表是 MyISAM 引擎,就算寫了 BEGIN TRANSACTION 和 COMMIT,也不會有任何事務(wù)效果——執(zhí)行到一半失敗,數(shù)據(jù)照樣會“翻車”。
解決辦法:創(chuàng)建表時(shí)明確指定 InnoDB 引擎:
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
balance INT NOT NULL
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
2. 陷阱2
在事務(wù)中執(zhí)行“非事務(wù)操作”
比如在事務(wù)里執(zhí)行 DROP TABLE、ALTER TABLE 等 DDL 語句——這些語句會自動提交事務(wù),導(dǎo)致之前的操作被強(qiáng)制 COMMIT,后續(xù)的 ROLLBACK 失效。
例子:
START TRANSACTION; UPDATE user SET balance = balance - 100 WHERE id = 1; -- 第一步 ALTER TABLE user ADD COLUMN phone VARCHAR(20); -- 執(zhí)行DDL,自動提交事務(wù) UPDATE user SET balance = balance + 100 WHERE id = 2; -- 這步會開啟新事務(wù) ROLLBACK; -- 只能回滾第二步,第一步的“減100”已經(jīng)被提交,無法回滾
解決辦法:DDL 語句不要放在事務(wù)里執(zhí)行;如果必須執(zhí)行,先確保當(dāng)前沒有未提交的事務(wù)。
3. 陷阱3
事務(wù)過大,導(dǎo)致鎖等待或死鎖
如果一個(gè)事務(wù)包含大量操作(比如批量更新10萬條數(shù)據(jù)),會導(dǎo)致事務(wù)執(zhí)行時(shí)間過長,占用數(shù)據(jù)庫鎖的時(shí)間也變長——其他事務(wù)需要等待這個(gè)鎖釋放,容易出現(xiàn)“鎖等待超時(shí)”;如果兩個(gè)事務(wù)互相等待對方的鎖,還會導(dǎo)致“死鎖”。
解決辦法:
- 拆分大事務(wù):把“批量更新10萬條”拆成100次“每次更新1000條”,每次執(zhí)行完一個(gè)小事務(wù)就提交;
- 避免長事務(wù):事務(wù)中不要包含用戶輸入、網(wǎng)絡(luò)請求等耗時(shí)操作(比如在事務(wù)里調(diào)用第三方支付接口,接口卡30秒,事務(wù)就會鎖30秒)。 七:總結(jié):事務(wù)不是“銀彈”,但沒有事務(wù)萬萬不能
MySQL 事務(wù)不是“萬能藥”,它不能解決所有數(shù)據(jù)問題(比如硬件物理損壞需要靠備份恢復(fù)),但它是保障“數(shù)據(jù)一致性”的基礎(chǔ)——沒有事務(wù),任何涉及多步操作的數(shù)據(jù)場景,都可能出現(xiàn)“翻車”風(fēng)險(xiǎn)。
最后,用三句話總結(jié)事務(wù)的核心用法:
- 牢記 ACID:原子性保障“不丟步”,一致性保障“不違規(guī)”,隔離性保障“不沖突”,持久性保障“不丟失”;
- 選對隔離級別:默認(rèn)用“可重復(fù)讀”,互聯(lián)網(wǎng)場景可降為“讀已提交”,金融場景不升為“串行化”;
- 避開常見陷阱:用 InnoDB 引擎,不混放 DDL 語句,拆分大事務(wù)。
掌握了這些,你就能讓數(shù)據(jù)從“脆弱易翻車”變得“穩(wěn)如泰山”,再也不用為“錢扣了沒到賬”“庫存超賣”這類問題頭疼了!
以上就是MySQL事務(wù)保證數(shù)據(jù)一致性的核心講解的詳細(xì)內(nèi)容,更多關(guān)于MySQL數(shù)據(jù)一致性的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mysql覆蓋表中重復(fù)記錄的兩種實(shí)現(xiàn)方式
replaceinto和onduplicatekeyupdate這兩種方式都需要表中存在主鍵或者唯一索引,前者會先刪除再插入,后者則直接更新,如果沒有主鍵或唯一索引,則兩者都相當(dāng)于insertinto操作2026-04-04
MySQL8.0 Redo Log 歸檔與禁用實(shí)戰(zhàn)指南
MySQL8.0新增的Redo Log歸檔與禁用功能,分別解決了備份時(shí)日志丟失問題和數(shù)據(jù)導(dǎo)入時(shí) IO 開銷問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-04-04
MySQL UNION操作符基礎(chǔ)知識點(diǎn)
在本文里小編給大家整理了關(guān)于MySQL UNION操作符的相關(guān)知識點(diǎn)內(nèi)容,需要的朋友們跟著學(xué)習(xí)下。2019-02-02
MySQL中Innodb的事務(wù)隔離級別和鎖的關(guān)系的講解教程
這篇文章主要介紹了MySQL中Innodb的事務(wù)隔離級別和鎖的關(guān)系講解教程,來自于美團(tuán)技術(shù)團(tuán)隊(duì)的經(jīng)驗(yàn)實(shí)際經(jīng)驗(yàn)分享,需要的朋友可以參考下2015-11-11
MySQL Flink Watermark實(shí)現(xiàn)事件時(shí)間處理的關(guān)鍵技術(shù)
Flink Watermark是Flink流處理框架中實(shí)現(xiàn)事件時(shí)間處理的關(guān)鍵技術(shù)之一。它是通過一種機(jī)制來解決數(shù)據(jù)亂序和延遲的問題,使得Flink可以在處理遲到的數(shù)據(jù)時(shí)保證結(jié)果的準(zhǔn)確性。Flink Watermark可以在數(shù)據(jù)流中插入一個(gè)時(shí)間戳,將數(shù)據(jù)流轉(zhuǎn)化為具有時(shí)間維度的數(shù)據(jù)集合2023-05-05

