1宏榕、概述
事務(wù)(Transaction)拓诸,在數(shù)據(jù)庫領(lǐng)域中可以理解成對數(shù)據(jù)操作的一個執(zhí)行單元侵佃。
事務(wù)是一個整體,由一條或者多條 SQL 語句組成奠支,這些 SQL 語句要么都執(zhí)行成功馋辈,要么都執(zhí)行失敗,只要有一條 SQL 出現(xiàn)異常胚宦,整個操作就會回滾首有,導(dǎo)致整個事務(wù)執(zhí)行失敗。
事務(wù)所涉及的 SQL 語句都是 DML 即數(shù)據(jù)操縱語句枢劝,像純 DQL 這樣的不涉及事務(wù)井联。
DML 涉及到數(shù)據(jù)的修改(增、刪您旁、改)烙常,而在計算機(jī)領(lǐng)域中,無可避免地會遇到并發(fā)鹤盒、網(wǎng)絡(luò)傳輸等來自各個方面的各種問題蚕脏,如果處理不當(dāng)會導(dǎo)致數(shù)據(jù)庫中的數(shù)據(jù)出現(xiàn)偏差,帶來很多麻煩侦锯。用事務(wù)的形式組織這些 DML驼鞭,如果事務(wù)中的所有語句都準(zhǔn)確無誤執(zhí)行,提交事務(wù)即最終確認(rèn)對數(shù)據(jù)的修改尺碰;而一旦其中任何一個語句出現(xiàn)問題挣棕,不提交事務(wù)并且需要恢復(fù)原狀。DQL 是查詢亲桥,僅進(jìn)行查詢操作不涉及數(shù)據(jù)修改洛心,所以沒有必要以事務(wù)的形式組織純 DQL。
明確一點题篷,只是說在僅進(jìn)行查詢操作的情況下词身,不用涉及事務(wù)。而不是說事務(wù)中不能有查詢操作番枚!事務(wù)中可以有查詢操作法严,但是需涉及到相應(yīng)的 DML 操作!
回滾:即在事務(wù)運行的過程中發(fā)生了某種故障葫笼,事務(wù)不能繼續(xù)執(zhí)行深啤,將事務(wù)中對數(shù)據(jù)庫的所有已完成的操作全部撤銷,滾回到事務(wù)開始時的狀態(tài)渔欢。(在提交之前執(zhí)行)
2墓塌、事務(wù)的典型案例
講數(shù)據(jù)庫的事務(wù)瘟忱,最經(jīng)典的案例就是銀行賬戶轉(zhuǎn)賬奥额。
有兩個銀行賬戶分別是 A 和 B苫幢,其中 A 賬戶中有1000元,B 賬戶中有200元垫挨,A 賬戶需向 B 賬戶轉(zhuǎn)賬300元韩肝。
假設(shè)銀行的數(shù)據(jù)庫用如下表存儲賬戶的金額信息:
CREATE TABLE tb (
id VARCHAR(10) PRIMARY KEY,
salary INT NOT NULL);
INSERT INTO tb (id,salary)
VALUES ('A',1000),('B',200);
- 進(jìn)行轉(zhuǎn)賬,對應(yīng)數(shù)據(jù)庫應(yīng)執(zhí)行的 SQL 語句:
-- 語句1
UPDATE tb
SET salary=salary-300
WHERE id='A';
-- 語句2
UPDATE tb
SET salary=salary+300
WHERE id='B';
針對上述的語句1和語句2九榔,如果兩個都正確執(zhí)行了哀峻,沒有問題;但是如果語句1或語句2中的任何一個語句在執(zhí)行過程中出現(xiàn)差錯哲泊,比如說剩蟀,成功執(zhí)行語句1后,因為網(wǎng)絡(luò)連接出現(xiàn)問題切威,語句2沒有成功執(zhí)行育特,最終,賬戶 B 沒有收到轉(zhuǎn)賬且賬戶 A 中還少了300元先朦。這樣的話缰冤,直接導(dǎo)致現(xiàn)實中的經(jīng)濟(jì)損失。所以喳魏,需要以數(shù)據(jù)庫的事務(wù)來組織上面的兩個 SQL 語句棉浸,一旦出現(xiàn)問題,終止事務(wù)刺彩,及時回滾迷郑,恢復(fù)原狀。
3迂苛、MySQL 中事務(wù)自動提交
MySQL 默認(rèn)將一條 DML 語句就作為一個事務(wù)看待三热,并且自動提交即自動確認(rèn)生效。
MySQL 中查看配置自動提交的變量:
SHOW VARIABLES LIKE 'autocommit';
- 將默認(rèn)自動提交設(shè)置為手動提交:
SET @@autocommit=OFF;
一旦經(jīng)過了上面的設(shè)置后三幻,如果希望執(zhí)行的 DML 語句生效就必須使用COMMIT
來手動提交事務(wù)就漾。
4、MySQL 中事務(wù)手動提交
在沒有將
autocommit
設(shè)置為OFF
之前念搬,也可以手動提交事務(wù)抑堡,前提是需要顯式開啟事務(wù)。開啟事務(wù)之后朗徊,其后面執(zhí)行的所有 DML 語句都不會立即生效首妖,直到提交事務(wù);如果出現(xiàn)問題爷恳,回滾事務(wù)有缆,恢復(fù)到開啟事務(wù)前的狀態(tài)。也就是說,事務(wù)的結(jié)局有兩種棚壁,要么被提交杯矩,要么被撤銷(回滾)即
COMMIT
與ROLLBACK
二選一。
-- 顯式開啟一個事務(wù)
-- 這個語句顯式地標(biāo)記一個事務(wù)的起始點
START TRANSACTION;
-- 或者直接使用
BEGIN;
-- DML
······
-- 表示提交事務(wù)袖外,即提交事務(wù)的所有操作使前面所有的 DML 生效史隆,具體地說,就是將事務(wù)中所有對數(shù)據(jù)庫的更新都寫到物理的磁盤上中曼验,事務(wù)正常結(jié)束泌射。
COMMIT;
-- 表示撤銷(回滾)事務(wù),即在事務(wù)運行的過程中發(fā)生了某種故障鬓照,事務(wù)不能繼續(xù)執(zhí)行熔酷,系統(tǒng)將事務(wù)中對數(shù)據(jù)庫的所有已完成的操作全部撤銷,回滾到事務(wù)開始之前的狀態(tài)豺裆。
ROLLBACK纯陨;
- 舉例:轉(zhuǎn)賬
CREATE TABLE my_tb1 (
id CHAR(1) PRIMARY KEY,
salary INT NOT NULL DEFAULT 0
);
INSERT INTO my_tb1 (id,salary)
VALUES ('A',1000),('B',300);
-- 事務(wù)成功,提交
START TRANSACTION;
UPDATE my_tb1
SET salary=salary-200
WHERE id='A';
UPDATE my_tb1
SET salary=salary+200
WHERE id='B';
COMMIT;
-- 事務(wù)失敗留储,回滾
START TRANSACTION;
UPDATE my_tb1
SET salary=salary-200
WHERE id='A';
UPDATE my_tb1
SET salary=salary+200
WHERE id='B';
ROLLBACK;
5翼抠、事務(wù)的特性
6、MySQL 事務(wù)隔離級別
根據(jù)事務(wù)的隔離性获讳,各個事務(wù)之間是相互獨立的阴颖,但是現(xiàn)實中往往會因為并發(fā)訪問等因素導(dǎo)致“隔離”出現(xiàn)問題,這個時候可以通過設(shè)置隔離級別來解決這些問題丐膝。
關(guān)于并發(fā)訪問等帶來的問題量愧,并不是僅在數(shù)據(jù)庫層面就能解決的;同樣的道理帅矗,在數(shù)據(jù)庫層面偎肃,也并不是只有設(shè)置隔離級別這一種方式解決問題。
并發(fā)訪問帶來的問題:
1浑此、臟讀:讀取到涉及未提交事務(wù)的數(shù)據(jù)累颂。讀取到的數(shù)據(jù)稱為“臟數(shù)據(jù)”。
比如:賬戶 A 向 賬戶 B 轉(zhuǎn)賬500元凛俱,賬戶 B 讀取到自己的賬戶余額中有500元紊馏,當(dāng) B 想用這些錢去購物結(jié)賬的時候,出現(xiàn)問題蒲犬,導(dǎo)致“轉(zhuǎn)賬”事務(wù)回滾朱监,此時 B 再次查看賬戶的時候,這500元沒有了原叮。
2赫编、不可重復(fù)讀:在一個事務(wù)中(大前提巡蘸,在一個事務(wù)中),對同一個數(shù)據(jù)擂送,多次讀取的結(jié)果不一致赡若。
3、幻讀:在一個事務(wù)中团甲,雖然是基于查詢到的結(jié)果設(shè)計并執(zhí)行 DML 但是出現(xiàn)問題,也就是查詢出的結(jié)果無法支撐后續(xù)的操作黍聂。
比如:希望通過一個事務(wù)向表中插入一條數(shù)據(jù)躺苦,查詢到表中沒有與之重復(fù)的數(shù)據(jù),但是執(zhí)行插入操作的時候产还,卻發(fā)現(xiàn)該數(shù)據(jù)已經(jīng)存在匹厘。
- MySQL 中事務(wù)隔離級別(由低到高):
最低級別:讀未提交(read uncommitted)
解釋:進(jìn)行查詢操作,未提交事務(wù)引發(fā)的數(shù)據(jù)修改的結(jié)果也能被查詢到脐区。
能規(guī)避的問題:無法規(guī)避任何一種問題
讀已提交(read committed)
解釋:進(jìn)行查詢操作愈诚,未提交事務(wù)引發(fā)的數(shù)據(jù)修改的結(jié)果不能被查詢到,只能查詢到已提交事務(wù)引發(fā)的數(shù)據(jù)修改的結(jié)果牛隅。
能規(guī)避的問題:臟讀
備注:Oracle 數(shù)據(jù)庫炕柔、Microsoft SQL Server 數(shù)據(jù)庫默認(rèn)使用的隔離級別。
可重復(fù)讀(repeatable read)
解釋:在“讀已提交”的基礎(chǔ)上媒佣,
能規(guī)避的問題:臟讀匕累、不可重復(fù)讀
備注:MySQL 數(shù)據(jù)庫默認(rèn)使用的隔離級別
最高級別:串行化(serializable)
解釋:
能規(guī)避的問題:臟讀、不可重復(fù)讀默伍、幻讀
- 事務(wù)隔離級別越高欢嘿,能夠規(guī)避的問題越多,數(shù)據(jù)的安全性越強(qiáng)但是讀寫效率會隨之降低也糊。需結(jié)合實際情況選擇合適的隔離級別炼蹦。另外,由并發(fā)帶來的問題狸剃,不僅僅需要在數(shù)據(jù)庫層面解決掐隐,在數(shù)據(jù)庫層面,也不只有隔離級別這一種解決方案钞馁。
6.1瑟枫、MySQL 查看與修改事務(wù)隔離級別
- 查看隔離級別
MySQL 中有兩類事務(wù)隔離級別,一類是全局事務(wù)隔離級別指攒,一類是當(dāng)前會話事務(wù)隔離級別慷妙。
全局事務(wù)隔離級別:對所有會話都生效;
當(dāng)前會話事務(wù)隔離級別:只對當(dāng)前會話有效允悦;
會話:與 MySQL 服務(wù)建立一個連接稱為一個會話膝擂。
-- 查看全局事務(wù)隔離級別
SELECT @@global.transaction_isolation;
-- 查看當(dāng)前會話事務(wù)隔離級別
SELECT @@transaction_isolation;
SELECT @@session.transaction_isolation;
-- 查看全局事務(wù)隔離級別
-- MySQL 8.0 版本無法使用虑啤,提示沒有這個值
SELECT @@global.tx_isolation;
-- 查看當(dāng)前會話事務(wù)隔離級別
-- MySQL 8.0 版本無法使用,提示沒有這個值
SELECT @@tx_isolation;
SELECT @@session.tx_isolation;
- 修改事務(wù)隔離級別
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
6.2架馋、“臟讀”演示
- 為了演示“臟讀”現(xiàn)象狞山,先將 MySQL 全局的事務(wù)隔離級別調(diào)整至
READ UNCOMMITTED
。
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
- 仍以轉(zhuǎn)賬為例叉寂。新(修改完隔離級別后必須重新建立會話才有效)打開兩個命令行窗口即兩個會話萍启,來模擬一種并發(fā)場景,一個稱為
Session 1
屏鳍,另一個稱為Session 2
勘纯。
1、在
Session 2
中開啟一個事務(wù)钓瞭,執(zhí)行“轉(zhuǎn)賬”操作驳遵,暫不提交:
2、此時山涡,在
Session 1
中執(zhí)行查詢操作堤结,查詢結(jié)果如下:可以看到,未提交的事務(wù)引起的數(shù)據(jù)變化也會被查詢到鸭丛。如果
Session 2
最終提交了事務(wù)竞穷,也沒什么影響,但是如果Session 2
因某種原因必須回滾事務(wù)并且Session 1
也沒有重新查詢確認(rèn)鳞溉,此時Session 1
出現(xiàn)的問題就是“臟讀”来庭,即讀到了“臟數(shù)據(jù)”。
6.3穿挨、“不可重復(fù)讀”演示
- 為了演示“不可重復(fù)讀”現(xiàn)象月弛,先將 MySQL 全局的事務(wù)隔離級別調(diào)整至
READ UNCOMMITTED
或READ COMMITTED
。
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
- 仍以轉(zhuǎn)賬為例科盛。新打開兩個命令行窗口即兩個會話帽衙,來模擬一種并發(fā)場景,一個稱為
Session 1
贞绵,另一個稱為Session 2
厉萝。
1、在
Session 2
中開啟事務(wù)榨崩,先執(zhí)行一次查詢:
2谴垫、在
Session 1
中開啟事務(wù),執(zhí)行“轉(zhuǎn)賬”母蛛,并提交:
3翩剪、在
Session 2
中已開啟的事務(wù)中再次執(zhí)行查詢:在同一個事務(wù)中,讀取到的數(shù)據(jù)不一樣彩郊,這就是“不可重復(fù)讀”前弯,可能會感到奇怪蚪缀,查詢到不一樣的不是很正常的嗎,在下面會按照上面的步驟演示“可重復(fù)讀”恕出。
- 演示“可重復(fù)讀”询枚,首先將事務(wù)隔離等級調(diào)整至
REPEATABLE READ
或SERIALIZABLE
,規(guī)避“不可重復(fù)讀”浙巫。下面與“不可重復(fù)讀”步驟一樣:
可重復(fù)讀:在一個事務(wù)中(大前提金蜀,在一個事務(wù)中),對同一個數(shù)據(jù)的畴,多次讀取的結(jié)果一致渊抄。
6.4、“幻讀”演示
演示“幻讀”苗傅,首先將事務(wù)隔離等級調(diào)整至
READ UNCOMMITTED
或READ COMMITTED
或REPEATABLE READ
。以添加賬戶為例班巩。新打開兩個命令行窗口即兩個會話渣慕,來模擬一種并發(fā)場景,一個稱為
Session 1
抱慌,另一個稱為Session 2
逊桦。
1、在
Session 1
和Session 2
中均開啟事務(wù)抑进。
2强经、Session 1
準(zhǔn)備向賬戶中添加一個id
(主鍵)為 C 的賬戶,所以寺渗,先查詢一下是否存在id
為 C 的賬戶匿情。3、
Session 2
在Session 1
通過查詢確定后要插入數(shù)據(jù)之前信殊,插入了一個id
為 C 的數(shù)據(jù)炬称,并提交事務(wù)。4涡拘、等
Session 1
在插入數(shù)據(jù)時玲躯,發(fā)現(xiàn)出現(xiàn)錯誤,無法將數(shù)據(jù)插入鳄乏。
避免幻讀跷车,需要將隔離級別提升到最高的
SERIALIZABLE
。一旦提示到這個級別橱野,意味著朽缴,在一個事務(wù)中執(zhí)行 DML 但未COMMIT
或未ROLLBACK
的時候,其他事務(wù)進(jìn)行的所有操作(包括查詢)都將被“阻塞”水援,直到那個事務(wù)執(zhí)行了COMMIT
或ROLLBACK
不铆。
SERIALIZABLE
級別基本上是不去使用的蝌焚,因為效率太低。還是那句話誓斥,因并發(fā)導(dǎo)致的問題只洒,不應(yīng)只在數(shù)據(jù)庫層面解決,即使在數(shù)據(jù)庫層面劳坑,也不止設(shè)置隔離級別這一種方案毕谴。