通過這一篇文章讓你徹底了解事務,文章當中提供了很多真實示例,供大家參考學習!
目錄
一、什么是事務?
事務 是一組操作的集合,它是一個不可分割的工作單位,事務會把所有的操作 作為一個整體一起向系統提交 或 撤銷操作請求,即這些操作要么同時成功,要么同時失敗。
在關系數據庫中,一個事務可以是一條SQL語句,或者一組SQL語句;
在JAVA當中,一個事務可以是一個接口,也可以是service層當中的一個方法;
舉例:張三給李四轉賬1000塊錢,張三銀行賬戶的錢減少1000,而李四銀行賬戶的錢要增加1000。 這一組操作就必須在一個事務的范圍內,要么都成功,要么都失敗。
正常情況:轉賬這個操作, 需要分為以下這么三步來完成
異常情況:轉賬這個操作, 也是分為以下這么三步來完成 , 在執行第三步是報錯了, 這樣就導致張三減少1000塊錢, 而李四的金額沒變, 這樣就造成了數據的不一致, 就出現問題了。
為了解決上述的問題,就需要通過數據的事務來完成,我們只需要 在業務邏輯執行之前開啟事務,執行完畢后提交事務。如果執行過程中報錯,則回滾事務,把數據恢復到事務開始之前的狀態 。
注意: 默認MySQL的事務是自動提交的,也就是說,當執行完一條DML語句時,MySQL會立即隱式的提交事務。
二、事務操作
光說不練肯定不行,下面我們通過sql來演示以上場景,首先演示沒有事務的情況,然后再演示通過事務來控制的情況。事務控制mysql當中有兩種方式,我們分別進行演示。
數據準備:
DROP TABLE IF EXISTS account;
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
NAME VARCHAR ( 10 ) COMMENT '姓名',
money DOUBLE ( 10, 2 ) COMMENT '余額'
) COMMENT '賬戶表';
INSERT INTO account ( NAME, money ) VALUES ( '張三', 2000 ),( '李四', 2000 );
1、沒有事務會出現什么場景?
(1) 測試正常情況
-- 1. 查詢張三余額
select * from account where name = '張三';
-- 2. 張三的余額減少1000
update account set money = money - 1000 where name = '張三';
-- 3. 李四的余額增加1000
update account set money = money + 1000 where name = '李四';
(2) 測試異常情況
UPDATE account set money = 2000;
-- 1. 查詢張三余額
select * from account where name = '張三';
-- 2. 張三的余額減少1000
update account set money = money - 1000 where name = '張三';
出錯了.... (這里是故意整的不加注釋,來模仿執行當前事務執行到中間報錯了)
-- 3. 李四的余額增加1000
update account set money = money + 1000 where name = '李四';
我們把數據都恢復到2000, 然后再次一次性執行上述的SQL語句(出錯了… 這句話不符合SQL語 法,執行就會報錯),檢查最終的數據情況, 發現數據在操作前后不一致了。
2、控制事務方式一(手動提交)
mysql當中的事務默認是自動提交,什么是自動提交?
所謂自動提交就是每執行一條sql就進行提交一次。而實際我們在開發當中,往往會出現上面類似業務,多條sql同時執行,還要保證要么都成功要么都失敗。而在java當中我們并沒有看到過什么commit提交事務,什么rollback回滾事務的相關sql,這是因為我們使用的持久層框架都進行了封裝,例如MyBatis默認就是手動提交事務。而他的commit和rollback全權由框架來管控。
什么是手動提交事務?
只要不執行commit,那么mysql會認為你所執行的sql都是在一個事務當中,當commit的時候,假如成功了就都成功了,一旦執行的sql有異常,就全部回滾!
SELECT @@autocommit ;
SET @@autocommit = 0 ;
COMMIT;
ROLLBACK;
注意:
- 上述的這種方式,我們是修改了事務的自動提交行為, 把默認的自動提交修改為了手動提交, 此時我們執行的DML語句都不會提交,需要手動的執行commit進行提交。
- SET @@autocommit = 0 ; 設置手動提交 是臨時生效(只對當前會話有效),一旦會話關閉就沒了。想要設置永久手動提交可以通過mysql的配置文件當中添加 autocommit=0 然后重啟即可。
什么是會話?
會話并不是指的客戶端連接,一個客戶端連接可以創建多個會話,navicate應該都用過,通過下圖應該會一目了然!下面兩個查詢界面就是兩個會話!
sql測試:
提交事務之后會發現數據根本沒修改成功,原因就是執行過程遇到了報錯,然后自動會進行回滾!
-- 先改為手動提交
SET @@autocommit = 0 ;
-- 1. 查詢張三余額
select * from account where name = '張三';
-- 2. 張三的余額減少1000
update account set money = money - 1000 where name = '張三';
出錯了.... (這里是故意整的不加注釋,來模仿執行當前事務執行到中間報錯了)
-- 3. 李四的余額增加1000
update account set money = money + 1000 where name = '李四';
-- 4. 提交事務
COMMIT;
3、控制事務方式二(通過命令開啟事務)
除了上面所說的改為手動提交,還有一種就是通過sql來指定我要創建一個事務,commit的時候就是事務結束當前事務的時候。
- 開啟事務: START TRANSACTION 或 BEGIN ;
- 提交事務: COMMIT;
- 回滾事務: ROLLBACK;
sql測試:
-- 開啟事務
start transaction
-- 1. 查詢張三余額
select * from account where name = '張三';
-- 2. 張三的余額減少1000
update account set money = money - 1000 where name = '張三';
-- 3. 李四的余額增加1000
update account set money = money + 1000 where name = '李四';
-- 如果正常執行完畢, 則提交事務
commit;
-- 如果執行過程中報錯, 則回滾事務(commit失敗的時候會自動執行回滾,不需要我們管)
-- rollback;
三、事務四大特性
事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱為 ACID特性 。
- 原子性(atomicity) 。一個事務是一個不可分割的工作單位,事務中包括的操作 要么都做,要么都不做 。
- 一致性(consistency) 。事務必須是使 數據庫從一個一致性狀態變到另一個一致性狀態 。一致性與原子性是密切相關的。
- 隔離性(isolation) 。一個事務的執行不能被其他事務干擾。即一個事務內部的操作和使用的數據對并發的其他事務是隔離的, 并發執行的各個事務之間不能互相干擾 。
- 持久性(durability) 。持久性也稱永久性(permanence),指一個事務一旦提交,它 對數據庫中數據的改變就應該是永久性的 。接下來的其他操作或故障不應該對其有任何影響。
假設我現在去ATM機轉賬,將我的建設銀行卡的1000塊錢轉到中國銀行的卡里去。程序可能是這樣執行的:
第一步:從建設銀行卡的余額里扣除1000;
第二步:然后再從中國銀行的卡的余額里增加1000。
假如在第一步執行完之后服務器宕機了,那么顯然第二步將無法完成,我的建設銀行卡被扣了1000,但中國銀行的卡卻沒增加,我將白白損失了1000塊錢。在數據庫中,這就是所謂的“不一致性狀態”。
那么如何實現“一致性”呢?
事實上,ACID中的AID都是為了實現C的。事務的最終目的就是為了實現“ 一致性 ”。如果轉賬的操作具有原子性,那么在中途出現錯誤的時候發生回滾,就不會出現不一致的情況,可見,“原子性”和“一致性”是緊密聯系在一起的!
四、事務的隔離性
這里再重點提一下ACID當中的 隔離性(isolation) 。
隔離性是針對數據資源的并發訪問,規定了 各個事務之間相互影響的程度 。
舉例:為了疫情防控,各個城市針對于每個人的出行情況,出了對應的隔離級別,例如低風險的回老家就是3天兩檢,中風險的就是隔離14天,特別嚴重的就是集中隔離21天,等等。。。
隔離級別就是為了防止出現問題,而定的規則!
而事務的隔離性也分為了4種類型的隔離級別,這些隔離級別專門用于應對 數據資源并發訪問 下不同問題的場景。(就好比上面的例子,每個隔離級別都是對應一個場景,三天兩檢就是對應的低風險)。
1、并發事務下會產生什么問題?
什么是并發事務?
并發事務就是多個客戶端同時開啟事務,或者是多個會話同時開啟事務!Java當中一個接口就是一個事務,也可以稱之為一次會話,接口當中包含了很多sql,當并發訪問接口的時候會出現什么問題,也被稱為并發事務問題。當然本篇主要以數據庫并發事務產生的問題為主!
隔離級別專門用于應對 數據資源并發訪問 下不同問題的場景,那么究竟有哪些問題呢?有三個:臟讀、不可重復讀、幻讀
- 臟讀: 讀到了還沒有提交事務的數據,簡稱讀未提交
舉例:如果一個事務B對數據進行了更改,但是還沒有提交,而另一個事務A就可以讀到事務B尚未提交的更新結果。這樣,當事務B進行回滾時,那么事務A開始讀到的數據就是一筆臟數據。
- 不可重復讀: 同一個事務在事務過程中,對同一個數據進行讀取操作,讀取到的結果不同。
舉例:事務 A 多次讀取同一數據,但事務 B 在事務A多次讀取的過程中,對數據作了更新并提交,導致事務A多次讀取同一數據時,結果 不一致。
- 幻讀: 一個事務按照條件查詢數據時,沒有對應的數據行,但是在插入數據時,又發現這行數據已經存在,好像出現了 “幻影”。
不可重復讀側重表達 讀-讀,幻讀則是說 讀-寫,用寫來證實讀的是鬼影。
如何避免:
- 要避免臟讀,需要控制在事務沒有提交更新前,其他事務無法看到此事務的更新結果。
- 要避免不可重復讀,假如查詢單條數據,可以通過行鎖,范圍查詢可以進行表鎖。
- 要避免幻讀,需要將整張表都鎖住了。
2、事務的隔離級別
上述所說的"臟讀",“不可重復讀”,"幻讀"這些問題,其實就是數據庫讀一致性問題,必須由數據庫提供的事務隔離機制來進行解決。
- 讀未提交(Read Uncommitted): 最低的隔離級別。一個事務可以讀取另一個事務沒有提交的更新結果。它是性能最好,也可以說它是最野蠻的方式,因為它壓根兒就不加鎖,所以根本談不上什么隔離效果,可以理解為沒有隔離。
- 讀已提交(Read Committed): 一個事務的更新操作只有在提交了之后,才會被另一個事務讀取到同一筆數據更新后的結果。
- 可重復讀(Repeatable Read): 在整個事務中,對同一筆數據的讀取結果是相同的,不管其他事務是否同時在對這筆數據進行更新,也不管這筆更新是否提交。
- 串行化(SERIALIZABLE): 串行化就相當于處理一個人請求的時候,別的人都等著。讀的時候加共享鎖,也就是其他事務可以并發讀,但是不能寫。寫的時候加排它鎖,其他事務不能并發寫也不能并發讀。
注意:
- MySQL默認事務隔離級別為可重復讀(RR),oracle默認事務隔離級別為讀已提交(RC)。
- MySQL存在幻讀,而oracle存在不可重復讀和幻讀的情況!
- 數據庫的事務隔離越嚴格,并發副作用越小,但付出的代價越大;
命令:
show variables like 't%_isolation';
select @@session.transaction_isolation;
select @@global.transaction_isolation;
注意:早期版本的mysql中用的變量名稱是tx_isolation,5.7.20版本之后,用的是transaction_isolation。
事務隔離級別分為會話和全局,會話在上面有提到過,一個客戶端可以有多個會話。會話一旦關閉,設置的就失效了。可以通過以下命令針對兩種進行修改, SESSION就是會話級別的 , GLOBAL全局的
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
如下示例,修改會話事務隔離級別為 READ UNCOMMITTED:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
(1)首先演示第一種:讀未提交(Read Uncommitted)
首先讀未提交會出現臟讀,那么我們就進行通過讀未提交來演示臟讀!
我們就還是以這兩條數據進行演示,其次我們要進行演示并發場景,就需要兩個客戶端,那么我們直接通過cmd打開兩個窗口 來進行連接mysql演示并發場景。
打開cmd通過命令進行連接: mysql -hlocalhost -u賬號 -p密碼
我的客戶端查出來的數據是亂碼的,應該是數據庫編碼有問題,但是這不影響我們演示
以下示例就是典型的臟讀,也就是讀取到了他沒有提交的數據,沒有提交就意味著數據并沒有存到磁盤,他很有可能會進行回滾,一旦回滾,而別的客戶端讀到了他沒提交的數據,而且還依賴這些臟數據做了別的操作,那將后果不堪設想!
(2)第二種:讀已提交(Read Committed)
讀已提交可以避免臟讀,這我們就不再測試了,想測試的可以按照上面的測試方法進行測試,看看是否會有以上問題!
讀已提交存在不可重復讀和幻讀的問題,同時也是oracle默認的隔離級別,我們重點演示不可重復讀!
其實仔細想想不可重復讀其實并不是很嚴重,他無非是在一個事務當中多次讀取可能值不一樣,但是他讀出來的都是提交過后的,也就意味著永遠是最新的數據,有時候未必是一件壞事!!!
(3)第三種:可重復讀(Repeatable Read)
可重復讀可以避免臟讀和不可重復讀情況,但是沒辦法避免幻讀,所以本次重點演示幻讀!
在測試這個的時候我們需要將id自增給關掉,自增的情況下不會出現這種情況!
新增的時候發現報錯,原因就是id為主鍵唯一,但是不是新增,在新增的時候實際上數據庫已經存在了id為3的數據,導致新增失敗,而他在這個事務當中不管怎么去查,也查不到id為3的數據,這就是幻覺!!!
說白了不可重復讀就是在當前事務當中,不管別的客戶端操作沒操作過數據,他查到的都是他開啟事務之前的數據。所以他根本不可能存在臟讀,也不可能存在不可重復讀。
(4)第四種:串行化(Serilizable)
讀的時候加共享鎖,也就是其他事務可以并發讀,但是不能寫。寫的時候加排它鎖,其他事務不能并發寫也不能并發讀。
五、本章總結
本篇文章要求掌握的:
- mysql當中事務默認是自動提交,可以改為手動提交
- 在一次事務當中,假如出現異常,事務會自動進行回滾
- 通過以上示例,如果還不知道事務是干什么的,建議多讀幾次,事務知識點太重要了,特別是學習后端的兄弟們!
- 數據庫當中,并發事務會引發什么問題?幻讀、不可重復讀、臟讀
- 隔離級別就是為了解決 并發事務可能會引發的問題 而誕生的,其中分為了四種級別,我們可以根據自己系統的情況進行自由選擇,事務隔離越嚴格,并發副作用越小,但付出的代價越大。正常開發當中一般都會采用默認的。
四種分別是:讀未提交、讀已提交、不可重復讀、串行化。 - 并發事務會引發什么問題,還有隔離級別 這兩個都是面試經常會問的!!!
原文鏈接:
https://blog.csdn.NET/weixin_43888891/article/details/126024380?utm_source=tuicool&utm_medium=referral