我們?cè)O(shè)想一個(gè)場(chǎng)景,這個(gè)場(chǎng)景中我們需要插入多條相關(guān)聯(lián)的數(shù)據(jù)到數(shù)據(jù)庫(kù),不幸的是,這個(gè)過(guò)程可能會(huì)遇到下面這些問(wèn)題:
公司專(zhuān)注于為企業(yè)提供成都做網(wǎng)站、成都網(wǎng)站建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、微信公眾號(hào)開(kāi)發(fā)、商城開(kāi)發(fā),小程序開(kāi)發(fā),軟件定制網(wǎng)站等一站式互聯(lián)網(wǎng)企業(yè)服務(wù)。憑借多年豐富的經(jīng)驗(yàn),我們會(huì)仔細(xì)了解各客戶(hù)的需求而做出多方面的分析、設(shè)計(jì)、整合,為客戶(hù)設(shè)計(jì)出具風(fēng)格及創(chuàng)意性的商業(yè)解決方案,成都創(chuàng)新互聯(lián)公司更提供一系列網(wǎng)站制作和網(wǎng)站推廣的服務(wù)。
上面的任何一個(gè)問(wèn)題都可能會(huì)導(dǎo)致數(shù)據(jù)的不一致性。為了保證數(shù)據(jù)的一致性,系統(tǒng)必須能夠處理這些問(wèn)題。事務(wù)就是我們抽象出來(lái)簡(jiǎn)化這些問(wèn)題的首選機(jī)制。事務(wù)的概念起源于數(shù)據(jù)庫(kù),目前,已經(jīng)成為一個(gè)比較廣泛的概念。
何為事務(wù)? 一言蔽之, 事務(wù)是邏輯上的一組操作,要么都執(zhí)行,要么都不執(zhí)行。
事務(wù)最經(jīng)典也經(jīng)常被拿出來(lái)說(shuō)例子就是轉(zhuǎn)賬了。假如小明要給小紅轉(zhuǎn)賬 1000 元,這個(gè)轉(zhuǎn)賬會(huì)涉及到兩個(gè)關(guān)鍵操作,這兩個(gè)操作必須都成功或者都失敗。
事務(wù)會(huì)把這兩個(gè)操作就可以看成邏輯上的一個(gè)整體,這個(gè)整體包含的操作要么都成功,要么都要失敗。這樣就不會(huì)出現(xiàn)小明余額減少而小紅的余額卻并沒(méi)有增加的情況。
大多數(shù)情況下,我們?cè)谡務(wù)撌聞?wù)的時(shí)候,如果沒(méi)有特指 分布式事務(wù) ,往往指的就是 數(shù)據(jù)庫(kù)事務(wù) 。
數(shù)據(jù)庫(kù)事務(wù)在我們?nèi)粘i_(kāi)發(fā)中接觸的最多了。如果你的項(xiàng)目屬于單體架構(gòu)的話,你接觸到的往往就是數(shù)據(jù)庫(kù)事務(wù)了。
那數(shù)據(jù)庫(kù)事務(wù)有什么作用呢?
簡(jiǎn)單來(lái)說(shuō),數(shù)據(jù)庫(kù)事務(wù)可以保證多個(gè)對(duì)數(shù)據(jù)庫(kù)的操作(也就是 SQL 語(yǔ)句)構(gòu)成一個(gè)邏輯上的整體。構(gòu)成這個(gè)邏輯上的整體的這些數(shù)據(jù)庫(kù)操作遵循: 要么全部執(zhí)行成功,要么全部不執(zhí)行 。
另外,關(guān)系型數(shù)據(jù)庫(kù)(例如: MySQL 、 SQL Server 、 Oracle 等)事務(wù)都有 ACID 特性:
ACID
這里要額外補(bǔ)充一點(diǎn): 只有保證了事務(wù)的持久性、原子性、隔離性之后,一致性才能得到保障。也就是說(shuō) A、I、D 是手段,C 是目的!
在典型的應(yīng)用程序中,多個(gè)事務(wù)并發(fā)運(yùn)行,經(jīng)常會(huì)操作相同的數(shù)據(jù)來(lái)完成各自的任務(wù)(多個(gè)用戶(hù)對(duì)同一數(shù)據(jù)進(jìn)行操作)。并發(fā)雖然是必須的,但可能會(huì)導(dǎo)致以下的問(wèn)題。
不可重復(fù)讀和幻讀區(qū)別 :不可重復(fù)讀的重點(diǎn)是修改比如多次讀取一條記錄發(fā)現(xiàn)其中某些列的值被修改,幻讀的重點(diǎn)在于新增或者刪除比如多次查詢(xún)同一條查詢(xún)語(yǔ)句(DQL)時(shí),記錄發(fā)現(xiàn)記錄增多或減少了。
SQL 標(biāo)準(zhǔn)定義了四個(gè)隔離級(jí)別:
隔離級(jí)別臟讀不可重復(fù)讀幻讀 READ-UNCOMMITTED READ-COMMITTED REPEATABLE-READ SERIALIZABLE
MySQL 的隔離級(jí)別基于鎖和 MVCC 機(jī)制共同實(shí)現(xiàn)的。
SERIALIZABLE 隔離級(jí)別,是通過(guò)鎖來(lái)實(shí)現(xiàn)的。除了 SERIALIZABLE 隔離級(jí)別,其他的隔離級(jí)別都是基于 MVCC 實(shí)現(xiàn)。
不過(guò), SERIALIZABLE 之外的其他隔離級(jí)別可能也需要用到鎖機(jī)制,就比如 REPEATABLE-READ 在當(dāng)前讀情況下需要使用加鎖讀來(lái)保證不會(huì)出現(xiàn)幻讀。
MySQL InnoDB 存儲(chǔ)引擎的默認(rèn)支持的隔離級(jí)別是 REPEATABLE-READ(可重讀) 。我們可以通過(guò) SELECT @@tx_isolation; 命令來(lái)查看,MySQL 8.0 該命令改為 SELECT @@transaction_isolation;
從上面對(duì) SQL 標(biāo)準(zhǔn)定義了四個(gè)隔離級(jí)別的介紹可以看出,標(biāo)準(zhǔn)的 SQL 隔離級(jí)別定義里,REPEATABLE-READ(可重復(fù)讀)是不可以防止幻讀的。
但是!InnoDB 實(shí)現(xiàn)的 REPEATABLE-READ 隔離級(jí)別其實(shí)是可以解決幻讀問(wèn)題發(fā)生的,主要有下面兩種情況:
因?yàn)楦綦x級(jí)別越低,事務(wù)請(qǐng)求的鎖越少,所以大部分?jǐn)?shù)據(jù)庫(kù)系統(tǒng)的隔離級(jí)別都是 READ-COMMITTED ,但是你要知道的是 InnoDB 存儲(chǔ)引擎默認(rèn)使用 REPEATABLE-READ 并不會(huì)有任何性能損失。
InnoDB 存儲(chǔ)引擎在分布式事務(wù)的情況下一般會(huì)用到 SERIALIZABLE 隔離級(jí)別。
1.查看當(dāng)前會(huì)話隔離級(jí)別
select @@tx_isolation;
2.查看系統(tǒng)當(dāng)前隔離級(jí)別
select @@global.tx_isolation;
3.設(shè)置當(dāng)前會(huì)話隔離級(jí)別
set session transaction isolatin level repeatable read;
4.設(shè)置系統(tǒng)當(dāng)前隔離級(jí)別
set global transaction isolation level repeatable read;
5.命令行,開(kāi)始事務(wù)時(shí)
set autocommit=off 或者 start transaction
關(guān)于隔離級(jí)別的理解
1.read uncommitted
可以看到未提交的數(shù)據(jù)(臟讀),舉個(gè)例子:別人說(shuō)的話你都相信了,但是可能他只是說(shuō)說(shuō),并不實(shí)際做。
2.read committed
讀取提交的數(shù)據(jù)。但是,可能多次讀取的數(shù)據(jù)結(jié)果不一致(不可重復(fù)讀,幻讀)。用讀寫(xiě)的觀點(diǎn)就是:讀取的行數(shù)據(jù),可以寫(xiě)。
3.repeatable read(MySQL默認(rèn)隔離級(jí)別)
可以重復(fù)讀取,但有幻讀。讀寫(xiě)觀點(diǎn):讀取的數(shù)據(jù)行不可寫(xiě),但是可以往表中新增數(shù)據(jù)。在MySQL中,其他事務(wù)新增的數(shù)據(jù),看不到,不會(huì)產(chǎn)生幻讀。采用多版本并發(fā)控制(MVCC)機(jī)制解決幻讀問(wèn)題。
4.serializable
可讀,不可寫(xiě)。像java中的鎖,寫(xiě)數(shù)據(jù)必須等待另一個(gè)事務(wù)結(jié)束。
數(shù)據(jù)庫(kù)事務(wù)及隔離級(jí)別
隔離級(jí)別:臟讀、幻讀、一致讀、不可重復(fù)讀、更新丟失
1.臟讀(Dirty Reads):一個(gè)事務(wù)開(kāi)始讀取了某行數(shù)據(jù)但是另外一個(gè)事務(wù)已經(jīng)更新了此數(shù)據(jù)但沒(méi)有能夠及時(shí)提交。這是相當(dāng)危險(xiǎn)很可能所有操作都被回滾
2.幻讀(Phantom Reads):也稱(chēng)為幻像(幻影)。事務(wù)在操作過(guò)程中進(jìn)行兩次查詢(xún),第二次查詢(xún)結(jié)果包含了第一次查詢(xún)中未出現(xiàn)的數(shù)據(jù)(這里并不要求兩次查詢(xún)SQL語(yǔ)句相同)這是因?yàn)樵趦纱尾樵?xún)過(guò)程中有另外一個(gè)事務(wù)插入數(shù)據(jù)造成的
3.不可重復(fù)讀(Non-repeatable Reads):一個(gè)事務(wù)對(duì)同一行數(shù)據(jù)重復(fù)讀取兩次但是卻得到了不同結(jié)果。例如在兩次讀取中途有另外一個(gè)事務(wù)對(duì)該行數(shù)據(jù)進(jìn)行了修改并提交
4.兩次更新問(wèn)題(Second lost updates problem):無(wú)法重復(fù)讀取特例,有兩個(gè)并發(fā)事務(wù)同時(shí)讀取同一行數(shù)據(jù)然后其中一個(gè)對(duì)它進(jìn)行修改提交而另一個(gè)也進(jìn)行了修改提交這就會(huì)造成第一次寫(xiě)操作失效
5.更新丟失(Lost update):兩個(gè)事務(wù)都同時(shí)更新一行數(shù)據(jù)但是第二個(gè)事務(wù)卻中途失敗退出導(dǎo)致對(duì)數(shù)據(jù)兩個(gè)修改都失效了這是系統(tǒng)沒(méi)有執(zhí)行任何鎖操作因此并發(fā)事務(wù)并沒(méi)有被隔離開(kāi)
20、鎖是什么?
鎖:在所有的DBMS(數(shù)據(jù)庫(kù)管理系統(tǒng))中,鎖是實(shí)現(xiàn)事務(wù)的關(guān)鍵,鎖可以保證事務(wù)的完整性和并發(fā)性。與現(xiàn)實(shí)生活中鎖一樣,它可以使某些數(shù)據(jù)的擁有者,在某段時(shí)間內(nèi)不能使用某些數(shù)據(jù)或數(shù)據(jù)結(jié)構(gòu)。當(dāng)然鎖還分級(jí)別的。
鎖分為行級(jí)鎖和表鎖。
行級(jí)鎖:主要是在執(zhí)行操作過(guò)程中,鎖定指定的行。
主要的鎖行語(yǔ)句有:insert ,update,delete ,及select ....for update。
表鎖:指在運(yùn)行操作指令過(guò)程中,由用戶(hù)指定鎖定某張表。lock table XXX in mode share;
共享鎖,排他鎖,共享排它,行共享,行排他。
鎖模式包括?
共享鎖:(讀?。┎僮鲃?chuàng)建的鎖。其他用戶(hù)可以并發(fā)讀取數(shù)據(jù),但任何事物都不能獲取數(shù)據(jù)上的排它鎖,直到已釋放所有共享鎖。
排他鎖(X鎖):對(duì)數(shù)據(jù)A加上排他鎖后,則其他事務(wù)不能再對(duì)A加任任何類(lèi)型的封鎖。獲準(zhǔn)排他鎖的事務(wù)既能讀數(shù)據(jù),又能修改數(shù)據(jù)。
更新鎖:更新 (U) 鎖可以防止通常形式的死鎖。如果兩個(gè)事務(wù)獲得了資源上的共享模式鎖,然后試圖同時(shí)更新數(shù)據(jù),則兩個(gè)事務(wù)需都要轉(zhuǎn)換共享鎖為排它 (X) 鎖,并且每個(gè)事務(wù)都等待另一個(gè)事務(wù)釋放共享模式鎖,因此發(fā)生死鎖。
若要避免這種潛 在的死鎖問(wèn)題,請(qǐng)使用更新 (U) 鎖。一次只有一個(gè)事務(wù)可以獲得資源的更新 (U) 鎖。如果事務(wù)修改資源,則更新 (U) 鎖轉(zhuǎn)換為排它 (X) 鎖。否則,鎖轉(zhuǎn)換為共享鎖。
鎖的粒度主要有以下幾種類(lèi)型:
行鎖: 粒度最小,并發(fā)性最高
頁(yè)鎖:一次鎖定一頁(yè)。25個(gè)行鎖可升級(jí)為一個(gè)頁(yè)鎖。
表鎖:粒度大,并發(fā)性低
數(shù)據(jù)庫(kù)鎖:控制整個(gè)數(shù)據(jù)庫(kù)操作
樂(lè)觀鎖:樂(lè)觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè),如果發(fā)現(xiàn)沖突了,則讓返回用戶(hù)錯(cuò)誤的信息,讓用戶(hù)決定如何去做。一般的實(shí)現(xiàn)樂(lè)觀鎖的方式就是記錄數(shù)據(jù)版本。
悲觀鎖:每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)block直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖,讀鎖,寫(xiě)鎖等,都是在做操作之前先上鎖。
20、數(shù)據(jù)庫(kù)的樂(lè)觀鎖和悲觀鎖是什么? oracle 是行級(jí)鎖
數(shù)據(jù)庫(kù)管理系統(tǒng)(DBMS)中,并發(fā)控制的任務(wù)是:確保在多個(gè)事務(wù)同時(shí)存取同一數(shù)據(jù)時(shí),不破壞事務(wù)的隔離性和統(tǒng)一性以及數(shù)據(jù)庫(kù)的統(tǒng)一性。
悲觀鎖:假定會(huì)發(fā)生并發(fā)沖突,屏蔽一切可能違反數(shù)據(jù)完整性的操作
樂(lè)觀鎖:假設(shè)不會(huì)發(fā)生并發(fā)沖突,只在提交操作時(shí)檢查是否違反數(shù)據(jù)完整性。
21、悲觀鎖和樂(lè)觀鎖的區(qū)別,怎么實(shí)現(xiàn)
悲觀鎖:一段執(zhí)行邏輯加上悲觀鎖,不同線程同時(shí)執(zhí)行時(shí),只能有一個(gè)線程執(zhí)行,其他的線程在入口處等待,直到鎖被釋放。
樂(lè)觀鎖:一段執(zhí)行邏輯加上樂(lè)觀鎖,不同線程同時(shí)執(zhí)行時(shí),可以同時(shí)進(jìn)入執(zhí)行,在最后更新數(shù)據(jù)的時(shí)候要檢查這些數(shù)據(jù)是否被其他線程修改了(版本和執(zhí)行初是否相同),沒(méi)有修改則進(jìn)行更新,否則放棄本次操作。
事務(wù)的隔離級(jí)別
數(shù)據(jù)庫(kù)事務(wù)的隔離級(jí)別有4個(gè),由低到高依次為Read uncommitted、Read committed、Repeatable read、Serializable,這四個(gè)級(jí)別可以逐個(gè)解決臟讀、不可重復(fù)讀、幻讀這幾類(lèi)問(wèn)題。
√: 可能出現(xiàn) ×: 不會(huì)出現(xiàn)
臟讀不可重復(fù)讀幻讀
Read uncommitted√√√
Read committed×√√
Repeatable read××√
Serializable×××
注意:我們討論隔離級(jí)別的場(chǎng)景,主要是在多個(gè)事務(wù)并發(fā)的情況下,因此,接下來(lái)的講解都圍繞事務(wù)并發(fā)。
Read uncommitted 讀未提交
公司發(fā)工資了,領(lǐng)導(dǎo)把5000元打到singo的賬號(hào)上,但是該事務(wù)并未提交,而 singo正好去查看賬戶(hù),發(fā)現(xiàn)工資已經(jīng)到賬,是5000元整,非常高興。可是不幸的是,領(lǐng)導(dǎo)發(fā)現(xiàn)發(fā)給singo的工資金額不對(duì),是2000元,于是迅速 回滾了事務(wù),修改金額后,將事務(wù)提交,最后singo實(shí)際的工資只有2000元,singo空歡喜一場(chǎng)。
出現(xiàn)上述情況,即我們所說(shuō)的臟讀,兩個(gè)并發(fā)的事務(wù),“事務(wù)A:領(lǐng)導(dǎo)給singo發(fā)工資”、“事務(wù)B:singo查詢(xún)工資賬戶(hù)”,事務(wù)B讀取了事務(wù)A尚未提交的數(shù)據(jù)。
當(dāng)隔離級(jí)別設(shè)置為Read uncommitted時(shí),就可能出現(xiàn)臟讀,如何避免臟讀,請(qǐng)看下一個(gè)隔離級(jí)別。
Read committed 讀提交
singo拿著工資卡去消費(fèi),系統(tǒng)讀取到卡里確實(shí)有2000元,而此時(shí)她的老婆也正好 在網(wǎng)上轉(zhuǎn)賬,把singo工資卡的2000元轉(zhuǎn)到另一賬戶(hù),并在singo之前提交了事務(wù),當(dāng)singo扣款時(shí),系統(tǒng)檢查到singo的工資卡已經(jīng)沒(méi)有 錢(qián),扣款失敗,singo十分納悶,明明卡里有錢(qián),為何......
出現(xiàn)上述情況,即我們所說(shuō)的不可重復(fù)讀,兩個(gè)并發(fā)的事務(wù),“事務(wù)A:singo消費(fèi)”、“事務(wù)B:singo的老婆網(wǎng)上轉(zhuǎn)賬”,事務(wù)A事先讀取了數(shù)據(jù),事務(wù)B緊接了更新了數(shù)據(jù),并提交了事務(wù),而事務(wù)A再次讀取該數(shù)據(jù)時(shí),數(shù)據(jù)已經(jīng)發(fā)生了改變。
當(dāng)隔離級(jí)別設(shè)置為Read committed時(shí),避免了臟讀,但是可能會(huì)造成不可重復(fù)讀。
大多數(shù)數(shù)據(jù)庫(kù)的默認(rèn)級(jí)別就是Read committed,比如Sql Server , Oracle。如何解決不可重復(fù)讀這一問(wèn)題,請(qǐng)看下一個(gè)隔離級(jí)別。
Repeatable read 重復(fù)讀
當(dāng)隔離級(jí)別設(shè)置為Repeatable read時(shí),可以避免不可重復(fù)讀。當(dāng)singo拿著工資卡去消費(fèi)時(shí),一旦系統(tǒng)開(kāi)始讀取工資卡信息(即事務(wù)開(kāi)始),singo的老婆就不可能對(duì)該記錄進(jìn)行修改,也就是singo的老婆不能在此時(shí)轉(zhuǎn)賬。
雖然Repeatable read避免了不可重復(fù)讀,但還有可能出現(xiàn)幻讀。
singo的老婆工作在銀行部門(mén),她時(shí)常通過(guò)銀行內(nèi)部系統(tǒng)查看singo的信用卡消費(fèi) 記錄。有一天,她正在查詢(xún)到singo當(dāng)月信用卡的總消費(fèi)金額(select sum(amount) from transaction where month = 本月)為80元,而singo此時(shí)正好在外面胡吃海塞后在收銀臺(tái)買(mǎi)單,消費(fèi)1000元,即新增了一條1000元的消費(fèi)記錄(insert transaction ... ),并提交了事務(wù),隨后singo的老婆將singo當(dāng)月信用卡消費(fèi)的明細(xì)打印到A4紙上,卻發(fā)現(xiàn)消費(fèi)總額為1080元,singo的老婆很詫異,以為出 現(xiàn)了幻覺(jué),幻讀就這樣產(chǎn)生了。
注:Mysql的默認(rèn)隔離級(jí)別就是Repeatable read。
Serializable 序列化
Serializable是最高的事務(wù)隔離級(jí)別,同時(shí)代價(jià)也花費(fèi)最高,性能很低,一般很少使用,在該級(jí)別下,事務(wù)順序執(zhí)行,不僅可以避免臟讀、不可重復(fù)讀,還避免了幻像讀。
首選你需要了解一下 數(shù)據(jù)臟讀,幻讀,等一些概念,其次是你要了解一下鎖這個(gè)概念,當(dāng)一條數(shù)據(jù)被讀取時(shí),處于鎖狀態(tài),其他的用戶(hù)無(wú)法對(duì)其進(jìn)行操作。
接上篇 事務(wù)隔離級(jí)別和幻讀 ,留了個(gè)坑,沒(méi)想到竟然過(guò)了10天,時(shí)間不注意真的過(guò)的好快。順便提下,圖片鏈接是屬于網(wǎng)站的,開(kāi)發(fā)自己的圖床迫在眉睫,萬(wàn)一哪天遷移就要做很多額外工作,一些概念或者思路用圖片表達(dá)更直觀清楚。
回到正題,之前提到一般情況下MySQL的InnoDB引擎在可重復(fù)讀的情況下是沒(méi)法保證不出現(xiàn)幻讀的,但實(shí)際情況是MySQL可以通過(guò)加鎖來(lái)防止幻讀的出現(xiàn),這種鎖定通過(guò)Next-key機(jī)制來(lái)實(shí)現(xiàn),是屬于記錄鎖和間隙鎖(Gap鎖)的結(jié)合。
引申,行級(jí)別鎖的三種算法:
舉個(gè)存在唯一索引和輔助索引的例子做說(shuō)明:
執(zhí)行 select * from test where b = 3 for update
存在兩個(gè)索引,分別加鎖,唯一主鍵列a加record lock , 輔助索引列b加next-key lock (1,3) 以及給下一個(gè)值的區(qū)間(3,6)加gap鎖;
因此在另一個(gè)事務(wù)里執(zhí)行以下語(yǔ)句都會(huì)阻塞,具體分析:
第一個(gè)阻塞因?yàn)榧恿宋ㄒ凰饕膔ecord lock a = 5;
第二個(gè)主鍵插入4,符合條件,但是根據(jù)輔助索引b 的范圍, b = 2 在(1,3)中,同樣阻塞;
第三個(gè)a =6 不在主鍵a鎖定范圍,b = 5 也不在輔助索引b 的范圍(1,3)中,但在另一個(gè)gap鎖范圍(3,6)中,因此也阻塞;
這種鎖定情形下,可以執(zhí)行的包括類(lèi)似語(yǔ)句:
insert的特殊情況
對(duì)于insert 會(huì)檢查下一條記錄是否被鎖定,如上述例子有 select * from test where b = 3 for update 插入 insert into test select 2,2 會(huì)檢測(cè)到b = 3 已經(jīng)被鎖定,而 insert into test select 2,0 可以執(zhí)行;
[1]:《MySQL技術(shù)內(nèi)幕:InnoDB存儲(chǔ)引擎》-第六章:鎖