Hibernate 性能之事務(wù)與并發(fā)
1. 前言
本節(jié)課和大家一起聊一聊事務(wù)和并發(fā)。通過(guò)本節(jié)課程的學(xué)習(xí),你將了解到:
- 什么是事務(wù) ;
- 事務(wù)的隔離機(jī)制。
2. 事務(wù)
什么是事務(wù)?
一講到事務(wù),就有一個(gè)典型的案例:轉(zhuǎn)賬。
轉(zhuǎn)賬業(yè)務(wù)涉及到 2 個(gè)賬號(hào)的變更。例如 A 現(xiàn)在要把自己賬戶上僅有的 100 元大鈔轉(zhuǎn)給自己的好朋友 B。
轉(zhuǎn)賬過(guò)程中至少要涉及到兩條更新語(yǔ)句:
- A 賬面上的錢(qián)減少,使用更新語(yǔ)句實(shí)現(xiàn):
Update 賬號(hào)表 set 我的錢(qián)=我的錢(qián)-100 where 賬號(hào)擁有人=A
- B 賬戶上的錢(qián)增多,使用更新語(yǔ)句實(shí)現(xiàn):
Update 賬號(hào)表 set 我的錢(qián)=我的錢(qián)+100 where 賬號(hào)擁有人=B
如果、萬(wàn)一這兩條更新語(yǔ)句中有一條沒(méi)有執(zhí)行成功,會(huì)發(fā)生什么情況了?
-
發(fā)生在 A 身上的更新語(yǔ)句沒(méi)有成功,B 的更新語(yǔ)句成功了。也就是說(shuō) A 賬面上的錢(qián)沒(méi)有減少,B 賬面上的錢(qián)卻增加了。天呀!這是何等好事,關(guān)鍵是這錢(qián)是從哪里來(lái)的,左想右想,看來(lái)只能是銀行里來(lái)的,但是,你覺(jué)得銀行會(huì)做這種傻事嗎?不會(huì)!那銀行又是如何保證不讓這種事情發(fā)生了。
-
發(fā)生在 B 身上的更新語(yǔ)句沒(méi)有成功,A 的更新語(yǔ)句成功了。也就是說(shuō) A 的錢(qián)減少了,但是 B 沒(méi)有增加。傻眼了吧,莫名其妙的錢(qián)就不見(jiàn)了。錢(qián)去哪兒了?你會(huì)讓這種事情發(fā)生嗎?也不會(huì),你會(huì)投訴銀行的。
當(dāng)然,如果這 2 條 SQL 語(yǔ)句執(zhí)行成功或者失敗,則不會(huì)發(fā)生任何損失,可見(jiàn),咱們必須控制這兩條 SQL 語(yǔ)句要么都成功,要么都失敗。
對(duì)!這就是事務(wù)。
所謂事務(wù),就是把一個(gè)業(yè)務(wù)邏輯當(dāng)成一個(gè)邏輯整體單元,其中的執(zhí)行代碼要么一起成功,但凡執(zhí)行過(guò)程中出現(xiàn)了某些錯(cuò)誤,就恢復(fù)到最原始的狀態(tài)。
轉(zhuǎn)賬就是一個(gè)業(yè)務(wù)邏輯,整個(gè)業(yè)務(wù)邏輯中至少包括 2 條 SQL 語(yǔ)句,這 2 條 SQL 語(yǔ)句互為依靠,彼此脫單對(duì)業(yè)務(wù)邏輯沒(méi)有任何意義。所以,必須當(dāng)成一個(gè)整體看待。
一個(gè)事務(wù)單元有 4 個(gè)特性,也就是事務(wù)的 ACID 特性:
- 原子性(atomicity): 表示一個(gè)事務(wù)內(nèi)的所有操作是一個(gè)整體,要么全部成功,要么全部失敗;
- 一致性(consistency): 表示一個(gè)事務(wù)內(nèi)有一個(gè)操作失敗時(shí),所有的更改過(guò)的數(shù)據(jù)都必須回滾到修改前的狀態(tài);如前面轉(zhuǎn)賬案例,轉(zhuǎn)賬前 A 和 B 加起來(lái)有多少錢(qián),無(wú)論轉(zhuǎn)賬是否成功,最后 A 和 B 加起來(lái)的錢(qián)應(yīng)該和前面相等;
- 隔離性(isolation): 事務(wù)查看數(shù)據(jù)時(shí)數(shù)據(jù)所處的狀態(tài),要么是另一并發(fā)事務(wù)修改它之前的狀態(tài),要么是另一事務(wù)修改它之后的狀態(tài),事務(wù)不會(huì)查看中間狀態(tài)的數(shù)據(jù);
- 持久性(durability): 事務(wù)完成之后,它對(duì)于系統(tǒng)的影響是永久性的。事務(wù)結(jié)束后,就沒(méi)有什么后悔藥了。
4 個(gè)特性中除了隔離性都比較好理解。所以,剩下的篇幅中,咱們好好聊一聊隔離性。要講透隔離性,肯定就離不開(kāi)并發(fā)概念。
什么是并發(fā)?
并發(fā)是計(jì)算機(jī)中的一個(gè)概念,但是在現(xiàn)實(shí)生活中還是能找到一個(gè)能類(lèi)比的例子。
火車(chē)上,2個(gè)人同時(shí)去一間洗手間,這個(gè)過(guò)程可以稱(chēng)其為并發(fā)。換成計(jì)算機(jī)概念就是說(shuō)兩段邏輯代碼同時(shí)使用同一個(gè)資源。當(dāng)然,這里只是從宏觀上理解并發(fā)。
老公和老婆共用一個(gè)賬號(hào),大家需要錢(qián)時(shí)都從這個(gè)賬號(hào)上取,可假設(shè)老公和老婆就是兩個(gè)事務(wù),這 2 個(gè)事務(wù)如果同時(shí)取錢(qián)時(shí),就必須隔離,否則就會(huì)出現(xiàn)麻煩。
2 個(gè)事務(wù)同時(shí)進(jìn)入這個(gè)賬號(hào),一查看,很高興,賬面上有錢(qián),于是都想取出這 1000 塊錢(qián)。銀行肯定只能讓一個(gè)事務(wù)成功,要不然銀行就虧大了。
這便是事務(wù)的隔離機(jī)制,當(dāng)一個(gè)事務(wù)對(duì)一個(gè)資源進(jìn)行操作時(shí),必須隔離另一個(gè)事務(wù)對(duì)其進(jìn)行相關(guān)操作。
但是,如果隔離的太嚴(yán)格了,事務(wù)之間就如同排隊(duì),需要一個(gè)一個(gè)來(lái),將會(huì)降低系統(tǒng)的響應(yīng)時(shí)間,使用者會(huì)認(rèn)為,切!這系統(tǒng)設(shè)計(jì)的夠糟糕的,睡了一覺(jué),還沒(méi)有響應(yīng)。
如果隔離的太寬松了,受事務(wù)之間的影響,會(huì)發(fā)生數(shù)據(jù)的異常。
所以在 JDBC 中一般會(huì)提供多種隔離機(jī)制,讓開(kāi)發(fā)者根據(jù)需要進(jìn)行選擇。
事務(wù)隔離級(jí)別從低到高:
- 讀取未提交(Read Uncommitted);
- 讀取已提交 (Read Committed);
- 可重復(fù)讀(Repeatable Read);
- 序列化(serializable)。
如何選擇,當(dāng)然是需要根據(jù)業(yè)務(wù)需要進(jìn)行設(shè)定。
不同的隔離機(jī)制下,并發(fā)的事務(wù)之間會(huì)發(fā)生一些什么樣的事情?
3. 隔離與并發(fā)
3.1 讀取未提交(Read Uncommitted)
字面上理解,可以讀取沒(méi)有提交的數(shù)據(jù),這是最低的事務(wù)隔離級(jí)別:
- 讀事務(wù)不會(huì)阻塞讀事務(wù)和寫(xiě)事務(wù),寫(xiě)事務(wù)也不會(huì)阻塞讀事務(wù),但是會(huì)阻塞寫(xiě)事務(wù);
- 寫(xiě)事務(wù)不阻塞讀事務(wù),可以讀取未提交的數(shù)據(jù)。
后果是,會(huì)出現(xiàn)臟讀現(xiàn)象。
如果財(cái)務(wù)工作人員在更新公司員工工資時(shí),不小心把 A 同事的原工資 2000 改成了 8000,但馬上發(fā)現(xiàn)了錯(cuò)誤,立馬更正過(guò)來(lái)。改過(guò)就好,不傷大雅。
問(wèn)題是,如果在財(cái)務(wù)工作人員修改時(shí),A 同事恰好也在查看自己的賬號(hào),因?yàn)榭煽吹絼e的事務(wù)沒(méi)有提交的數(shù)據(jù),天降財(cái)富呀,眼睛沒(méi)花吧,工資漲成 8000 元了。
如果晚上回家告訴了老婆,第二天再一查,工資還是 2000 。請(qǐng)問(wèn)下班后該如何向老婆交待!
這就叫臟讀。當(dāng)然,并不是沒(méi)有解決的辦法。
臟讀解決方案:在財(cái)務(wù)人員的事務(wù)提交前,任何其他事務(wù)不可讀取其修改過(guò)的值,則可以避免 A 同事回家挨罵的悲劇。
3.2 讀取已提交 (Read Committed)
這個(gè)比前面的要嚴(yán)格點(diǎn),只能讀已經(jīng)提交的,就不會(huì)出現(xiàn)臟讀情況。
- 寫(xiě)事務(wù)會(huì)阻塞讀事務(wù)和寫(xiě)事務(wù),但是讀事務(wù)不會(huì)阻塞讀事務(wù)和寫(xiě)事務(wù)。
- 讀事務(wù)不阻塞寫(xiě)事務(wù)
但是有可能造成不可重復(fù)讀。
如果 A 同事聽(tīng)說(shuō)老板要給自己漲工資,查看了一下賬號(hào),唉!還是只有 1000 元,心情好糟糕。
此時(shí)財(cái)務(wù)人員對(duì) A 同事的賬號(hào)進(jìn)行了修改了,變成 2000,并提交了事務(wù)。A 同事再次讀取時(shí),工資變成 2000。此時(shí)就不知道是自己眼花還是在做夢(mèng)。這就是不可重復(fù),意思就是說(shuō),在這種隔離下,你最好不要有事沒(méi)事的反復(fù)讀,可能會(huì)出現(xiàn)前后讀出來(lái)的數(shù)據(jù)不一致的情況。
3.3 可重復(fù)讀(Repeatable Read)
- 讀事務(wù)會(huì)阻塞寫(xiě)事務(wù),但是讀事務(wù)不會(huì)阻塞讀事務(wù),寫(xiě)事務(wù)會(huì)阻塞寫(xiě)事務(wù)和讀事務(wù);
- 讀事務(wù)不阻塞讀事務(wù) (針對(duì)的是記錄而不是表)。
可能會(huì)造成幻讀問(wèn)題:
幻讀是針對(duì)事務(wù)期間所插入的新數(shù)據(jù)而言。
如財(cái)務(wù)人員統(tǒng)計(jì)一下所有工資為 1000 的員工,這時(shí)人事向員工表插入了一條新記錄,工資也是 1000 。財(cái)務(wù)人員再次讀取所有工資為 1000 的員工, 共讀取到了 11 條記錄。
可采用對(duì)表加鎖的方式避免幻讀的事情發(fā)生,一般情況下不會(huì)過(guò)于關(guān)心是否會(huì)發(fā)生幻讀。
3.4 序列化(serializable)
這算是最嚴(yán)格的隔離級(jí)別,如果設(shè)置成這個(gè)級(jí)別,就不會(huì)出現(xiàn)以上所有的問(wèn)題題(臟讀,不可重復(fù)讀,幻影讀)。但是,性能極低,一般不用!
4. 小結(jié)
好了,到了該總結(jié)的時(shí)候了。本節(jié)課向大家介紹了事務(wù)機(jī)制,重點(diǎn)講解了事務(wù)的隔離機(jī)制。
隔離策略,就如同交通法則,壓一點(diǎn)點(diǎn)實(shí)線;紅燈時(shí),車(chē)頭出了暫停線一點(diǎn)點(diǎn)都要罰錢(qián),大家開(kāi)車(chē)就只能小心翼翼啦, 交通的暢通性就會(huì)降低。
但是,如果太放寬,就容易發(fā)生交通事故。
所以呀!隔離機(jī)制要根據(jù)實(shí)際情況進(jìn)行選擇。