數(shù)據(jù)庫(kù)問(wèn)題總結(jié)

                                                                        數(shù)據(jù)庫(kù)總結(jié)

1.數(shù)據(jù)庫(kù)的四大特性
數(shù)據(jù)庫(kù)的四大特性:原子性,一致性、隔離性、持久性。
原子性:在事務(wù)包含的所有操作要么同時(shí)執(zhí)行成功,要么同時(shí)失敗并回滾,因此事務(wù)操作如果成功就必須要完全應(yīng)用到數(shù)據(jù)庫(kù),如果失敗則不能對(duì)數(shù)據(jù)庫(kù)有任何影響。
一致性:指的是事務(wù)對(duì)數(shù)據(jù)庫(kù)操作前后,數(shù)據(jù)庫(kù)的整體約束性沒(méi)有被破壞。比如,A向B轉(zhuǎn)賬,不可能A扣了錢但是B確沒(méi)有到賬。
隔離性:是指當(dāng)多個(gè)用戶并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí),比如同時(shí)操作一張表時(shí),數(shù)據(jù)庫(kù)為每一個(gè)用戶開(kāi)啟的事務(wù),并不會(huì)被其他的事務(wù)所干擾,多個(gè)并發(fā)事務(wù)之間要相互隔離。
持久性:只一旦事務(wù)被提交后,那么對(duì)數(shù)據(jù)庫(kù)的改變將會(huì)是永久的

2.事務(wù)的并發(fā)?事務(wù)隔離級(jí)別,每個(gè)級(jí)別會(huì)引發(fā)什么問(wèn)題,MYSQL默認(rèn)是哪個(gè)級(jí)別?
首先,從理論上來(lái)說(shuō)的話,事務(wù)應(yīng)該彼此完全隔離,以避免并發(fā)事務(wù)所導(dǎo)致的問(wèn)題,但是這樣會(huì)對(duì)性能產(chǎn)生很大的影響,因此在實(shí)際的開(kāi)發(fā)過(guò)程中,為了提升性能,一般會(huì)采用較低的隔離級(jí)別,隔離級(jí)別可以通過(guò)事務(wù)屬系指定。
再說(shuō)一下事務(wù)在并發(fā)運(yùn)行的過(guò)程中會(huì)導(dǎo)致哪些問(wèn)題:
1.臟讀:事務(wù)A讀取了事務(wù)B更改的數(shù)據(jù),但是事務(wù)B回滾了,那么事務(wù)A就讀取了事務(wù)B更改但未提交的數(shù)據(jù),那么A讀取的數(shù)據(jù)就為臟數(shù)據(jù)。
2.不可重復(fù)度:事務(wù)A在多次讀取同一數(shù)據(jù)時(shí),事務(wù)B對(duì)這一數(shù)據(jù)更改并提交,導(dǎo)致事務(wù)A多次多次讀取同一數(shù)據(jù)時(shí),結(jié)果會(huì)因B事務(wù)的更改會(huì)導(dǎo)致兩次讀取的數(shù)據(jù)不一致。
3.幻讀:幻讀解決了不可重復(fù)讀,保證了同一個(gè)事務(wù)里,查詢的結(jié)果都是從事務(wù)開(kāi)始時(shí)的狀態(tài)。
例如:事務(wù)T1對(duì)一個(gè)表中的說(shuō)有行的某個(gè)數(shù)據(jù)從1改為了2,但是之后,事務(wù)T2又對(duì)這個(gè)表插入了一個(gè)數(shù)據(jù)項(xiàng),并且數(shù)值也是1,并且提交給了數(shù)據(jù)庫(kù),那么當(dāng)事務(wù)1的用戶查看剛剛修改后的數(shù)據(jù),會(huì)發(fā)現(xiàn)T2事務(wù)插入的那行還是1,就像是發(fā)生了幻覺(jué)一樣,這就產(chǎn)生了幻讀。
解決不可重復(fù)讀的問(wèn)題就是:鎖住滿足條件的行
解決幻讀的問(wèn)題:鎖住表

事務(wù)的隔離級(jí)別有四種:讀未提交,不可重復(fù)讀,可重復(fù)讀,串行化。

事務(wù)的隔離級(jí)別 臟讀 不可重復(fù)讀 幻讀
讀未提交 是 是 是
不可重復(fù)讀 否 是 是
可重復(fù)讀 否 否 是
串行化 否 否 否

讀未提交:另一個(gè)事務(wù)修改了數(shù)據(jù),但尚未提交,而本事務(wù)的select語(yǔ)句會(huì)讀取到這些未被提交的數(shù)據(jù),從而發(fā)生了臟讀。
不可重復(fù)讀:事務(wù)A在多次讀取統(tǒng)一數(shù)據(jù)時(shí),事務(wù)B在事務(wù)A讀取過(guò)程中,對(duì)數(shù)據(jù)做出了更改并提交,導(dǎo)致事務(wù)A多次讀取統(tǒng)一數(shù)據(jù)時(shí),前后兩次讀取的結(jié)果會(huì)不一致。
可重復(fù)讀:在同一個(gè)事務(wù)里,select的結(jié)果是事務(wù)開(kāi)始時(shí)執(zhí)行的狀態(tài),因此同樣的select操作讀取到的結(jié)果會(huì)不一致,會(huì)有幻讀現(xiàn)象。
串行化: 最高的隔離級(jí)別,在這個(gè)隔離級(jí)別下,不會(huì)有任何的異常產(chǎn)生,并發(fā)的事務(wù),事務(wù)是一個(gè)一個(gè)按照順序執(zhí)行的。

MYSQL默認(rèn)的隔離級(jí)別: 可重復(fù)讀。

事務(wù)的隔離級(jí)別:讀未提交:寫數(shù)據(jù)會(huì)鎖住相應(yīng)的行
可重復(fù)讀:寫數(shù)據(jù)會(huì)鎖住整張表
串行化:讀寫數(shù)據(jù)都會(huì)鎖住整張表。

總結(jié):隔離級(jí)別越高,越能保證數(shù)據(jù)的完整性和一致性,但是對(duì)并發(fā)性能的影響也越大,所以對(duì)于大多數(shù)程序來(lái)說(shuō),可以優(yōu)先考慮將數(shù)據(jù)庫(kù)的隔離級(jí)別設(shè)置成可重復(fù)讀,既能避免臟讀,同時(shí)也能保證并發(fā)效率,雖然會(huì)幻讀等一些的并發(fā)問(wèn)題,所以在個(gè)別的場(chǎng)合下,可以采用悲觀鎖和樂(lè)觀鎖來(lái)控制。

3.為什么數(shù)據(jù)庫(kù)的索引采用B+TREE索引而不是hash索引?
首先對(duì)于hash索引來(lái)說(shuō):僅僅能夠滿足等值查詢,并不能滿足范圍查詢,因?yàn)榻?jīng)過(guò)相應(yīng)的hash算法處理之后的hash值的大小關(guān)系,并不能保證和運(yùn)算前完全一樣

那么介紹一下hash算法:


clipboard.png

如圖所示:當(dāng)索引進(jìn)來(lái)首先會(huì)計(jì)算hash碼,然后散列到各個(gè)對(duì)應(yīng)的數(shù)組位置,但是當(dāng)出現(xiàn)hash沖突時(shí),會(huì)插入在鏈表的頭部(jdk1.7).

所以在數(shù)據(jù)庫(kù)不采用hash索引是因?yàn)殡m然能后處理等職查詢,但是在處理范圍查詢等一些情況時(shí)毫無(wú)用武之地,另外如果hash索引遭遇到大量的hash沖突會(huì)導(dǎo)致鏈表過(guò)長(zhǎng)或紅黑樹(shù)過(guò)高,則其索引性能不一定必b+tree高。

最重要的是如何去理解b+tree索引:


clipboard.png

對(duì)于這張圖:首先innodb有一個(gè)頁(yè)的概念。數(shù)據(jù)庫(kù)存儲(chǔ)的數(shù)據(jù)被依次放在頁(yè)里面
頁(yè)里面包含目錄 目錄里面包含真實(shí)的數(shù)據(jù)。
如上圖第100頁(yè)里面存儲(chǔ)了4條數(shù)據(jù),第一條和第二條存儲(chǔ)在目錄1里面,第3條和第四條存儲(chǔ)在目錄4里面。第200頁(yè)同理。
那么在這個(gè)時(shí)候我需要找3-322c這行數(shù)據(jù)時(shí)就需要先找到頁(yè)碼,在去目錄里面找到對(duì)應(yīng)的數(shù)據(jù)。
如果有更多的數(shù)據(jù)進(jìn)來(lái)就會(huì)有更多的頁(yè)碼。


clipboard.png

那么就會(huì)出現(xiàn)索引頁(yè),因?yàn)樽畹讓悠鋵?shí)存放的是數(shù)據(jù)頁(yè)。數(shù)據(jù)頁(yè)里面存放的就是每一頁(yè)的開(kāi)頭。

同理,當(dāng)數(shù)據(jù)還在增多時(shí)就像下圖


clipboard.png

最終的b+tree就是這樣來(lái)存儲(chǔ)數(shù)據(jù)的(以上都是按照主鍵來(lái)進(jìn)行的)

那么這么說(shuō)來(lái)b+tree的好處究竟在哪里呢?
首先索引本身也很大,不可能全部存儲(chǔ)在內(nèi)存中,因此索引往往以索引文件的形式存儲(chǔ)的磁盤上。這樣的話,索引查找過(guò)程中就要產(chǎn)生磁盤I/O消耗,相對(duì)于內(nèi)存存取,I/O存取的消耗要高幾個(gè)數(shù)量級(jí),所以索引的結(jié)構(gòu)組織要盡量減少查找過(guò)程中磁盤I/O的存取次數(shù),提升索引效率。
磁盤存取原理:
    索引一般以文件形式存儲(chǔ)在磁盤上,索引檢索需要磁盤I/O操作。與主存不同,磁盤I/O存在機(jī)械運(yùn)動(dòng)耗費(fèi),因此磁盤I/O的時(shí)間消耗是巨大的。
所以:
1.B+Tree 單個(gè)節(jié)點(diǎn)能放多個(gè)子節(jié)點(diǎn),相同IO次數(shù),檢索出更多信息。
2.B+TREE 只在葉子節(jié)點(diǎn)存儲(chǔ)數(shù)據(jù) & 所有葉子結(jié)點(diǎn)包含一個(gè)鏈指針 & 其他內(nèi)層非葉子節(jié)點(diǎn)只存儲(chǔ)索引數(shù)據(jù)。只利用索引快速定位數(shù)據(jù)索引范圍,先定位索引再通過(guò)索引高效快速定位數(shù)據(jù)。

4.有哪些鎖?
悲觀鎖:悲觀鎖的特點(diǎn)是先獲取鎖,再進(jìn)行業(yè)務(wù)操作。即“悲觀”的認(rèn)為獲取鎖是非常有可能失敗的,因此要先確保獲取鎖了再進(jìn)行業(yè)務(wù)操作。通常來(lái)講在數(shù)據(jù)庫(kù)上的悲觀鎖需要數(shù)據(jù)庫(kù)本身提高支持,即通常用的select...for update操作來(lái)實(shí)現(xiàn)悲觀鎖。當(dāng)數(shù)據(jù)庫(kù)執(zhí)行select for update時(shí)會(huì)獲取被select中的數(shù)據(jù)行的行鎖,因此其他并發(fā)執(zhí)行的select for update如果試圖選中同一行的則會(huì)發(fā)生排斥(需要等待行鎖被釋放),因此達(dá)到鎖的效果。select for update 獲取鎖的行鎖會(huì)在事務(wù)結(jié)束時(shí)自動(dòng)釋放,因此必須在事務(wù)中使用。
另外,MYSQL還有個(gè)問(wèn)題時(shí)select for update 語(yǔ)句執(zhí)行中所有的掃描過(guò)的行都會(huì)被鎖上,這一點(diǎn)容易造成問(wèn)題。因此如果在mysql中用悲觀鎖務(wù)必要確定索引,而不是全表掃描。
樂(lè)觀鎖: 也叫樂(lè)觀控制并發(fā),它假設(shè)多用戶并發(fā)的事務(wù)在處理時(shí)并不會(huì)相互影響,各事務(wù)能夠在不產(chǎn)生鎖的情況下處理各自影響的那部分?jǐn)?shù)據(jù)。在提交數(shù)據(jù)更新之前,每個(gè)事務(wù)會(huì)先檢查在該事務(wù)讀取數(shù)據(jù)后,有沒(méi)有其他事務(wù)又修改了數(shù)據(jù)。如果其他事務(wù)有更新的話,那么當(dāng)前正在提交的事務(wù)會(huì)進(jìn)行回滾。
樂(lè)觀鎖的特點(diǎn)是先進(jìn)行業(yè)務(wù)操作,不到萬(wàn)不得已不去拿鎖。即樂(lè)觀的認(rèn)為拿鎖多半會(huì)成功,因此在進(jìn)行完業(yè)務(wù)操作需要進(jìn)行實(shí)際更新數(shù)據(jù)的最后一步再去拿一下鎖就好。
樂(lè)觀鎖在數(shù)據(jù)庫(kù)上的實(shí)現(xiàn)完全是邏輯的,不需要數(shù)據(jù)庫(kù)提供特殊的支持。一般的做法是需要鎖的數(shù)據(jù)上增加一個(gè)版本號(hào),或者時(shí)間戳。
給表加版本號(hào),是數(shù)據(jù)庫(kù)實(shí)現(xiàn)樂(lè)觀鎖的一種方式。

對(duì)比:樂(lè)觀鎖在不發(fā)生取鎖失敗的情況下比悲觀鎖開(kāi)銷小,但是一旦取鎖失敗則回滾那么開(kāi)銷比較大。樂(lè)觀鎖和悲觀鎖是數(shù)據(jù)庫(kù)用來(lái)保證數(shù)據(jù)并發(fā)安全防止更新丟失的兩種方法

5.mysql的mvcc機(jī)制
MVCC,就是多版本控制。MVCC是一種并發(fā)控制的方法,一般再數(shù)據(jù)庫(kù)管理系統(tǒng)中,實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的并發(fā)訪問(wèn)。
在mysql的innodb引擎就是值在讀已提交和可重復(fù)讀這兩種隔離級(jí)別下的事務(wù)對(duì)于select操作會(huì)訪問(wèn)版本鏈中的記錄的過(guò)程。
所以,這就使得別的事務(wù)可以修改這條記錄,反正每次修改都會(huì)在版本鏈中記錄。select可以去版本鏈中拿記錄,這就實(shí)現(xiàn)了讀-寫,寫-讀的并發(fā)執(zhí)行,提升了系統(tǒng)的性能。
下面具體看看到底是如何實(shí)現(xiàn)的。

首先了解何為版本鏈?
在innodb存儲(chǔ)引擎中,它的每條記錄中必須有兩個(gè)必要的隱藏列。
trx_id: 這個(gè)id用來(lái)存儲(chǔ)每次對(duì)某條記錄進(jìn)行修改時(shí)的事務(wù)id。
roll_pointer:每次對(duì)記錄有修改的時(shí)候,都會(huì)把老的版本寫入undo日志中。這個(gè)roll_pointer就是存儲(chǔ)一個(gè)指針,它指向這個(gè)記錄上一個(gè)老版本的位置,通過(guò)它來(lái)獲得上一個(gè)老版本的信息。


clipboard.png

比如現(xiàn)在有個(gè)事務(wù)id為60的執(zhí)行這條記錄的修改語(yǔ)句


clipboard.png

此時(shí)在undo日志中就存在版本鏈
clipboard.png

再說(shuō)一個(gè)比較重要的概念:ReadView
首先對(duì)于讀已提交和可重復(fù)讀這兩種隔離級(jí)別他們生成ReadView的策略不同

先介紹一下ReadView. ReadView中主要就是有個(gè)列表來(lái)存儲(chǔ)我們系統(tǒng)中當(dāng)前活躍著的讀寫事務(wù),也就是begin了但是還未提交的事務(wù)。通過(guò)這個(gè)列表來(lái)判斷記錄的某個(gè)版本是否對(duì)于當(dāng)前事務(wù)可見(jiàn)。
假設(shè)當(dāng)前列表里的事務(wù)id為【80,100】
這時(shí)如果你要訪問(wèn)的記錄版本的事務(wù)id為50,比當(dāng)前的列表最小的id80小,那說(shuō)明這個(gè)事務(wù)在之前就提交了,所以對(duì)當(dāng)前活動(dòng)的事務(wù)來(lái)說(shuō)是可以訪問(wèn)的。
但是如果你要訪問(wèn)的事務(wù)id為80-100之間,那么就要再判斷一下是否在列表內(nèi),如果在那么就說(shuō)明此事務(wù)還未提交,所以版本并不能被訪問(wèn)。如果不在那說(shuō)明事務(wù)已經(jīng)被提交了,所以版本可以被訪問(wèn)。
但是如果你要訪問(wèn)的事務(wù)id=110,那么此本版比100還要大,那說(shuō)明這個(gè)版本是在ReadView生成之后才發(fā)生的,所以不能被訪問(wèn)。
這些記錄都是在版本鏈里面去找,先找最佳的記錄,如果最近的這條記錄不符合條件的話,即不可見(jiàn),再去上一個(gè)版本再比較當(dāng)前事務(wù)的id和這個(gè)版本的事務(wù)id能不能被訪問(wèn),以此類推直到返回可見(jiàn)版本或者結(jié)束。

舉例說(shuō)明一下:
在讀已提交的隔離級(jí)別下:
比如此時(shí)有一個(gè)事務(wù)id為100的事務(wù),修改了name,使得name等于小明2,但是事務(wù)還沒(méi)提交。則此時(shí)的版本鏈為:


clipboard.png

那此時(shí)另一個(gè)事務(wù)發(fā)起了select語(yǔ)句要查詢id為1的記錄,那此時(shí)生成的ReadView列表只有【100】,那就應(yīng)該去版本鏈里面去找了,首先肯定找最近一條,發(fā)現(xiàn)事務(wù)id為100,也就是那么為小明2的那條記錄,在列表內(nèi),故不能訪問(wèn)。
這個(gè)時(shí)候就要通過(guò)指針繼續(xù)去找下一條,name為小明1的記錄,

,小于列表的最小事務(wù)id100,所以可以訪問(wèn),直接訪問(wèn)結(jié)果為小明1.
那這時(shí)候我們把事務(wù)id為100的事務(wù)提交了,并且新建了一個(gè)事務(wù)id為110也修改了1的記錄,并且也不提交事務(wù)。


clipboard.png

這個(gè)時(shí)候的版本鏈為:
clipboard.png

這時(shí)候之前那個(gè)select事務(wù)又執(zhí)行了一次查詢,要查詢id=1的記錄。
這時(shí)關(guān)鍵的地方就來(lái)了。
如果你設(shè)置的是讀已提交的隔離級(jí)別,這個(gè)時(shí)候就會(huì)重新生成一個(gè)ReadView,那么列表里面的值就變成110.
按照上面的說(shuō)法,你去版本鏈里面通過(guò)事務(wù)id對(duì)比查找找打最合適的結(jié)果就是小明2.

可是,
如果你設(shè)置的是可重復(fù)讀的隔離級(jí)別,這個(gè)時(shí)候你的select事務(wù)的ReadView還是第一次select時(shí)候生成的ReadView,也就是【100】,所以select查詢的結(jié)果還是小明1,所以第二次和第一次的結(jié)果一樣,所以叫可重復(fù)讀。
也就是說(shuō)已提交讀隔離級(jí)別下的事務(wù)在每次查詢的開(kāi)始都會(huì)生成一個(gè)獨(dú)立的ReadView,而可重復(fù)讀隔離級(jí)別則在第一次讀的時(shí)候生成一個(gè)ReadView,之后的讀都復(fù)用之前的ReadView。
這就是mysql的MVCC,通過(guò)版本鏈,實(shí)現(xiàn)多版本,可并發(fā)讀-寫,寫-讀,通過(guò)ReadView生成的策略的不同實(shí)現(xiàn)不同的隔離級(jí)別。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容