數(shù)據(jù)庫(kù)相關(guān)知識(shí)回顧與總結(jié)

數(shù)據(jù)庫(kù)范式

https://www.zhihu.com/question/24696366

索引

  • 索引可以加快數(shù)據(jù)庫(kù)的檢索速度
  • 索引降低了插入、刪除、修改等維護(hù)任務(wù)的速度
  • 唯一索引可以確保每一行的唯一性
  • 通過使用索引,可以在查詢的過程中使用優(yōu)化隱藏器,提高系統(tǒng)的性能
  • 索引需要占用物理和數(shù)據(jù)空間

一條索引記錄包含的基本信息包括:鍵值(我們定義索引時(shí)指定的所有字段的值)+邏輯指針(指向數(shù)據(jù)頁或者是另一索引頁)。

索引類型
  • 聚集索引:表數(shù)據(jù)按照索引的順序來存儲(chǔ)。對(duì)于聚集索引,葉子結(jié)點(diǎn)存儲(chǔ)了真實(shí)的數(shù)據(jù)行,不再有另外單獨(dú)的數(shù)據(jù)頁;
  • 非聚集索引:表數(shù)據(jù)的存儲(chǔ)順序與索引順序無關(guān)。對(duì)于非聚集索引,葉節(jié)點(diǎn)包含索引字段值及指向數(shù)據(jù)頁數(shù)據(jù)行的邏輯指針,該層緊鄰數(shù)據(jù)頁,其行數(shù)量與數(shù)據(jù)頁一致;
  • 主碼索引:又稱聚集主碼,

B-Tree、B+-Tree、B*-Tree

主碼索引、聚集索引(聚集主碼)、非主碼索引(輔助索引)、唯一索引、外鍵索引、復(fù)合索引、外鍵索引、單列索引
MySQL索引背后的數(shù)據(jù)結(jié)構(gòu)及算法原理

數(shù)據(jù)庫(kù)的并發(fā)控制

事務(wù)(Transaction)

事務(wù)是什么?

事務(wù)是并發(fā)控制的基本單位。所謂事務(wù),它就是一組操作序列,這些操作要么執(zhí)行要么都不執(zhí)行,它是一組不可分割的工作單位。事務(wù)是數(shù)據(jù)庫(kù)維護(hù)數(shù)據(jù)一致性的單位,在每個(gè)事務(wù)結(jié)束時(shí),都能保持?jǐn)?shù)據(jù)一致性。

從上面的描述可以看出事務(wù)的提出主要是為了解決并發(fā)情況下保持?jǐn)?shù)據(jù)一致性的問題。

事務(wù)具有以下4個(gè)基本特征

  • 原子性(Atomic):事務(wù)中包含的每個(gè)操作都被看做一個(gè)邏輯單元,這個(gè)邏輯單元中的操作要么全部成功,要么全部失敗。(記錄之前的版本,允許回滾)

  • 一致性(Consistency):事務(wù)執(zhí)行的結(jié)果必須是使數(shù)據(jù)庫(kù)從一個(gè)一致性狀態(tài)變到另一個(gè)一致性狀態(tài)。換句話說,事務(wù)執(zhí)行前和事務(wù)執(zhí)行后數(shù)據(jù)內(nèi)在的邏輯始終是成立的。比如轉(zhuǎn)帳前后兩人的存款總和始終不變。因此當(dāng)數(shù)據(jù)庫(kù)只包含成功事務(wù)提交的結(jié)果時(shí),就說數(shù)據(jù)庫(kù)處于一致性狀態(tài)。如果數(shù)據(jù)庫(kù)系統(tǒng)運(yùn)行中發(fā)生故障,有些事務(wù)尚未完成就被迫中斷,這時(shí)數(shù)據(jù)庫(kù)就處于一種不正確的狀態(tài),或者說是不一致的狀態(tài)。例如在一在進(jìn)行轉(zhuǎn)賬的操作中,需要從賬戶A取出100元轉(zhuǎn)入賬戶B。那么就可以定義一個(gè)事務(wù),該事物包括兩個(gè)操作:第一個(gè)操作是從賬戶A中減去100元,第二個(gè)操作是向賬戶B中轉(zhuǎn)入100元。這兩個(gè)操作要么全做,要么全不做。全做或者全不做,數(shù)據(jù)庫(kù)就會(huì)處于一致性狀態(tài)。如果只做一個(gè)操作,則邏輯上就會(huì)發(fā)生錯(cuò)誤,減少或增加100元,數(shù)據(jù)庫(kù)就 處于不一致的狀態(tài)了。所以說一致性和原子性是密不可分的。

    但是現(xiàn)在問題來了——原子性就一定能夠保證一致性嗎?

    答案是否定的:原子性不能完全保證一致性。因?yàn)樵诙鄠€(gè)事務(wù)并行進(jìn)行的情況下,即使保證了每個(gè)事務(wù)的原子性,仍然可能導(dǎo)致數(shù)據(jù)不一致的結(jié)果。例如事務(wù)1需要將100元轉(zhuǎn)入賬戶A:先讀取A的賬戶余額的值,然后在這個(gè)值上加上100.但是,在這兩個(gè)操作之間,事務(wù)2修改了賬戶A的值,為它增加了100元,那么最后結(jié)果應(yīng)該是A增加了200元。但事實(shí)上,當(dāng)事務(wù)1最終完成時(shí),賬戶A只增加了100元,因?yàn)槭聞?wù)2的執(zhí)行結(jié)果被事務(wù)1覆蓋掉了。所以為了保證并發(fā)事務(wù)的一致性,就引入了事務(wù)的隔離性。(事務(wù)開始和結(jié)束之間的中間狀態(tài)不會(huì)被其他事務(wù)看到)

  • 隔離性(Isolation):一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)干擾。即一個(gè)事務(wù)的內(nèi)部操作及使用的數(shù)據(jù)對(duì)其他并發(fā)事務(wù)是隔離的,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能互相干擾。要達(dá)到這么一種效果:對(duì)于任意兩個(gè)并發(fā)的事務(wù)T1和T2,T2要么在T1開始之前就已經(jīng)結(jié)束,要么在T1結(jié)束之后才開始,這樣每個(gè)事務(wù)都感覺不到有其他事務(wù)在并發(fā)的執(zhí)行。關(guān)于事務(wù)的隔離性數(shù)據(jù)庫(kù)提供了多種隔離級(jí)別,后面會(huì)提到。(適當(dāng)?shù)仄茐囊恢滦詠硖嵘阅芘c并行度 例如:最終一致 ~= 讀未提交)
  • 持久性(Durability):持久性是指一個(gè)事務(wù)一旦提交,它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變就是永久性的。接下來的操作或故障不應(yīng)該對(duì)其執(zhí)行結(jié)果有影響。(每一次的事務(wù)提交之后就會(huì)保證不丟失)
保證事務(wù)ACID特性是事務(wù)管理的重要任務(wù)。事務(wù)ACID特性可能遭到破壞的因素有:
  1. 多個(gè)事務(wù)并行運(yùn)行時(shí),不同的事務(wù)操作交叉執(zhí)行;
  2. 事務(wù)在運(yùn)行過程中被強(qiáng)行停止。

在第一種情況下,數(shù)據(jù)庫(kù)管理系統(tǒng)必須保證多個(gè)事務(wù)的交叉運(yùn)行不影響這些事務(wù)的原子性;在第二種情況下,數(shù)據(jù)庫(kù)管理系統(tǒng)必須保證被強(qiáng)行終止的事務(wù)對(duì)數(shù)據(jù)庫(kù)和其他事務(wù)沒有任何影響。

下面來說說事務(wù)的隔離性

當(dāng)多個(gè)線程都開啟事務(wù)操作數(shù)據(jù)庫(kù)中的數(shù)據(jù)時(shí),數(shù)據(jù)庫(kù)系統(tǒng)要能進(jìn)行隔離操作,以保證各個(gè)線程獲取數(shù)據(jù)的準(zhǔn)確性,在介紹隔離性之前先說說如果不設(shè)置事務(wù)隔離級(jí)別可能會(huì)發(fā)生什么問題:

  • 臟讀(dirty read):臟讀是指在一個(gè)事務(wù)處理過程里讀取到了另一個(gè)未提交的事務(wù)中的數(shù)據(jù)。例如,事務(wù)T1修改某一數(shù)據(jù)并將其寫回磁盤,事務(wù)T2讀取同一數(shù)據(jù)后,T1由于某種原因被撤銷,此時(shí)被T1修改過的數(shù)據(jù)恢復(fù)原值,T2讀到的數(shù)據(jù)就與數(shù)據(jù)庫(kù)中的數(shù)據(jù)不一致,則T2讀到的數(shù)據(jù)就為“臟數(shù)據(jù)”,即不正確的數(shù)據(jù)。(修改時(shí)允許讀取導(dǎo)致臟讀)

  • 不可重復(fù)讀(non-repeatable read):不可重復(fù)讀是指在同一個(gè)事務(wù)內(nèi),針對(duì)同一數(shù)據(jù)進(jìn)行多次相同的查詢返回的結(jié)果不同。即當(dāng)事務(wù)T1讀取某一數(shù)據(jù)之后,事務(wù)T2對(duì)其進(jìn)行了修改,當(dāng)事務(wù)T1再次讀該數(shù)據(jù)時(shí),得到與前一次不同的值。(讀取時(shí)允許修改導(dǎo)致不可重復(fù)讀)

  • 丟失修改(lost update):

    1. 第一類丟失修改:事務(wù)T1撤銷時(shí),把已經(jīng)提交的事務(wù)T2的更新數(shù)據(jù)覆蓋了;
    2. 第二類丟失修改:兩個(gè)事務(wù)T1和T2讀入同一數(shù)據(jù)并修改,T2提交的結(jié)果破壞了T1提交的結(jié)果,導(dǎo)致T1的修改被丟失。(修改時(shí)允許修改導(dǎo)致丟失更新)
  • 幻讀(phantom read):(讀取時(shí)允許插入或刪除導(dǎo)致幻讀)

    1. 事務(wù)T1按一定條件從數(shù)據(jù)庫(kù)中都去了某些數(shù)據(jù)記錄之后,事務(wù)T2刪除了其中部分記錄2,當(dāng)T1再次按相同條件讀取數(shù)據(jù)時(shí),發(fā)現(xiàn)某些記錄神秘地消失了;
    2. 事務(wù)T1按一定條件從數(shù)據(jù)庫(kù)中讀取某些數(shù)據(jù)記錄后,事務(wù)T2插入了一些記錄,當(dāng)T1再次按相同條件讀取數(shù)據(jù)時(shí),發(fā)現(xiàn)多了一些記錄。

幻讀和不可重復(fù)讀有什么區(qū)別?

因?yàn)閺纳厦娴拿枋鰜砜?,幻讀和不可重復(fù)讀都是在同一次事務(wù)中按相同的條件查詢發(fā)現(xiàn)結(jié)果不一致。即從總的結(jié)果來看,似乎兩者都表現(xiàn)為兩次讀取的結(jié)果不一致。那幻讀和不可重復(fù)讀有什么區(qū)別呢?

  • 不可重復(fù)讀的重點(diǎn)是修改:同樣的條件,我們讀取過的數(shù)據(jù),再次讀取出來發(fā)現(xiàn)值不一樣了
  • 幻讀的重點(diǎn)在于新增或者刪除:同樣的條件,第一次和第二次讀取出來的記錄數(shù)不一樣

但是如果從控制的角度來看,兩者的區(qū)別就比較大了:

  • 對(duì)于不可重復(fù)讀,只需要鎖住滿足條件的記錄
  • 對(duì)于幻讀,它是由于并發(fā)事務(wù)增加或者刪除記錄導(dǎo)致的,不能像不可重復(fù)讀通過記錄加鎖解決,因?yàn)閷?duì)于新增的記錄根本無法加鎖。需要將事務(wù)串行化,才能避免幻讀。
事務(wù)隔離級(jí)別(由低到高)
  • 讀未提交(Read uncommited):如果一個(gè)事務(wù)已經(jīng)開始寫數(shù)據(jù),則另外一個(gè)事務(wù)不允許同時(shí)進(jìn)行寫操作,但允許其他事務(wù)讀取此行數(shù)據(jù)。該隔離級(jí)別可以通過“排它鎖”實(shí)現(xiàn)。讀未提交避免了丟失修改,但是卻可能出現(xiàn)臟讀——也就是事務(wù)T1都到了事務(wù)T2未提交的數(shù)據(jù);

  • 讀已提交(Read committed):讀取數(shù)據(jù)的事務(wù)允許其他食物繼續(xù)訪問該行數(shù)據(jù),但是未提交的寫事務(wù)將會(huì)禁止其他事物訪問該行。讀已提交是在讀未提交的基礎(chǔ)上避免了臟讀。即一個(gè)事務(wù)在寫數(shù)據(jù)的同時(shí),不允許其他事務(wù)讀取未提交的數(shù)據(jù),可以避免臟讀現(xiàn)象的發(fā)生??墒菃栴}又來了——某行記錄有一列值為0,事務(wù)T1正在對(duì)其進(jìn)行更新操作,把值update為1,同時(shí)事務(wù)T2正在對(duì)該行記錄進(jìn)行查詢操作。由于T2不允許讀取T1未提交的數(shù)據(jù),則此時(shí)T2讀取到的值為0。假設(shè)T1已經(jīng)提交,但是T2并未結(jié)束,并且又對(duì)該記錄進(jìn)行了一次查詢,發(fā)現(xiàn)此列數(shù)據(jù)的值變?yōu)榱?。問題出現(xiàn)了:T2在對(duì)同一數(shù)據(jù)進(jìn)行相同的兩次查詢操作時(shí),得到的數(shù)據(jù)的值并不一致,此時(shí)發(fā)生了不可重復(fù)讀的問題。

  • 可重復(fù)讀(Repeated read):讀取數(shù)據(jù)的事務(wù)將會(huì)禁止寫事務(wù)(但允許讀事務(wù)),寫事務(wù)則禁止任何其他事務(wù)。

    可以通過“共享讀鎖”和“排他寫鎖”避免了不可重復(fù)讀和臟讀,但是有時(shí)可能出現(xiàn)幻讀。

  • 序列化(Serializable):提供嚴(yán)格的事務(wù)隔離。它要求事務(wù)序列化執(zhí)行,事務(wù)只能一個(gè)接著一個(gè)執(zhí)行,但是不能并發(fā)執(zhí)行。如果僅僅通過“行級(jí)鎖”是無法實(shí)現(xiàn)事務(wù)序列化的,必須通過其他機(jī)制保證新插入的數(shù)據(jù)不會(huì)被剛執(zhí)行查詢操作的事務(wù)訪問到。序列化是最高的事務(wù)隔離級(jí)別,同時(shí)代價(jià)也花費(fèi)最高,但是性能很低,一般很少用。在該級(jí)別下,事務(wù)順序執(zhí)行,不僅可以避免臟讀、不可重復(fù)讀,還避免了幻讀。

隔離級(jí)別越高,越能保證數(shù)據(jù)的完整性和一致性,但是對(duì)并發(fā)性能的影響也越大。對(duì)于多數(shù)應(yīng)用程序,可以優(yōu)先考慮把數(shù)據(jù)系統(tǒng)的隔離級(jí)別設(shè)為Read Committed。它能夠避免臟讀,而且具有較好的并發(fā)性能。盡管它會(huì)導(dǎo)致不可重復(fù)讀、幻讀等并發(fā)問題。但是在可能出現(xiàn)這類問題的個(gè)別場(chǎng)合,可以由應(yīng)用程序采用悲觀鎖或樂觀鎖來控制。

大多數(shù)數(shù)據(jù)庫(kù)的默認(rèn)級(jí)別就是Read committed,比如 Sql Server和Oracle

MySQL的默認(rèn)隔離級(jí)別就是Repeatable read

事務(wù)相關(guān)的SQL語句

  • BEGIN /BEGIN WORK / START TRANSACTION:開啟事務(wù)
  • COMMIT:提交事務(wù)
  • ROLLBACK:回滾事務(wù)

封鎖

封鎖是實(shí)現(xiàn)并發(fā)控制的一個(gè)非常重要的技術(shù)。所謂封鎖就是事務(wù)T在對(duì)某個(gè)數(shù)據(jù)對(duì)象例如表、記錄等操作之前,先向系統(tǒng)發(fā)出請(qǐng)求,對(duì)其加鎖。加鎖后事務(wù)T就對(duì)該數(shù)據(jù)對(duì)象有了一定的控制,在事務(wù)T釋放它的鎖之前,其他事務(wù)不能更新此數(shù)據(jù)對(duì)象。例如,事務(wù)T1要修改A,若在讀出A之前先鎖住A,其他事務(wù)就不能再讀取和修改A了,直到T1修改并寫回A后解除了對(duì)A的封鎖為止。這樣,就不會(huì)丟失T1的修改。

確切的控制由封鎖的類型決定?;镜姆怄i類型有兩種:排他鎖(exclusive locks,簡(jiǎn)稱X鎖)和共享鎖(share locks,簡(jiǎn)稱S鎖)

  • X鎖(排他寫鎖):若事務(wù)T1對(duì)數(shù)據(jù)對(duì)象A加上X鎖,則只允許T讀取和修改A,其他任何事物都不能再對(duì)A加任何類型的鎖,直到T釋放A上的鎖為止。這就保證了其他事務(wù)在T釋放A上的鎖之前不能再讀取和修改A;
  • S鎖(共享讀鎖):若事務(wù)T對(duì)數(shù)據(jù)A加上S鎖,則事務(wù)T可以讀A但是不能修改A,其他事務(wù)只能對(duì)A加S鎖而不能加X鎖,直到T釋放A上的S鎖為止。這就保證了其他食物可以讀A,但在T釋放A上的S鎖之前不能對(duì)A進(jìn)行任何修改。

封鎖協(xié)議

  • 一級(jí)封鎖協(xié)議:事務(wù)T在對(duì)數(shù)據(jù)對(duì)象A進(jìn)行修改之前,必須對(duì)其加X鎖,直至事務(wù)結(jié)束才釋放。事務(wù)結(jié)束包括正常結(jié)束(COMMIT)和非正常結(jié)束(ROLLBACK);

    在一級(jí)加鎖協(xié)議中,如果僅僅是對(duì)數(shù)據(jù)進(jìn)行讀操作而不進(jìn)行修改,是不需要進(jìn)行加鎖的。所以只能避免修改丟失而不能避免不可重復(fù)讀和臟讀。

  • 二級(jí)封鎖協(xié)議:在一級(jí)加鎖協(xié)議的基礎(chǔ)上增加事務(wù)T在讀取數(shù)據(jù)R之前必須先對(duì)其加S鎖,讀完后即可釋放S鎖;

    二級(jí)加鎖協(xié)議除防止了丟失修改,還可進(jìn)一步防止讀臟數(shù)據(jù)。例如:事務(wù)T1正在對(duì)數(shù)據(jù)對(duì)象R進(jìn)行修改,此前已經(jīng)對(duì)R加上了X鎖,此時(shí)事務(wù)T2想讀取R,就必須對(duì)R加上S鎖,但是T2發(fā)現(xiàn)R已經(jīng)被T1加上了X鎖,于是T2只能等待T1釋放了在R上加的鎖之后才能對(duì)R加S鎖并讀取。這能防止T2讀取到T1未提交的數(shù)據(jù),從而避免了臟讀。

    但是在二級(jí)封鎖協(xié)議中,由于讀完數(shù)據(jù)后即可釋放S鎖,所以它不能保證可重復(fù)讀。

  • 三級(jí)封鎖協(xié)議:三級(jí)封鎖協(xié)議是指,在一級(jí)封鎖協(xié)議的基礎(chǔ)上增加事務(wù)T在讀取數(shù)據(jù)R之前對(duì)其加S鎖直至事務(wù)結(jié)束才釋放。

    三級(jí)封鎖協(xié)議除了防止丟失修改和讀“臟”數(shù)據(jù)之外,還進(jìn)一步防止了不可重復(fù)讀。

上述三級(jí)協(xié)議的主要區(qū)別在于什么操作需要申請(qǐng)加鎖,以及何時(shí)釋放鎖(即鎖的持有時(shí)間)。不同的封鎖協(xié)議使事務(wù)達(dá)到的一致性是不同的,封鎖協(xié)議越高,一致性程度越強(qiáng)。

活鎖和死鎖

  • 活鎖:如果事務(wù)T1封鎖了數(shù)據(jù)R,事務(wù)T2又請(qǐng)求封鎖R,于是T2等待。T3也請(qǐng)求封鎖R,當(dāng)T1釋放了R上的鎖之后系統(tǒng)首先批準(zhǔn)了T3的請(qǐng)求,T2繼續(xù)等待;然后T4又請(qǐng)求封鎖R,T3在釋放R上的鎖之后系統(tǒng)又批準(zhǔn)了T4的請(qǐng)求,T2有可能永遠(yuǎn)等待,這就是活鎖的情形。

    避免活鎖的簡(jiǎn)單方法就是采用先來先服務(wù)的策略。當(dāng)多個(gè)事務(wù)請(qǐng)求封鎖同一數(shù)據(jù)對(duì)象時(shí),封鎖子系統(tǒng)按請(qǐng)求鎖的先后次序?qū)κ聞?wù)進(jìn)行排隊(duì),數(shù)據(jù)對(duì)象上的鎖一旦釋放就批準(zhǔn)批準(zhǔn)申請(qǐng)隊(duì)列中第一個(gè)事務(wù)獲得鎖。

  • 死鎖:事務(wù)T1封鎖了數(shù)據(jù)R1,事務(wù)T2封鎖了數(shù)據(jù)R2;同時(shí),事務(wù)T1請(qǐng)求封鎖R2,因?yàn)門2已經(jīng)封鎖了R2,所以T1只能等待。T2也請(qǐng)求封鎖R1,由于R1被T1封鎖了,R2也只能等待。由于它們互相等待,T1和T2兩個(gè)事務(wù)永遠(yuǎn)也不能結(jié)束,于是就形成了死鎖。

    目前數(shù)據(jù)庫(kù)解決死鎖的問題主要有兩類方法:

    一. 死鎖的預(yù)防

    ? 在數(shù)據(jù)庫(kù)中,產(chǎn)生死鎖的原因是兩個(gè)或多個(gè)事務(wù)都已經(jīng)封鎖了一些數(shù)據(jù)對(duì)象,然后又都請(qǐng)求對(duì)已被事務(wù)封鎖的對(duì)象加鎖,從而出現(xiàn)死鎖。防止死鎖的發(fā)生其實(shí)就是要破壞產(chǎn)生死鎖的條件。預(yù)防死鎖發(fā)生通常有以下兩種方法。

    • 一次封鎖法:一次封鎖法要求每個(gè)事務(wù)必須一次將所有要使用的數(shù)據(jù)全部加鎖,否則就不能繼續(xù)執(zhí)行下去。一次封鎖法雖然可以有效防止死鎖的發(fā)生,但是增加了鎖的粒度,從而降低了系統(tǒng)的并發(fā)性。并且數(shù)據(jù)庫(kù)是不斷變化的,所以事先很難精確地確定每個(gè)事務(wù)所需進(jìn)行加鎖的對(duì)象,為此只能擴(kuò)大封鎖范圍,將事務(wù)在執(zhí)行過程中可能需要封鎖的數(shù)據(jù)對(duì)象全部加鎖,這就進(jìn)一步降低了并發(fā)度;

    • 順序封鎖法:順序封鎖法是預(yù)先對(duì)數(shù)據(jù)對(duì)象規(guī)定一個(gè)封鎖順序,所有事務(wù)都按這個(gè)順序?qū)嵤┓怄i。例如在B樹結(jié)構(gòu)的索引中,可規(guī)定封鎖的順序必須是從根節(jié)點(diǎn)開始,然后是下一級(jí)的子節(jié)點(diǎn),逐級(jí)封鎖。順序封鎖法可以有效地避免死鎖,但是要實(shí)現(xiàn)順序封鎖法十分的困難,因?yàn)楹茈y事先確定每一個(gè)事務(wù)要封鎖哪些對(duì)象,因此也就很難按規(guī)定的順序去實(shí)施加鎖。

      由此可見數(shù)據(jù)庫(kù)中不適合預(yù)防死鎖,只適合進(jìn)行死鎖的診斷與解除

    二、死鎖的診斷與解除

    ? 數(shù)據(jù)庫(kù)系統(tǒng)中診斷死鎖的方法與操作系統(tǒng)類似,一般使用超時(shí)法或事務(wù)等待圖法

    • 超時(shí)法:如果一個(gè)事務(wù)的等待時(shí)間超過了規(guī)定的時(shí)限,那么就認(rèn)為其發(fā)生了死鎖。超時(shí)法實(shí)現(xiàn)簡(jiǎn)單,但其不足也十分明顯,一是有可能誤判了死鎖,如事務(wù)因?yàn)槠渌蚨沟却龝r(shí)間超過時(shí)限,系統(tǒng)就會(huì)誤認(rèn)為發(fā)生了死鎖;二是若時(shí)限設(shè)置得太長(zhǎng),則不能及時(shí)發(fā)現(xiàn)死鎖。
    • 事務(wù)等待圖法:事務(wù)等待圖是一個(gè)有向圖G=(T,U),T為結(jié)點(diǎn)的集合,每個(gè)結(jié)點(diǎn)表示正在運(yùn)行的事務(wù);U為邊的集合,每條邊表示事務(wù)等待的情況。若T1等待T2,則在T1,T2之間畫一條有向邊,從T1指向T2。事務(wù)等待圖動(dòng)態(tài)地反應(yīng)了所有事務(wù)的等待情況。并發(fā)控制子系統(tǒng)周期性(比如每隔數(shù)秒)生成事務(wù)等待圖,并進(jìn)行檢測(cè)。如果發(fā)現(xiàn)圖中存在回路,則表示系統(tǒng)中出現(xiàn)了死鎖。

    數(shù)據(jù)庫(kù)管理系統(tǒng)的并發(fā)控制系統(tǒng)一旦檢測(cè)到系統(tǒng)中存在死鎖,就要設(shè)法解除。通常采用的方法是選擇一個(gè)處理死鎖代價(jià)最小的事務(wù),將其撤銷,釋放此事務(wù)持有的所有的鎖,使其他事務(wù)得以繼續(xù)運(yùn)行下去。當(dāng)然,對(duì)撤銷的事務(wù)所進(jìn)行的數(shù)據(jù)修改必須加以恢復(fù)。

兩段鎖協(xié)議

  • 在對(duì)任何數(shù)據(jù)進(jìn)行讀、寫操作之前,首先要申請(qǐng)并獲得對(duì)該數(shù)據(jù)的封鎖;
  • 在釋放一個(gè)鎖之后,事務(wù)不再申請(qǐng)和獲得任何其他封鎖。

所謂兩段鎖的含義是,事務(wù)分為兩個(gè)階段:第一個(gè)階段是獲得封鎖,也稱為拓展階段,在這個(gè)階段,事務(wù)可以申請(qǐng)獲得任何數(shù)據(jù)項(xiàng)上的任何類型的鎖,但是不能釋放鎖;第二個(gè)階段是釋放封鎖,也稱為收縮階段,在這個(gè)階段,事務(wù)可以釋放任何數(shù)據(jù)項(xiàng)上的任何類型的鎖,但是不能再申請(qǐng)任何鎖。

樂觀鎖與悲觀鎖

  • 樂觀鎖(Optimistic Lock)相對(duì)于悲觀鎖而言,樂觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè),如果發(fā)現(xiàn)沖突了,則讓返回用戶錯(cuò)誤的信息,讓用戶決定如何去做。

    ? 樂觀鎖的實(shí)現(xiàn)方式

    1. 使用數(shù)據(jù)版本機(jī)制實(shí)現(xiàn),這是樂觀鎖最常用的一種實(shí)現(xiàn)方式。數(shù)據(jù)版本即為數(shù)據(jù)增加一個(gè)版本表示,一般是通過為數(shù)據(jù)表增加一個(gè)數(shù)字類型的“version”字段來實(shí)現(xiàn)。當(dāng)讀取數(shù)據(jù)的時(shí)候?qū)ersion字段的值一并讀出,數(shù)據(jù)每更新一次對(duì)此version加一。當(dāng)提交更新的時(shí)候,判斷數(shù)據(jù)表對(duì)應(yīng)記錄的當(dāng)前版本信息與之前取出來的進(jìn)行對(duì)比。如果數(shù)據(jù)表當(dāng)前版本號(hào)與之前取出來的version的值相等,則予以更新否則認(rèn)為是過期數(shù)據(jù);
    2. 樂觀鎖的第二種實(shí)現(xiàn)方式和第一種差不多,同樣是在需要樂觀鎖控制的數(shù)據(jù)表中增加一個(gè)時(shí)間戳(timestamp),和上面的version類似,也是在更新提交的時(shí)候檢查當(dāng)前數(shù)據(jù)庫(kù)中數(shù)據(jù)的時(shí)間戳和更新前取到的時(shí)間戳進(jìn)行對(duì)比,如果一致則正常進(jìn)行接下來的操作,否則就是版本沖突認(rèn)為是過期數(shù)據(jù)。

其實(shí)樂觀鎖機(jī)制就是應(yīng)用到了CAS(Compare And Set)思想,在某些情況下可以減少對(duì)數(shù)據(jù)庫(kù)、關(guān)系或者是記錄的加鎖,提高并發(fā)性能。

  • 悲觀鎖(Pessimistic Lock)需要使用到數(shù)據(jù)庫(kù)的鎖機(jī)制。

在實(shí)際生產(chǎn)環(huán)境中,如果并發(fā)量不大并且不允許臟讀,我們就可以使用悲觀鎖解決并發(fā)問題;但如果系統(tǒng)的并發(fā)非常大的話,悲觀鎖定會(huì)帶來非常大的性能問題,所以我們就要選擇樂觀鎖定的方法。

分庫(kù)分表

在網(wǎng)站剛剛搭建的時(shí)候我們常常只使用一臺(tái)數(shù)據(jù)庫(kù)服務(wù)器??墒请S著業(yè)務(wù)的發(fā)展,數(shù)據(jù)庫(kù)中的數(shù)據(jù)不斷增加,單臺(tái)服務(wù)器逐漸不能滿足我們?nèi)粘5纳a(chǎn)需求了,此時(shí)我們可以考慮對(duì)數(shù)據(jù)庫(kù)進(jìn)行拆分。分庫(kù)分表的目的就是要把一個(gè)數(shù)據(jù)庫(kù)切分成多個(gè)部分放到不同的數(shù)據(jù)庫(kù)上,從而緩解單一數(shù)據(jù)庫(kù)的性能問題。(這里暫時(shí)先不考慮讀寫分離,只討論分庫(kù)分表)。

數(shù)據(jù)庫(kù)的切分主要分為兩個(gè)方面:水平切分和垂直切分。不太嚴(yán)格地講,如果數(shù)據(jù)庫(kù)是因?yàn)楸矶喽鴶?shù)據(jù)多,這時(shí)候適合使用垂直切分,即把關(guān)系緊密(比如同一模塊)的表切分出來放在一個(gè)數(shù)據(jù)庫(kù)上。如果表并不多,但是每張表的數(shù)據(jù)非常多,這時(shí)候適合水平切分,即把表的數(shù)據(jù)按某種規(guī)則(比如說ID散列)切分到多個(gè)數(shù)據(jù)庫(kù)上?,F(xiàn)實(shí)當(dāng)中更多是這兩種情況混雜在一起,這時(shí)候需要根據(jù)實(shí)際情況做出選擇,也可能會(huì)綜合使用垂直切分與水平切分,從而將原有數(shù)據(jù)庫(kù)切分成類似矩陣一樣可以無限擴(kuò)充的數(shù)據(jù)庫(kù)陣列。

  • 垂直切分

    垂直切分最大的特點(diǎn)就是規(guī)則簡(jiǎn)單,實(shí)施起來也比較方便,尤其適合各業(yè)務(wù)之間耦合度非常低、相互影響非常小并且業(yè)務(wù)邏輯非常清晰的系統(tǒng)。在這種系統(tǒng)中,可以很容易做到將不同業(yè)務(wù)模塊所使用的表拆分到不同的數(shù)據(jù)庫(kù)中。根據(jù)不同的表來進(jìn)行拆分,對(duì)應(yīng)用程序的影響也更小,拆分規(guī)則也會(huì)比較清晰。

  • 水平切分

    水平切分相對(duì)于垂直切分來說略微復(fù)雜,因?yàn)樵谒角蟹种兴帉⑼粋€(gè)表中的不同數(shù)據(jù)拆分到不同的數(shù)據(jù)庫(kù)當(dāng)中。對(duì)于應(yīng)用程序來說水平切分相比垂直切分,規(guī)則更加復(fù)雜,并且后期的數(shù)據(jù)維護(hù)也會(huì)比較麻煩。

    所以對(duì)于垂直切分來說,只要把業(yè)務(wù)邏輯和數(shù)據(jù)表間的關(guān)聯(lián)關(guān)系理清楚了,可以比較方便地進(jìn)行數(shù)據(jù)庫(kù)的分布式部署。然而我們?cè)撊绾未_定水平拆分的拆分規(guī)則呢?要想做到數(shù)據(jù)的水平切分,那么在每一個(gè)表中必須要用冗余字段作為切分依據(jù)和標(biāo)記字段。幸運(yùn)的話,我們可以選用特定表中的ID作為區(qū)分字段,基于此就有如下三種分庫(kù)分表的方式(當(dāng)然還可以有其它的方式):

  1. 號(hào)段分區(qū)

    ID為110000的放入DB1,ID為1000120000的放入DB2,以此類推。

    優(yōu)點(diǎn):可部分遷移

    缺點(diǎn):數(shù)據(jù)分布不均

  2. hash取模區(qū)分區(qū)

    對(duì)ID進(jìn)行hash,然后將得到的hash對(duì)一個(gè)特定的數(shù)字進(jìn)行取模運(yùn)算,比如說hashcode%4假如結(jié)果為0就放入DB1,結(jié)果為1就放入DB2,以此類推。

    優(yōu)點(diǎn):數(shù)據(jù)分布均勻

    缺點(diǎn):數(shù)據(jù)遷移的時(shí)候比較麻煩,不能按照機(jī)器性能分?jǐn)倲?shù)據(jù)

  3. 在認(rèn)證庫(kù)中保存數(shù)據(jù)庫(kù)配置

    建立一個(gè)DB,這個(gè)DB單獨(dú)保存特定ID到DB的映射關(guān)系,每次訪問數(shù)據(jù)庫(kù)的時(shí)候都要先查詢一次這個(gè)數(shù)據(jù)庫(kù),以得到具體的DB信息,然后才能進(jìn)行我們需要的操作。

    優(yōu)點(diǎn):靈活性強(qiáng),一對(duì)一關(guān)系

    缺點(diǎn):每次查詢之前都要多一次查詢,性能大打折扣

但是在進(jìn)行垂直拆分和水平拆分之后,雖然有助于減輕對(duì)數(shù)據(jù)庫(kù)的壓力,但是一系列的問題也隨之而來

  • 垂直拆分會(huì)帶來如下影響:

    1. 單機(jī)的ACID被打破了。數(shù)據(jù)到了多機(jī)之后,原來在單機(jī)通過事務(wù)來進(jìn)行的處理邏輯會(huì)受到很大的影響。我們面臨的選擇是:要么放棄原來的單機(jī)事務(wù),修改實(shí)現(xiàn),要么引入分布式事務(wù);
    2. 一些Join操作會(huì)變得比較困難,因?yàn)閿?shù)據(jù)可能已經(jīng)在兩個(gè)數(shù)據(jù)庫(kù)中了,所以不能很方便地利用數(shù)據(jù)庫(kù)自身的Join了,需要應(yīng)用或者其他方式解決;
    3. 靠外鍵去進(jìn)行約束的場(chǎng)景會(huì)受到影響。
  • 水平拆分會(huì)帶來如下影響:

    1. 同樣可能會(huì)有ACID被打破的情況;
    2. 同樣有可能有Join操作被影響的情況;
    3. 靠外鍵去進(jìn)行約束的場(chǎng)景會(huì)有影響;
    4. 依賴單庫(kù)的自增序列生成唯一ID會(huì)受影響;
    5. 針對(duì)單個(gè)邏輯意義上的表的查詢要跨庫(kù)了。

    針對(duì)以上問題的解決方案:

    1. 分布式事務(wù)問題:使用兩階段提交協(xié)議。我們?cè)趩螏?kù)上完成相關(guān)的數(shù)據(jù)操作之后,就會(huì)直接提交或者回滾,而在分布式系統(tǒng)中,在提交之前增加了準(zhǔn)備的階段,所以稱為兩階段提交。事務(wù)在第一階段對(duì)資源進(jìn)行準(zhǔn)備,如果在準(zhǔn)備階段有一個(gè)資源失敗,那么在第二階段的處理就是回滾所有資源,否則進(jìn)行Commit操作。但是在實(shí)際應(yīng)用當(dāng)中,由于事務(wù)管理器自身的穩(wěn)定性、可用性的影響,以及網(wǎng)絡(luò)通信中可能產(chǎn)生的問題,出現(xiàn)的情況會(huì)復(fù)雜很多。此外,事務(wù)管理器在多個(gè)資源之間進(jìn)行協(xié)調(diào),它自身要進(jìn)行很多日志記錄的工作。網(wǎng)絡(luò)上的交互次數(shù)的增多以及引入事務(wù)管理器的開銷,是使用兩階段提交協(xié)議使得分布式事務(wù)的開銷增大的兩個(gè)方面。因此,在進(jìn)行垂直拆分和水平拆分后,需要想清楚是否一定要引入兩階段的分布式事務(wù),在必要的情況下才建議使用。

    2. 多機(jī)Sequence問題:當(dāng)轉(zhuǎn)變?yōu)樗椒謳?kù)時(shí),原來單庫(kù)的Sequence以及ID的做法需要改變。我們需要從連續(xù)性唯一性兩方面來考慮問題。如果只從唯一性考慮,我們可以參考UUID的生成方式,或者根據(jù)自己的業(yè)務(wù)情況使用各個(gè)種子(不同維度的標(biāo)識(shí),例如IP、MAC、機(jī)器名、時(shí)間、本機(jī)計(jì)數(shù)器等因素)來生成唯一的ID。這樣生成的ID雖然保證了唯一性,但在整個(gè)分布式系統(tǒng)中的連續(xù)性不好。接下來看看連續(xù)性。這里的連續(xù)性指的是在整個(gè)分布式環(huán)境中生成的ID的連續(xù)性。在單機(jī)環(huán)境中,其實(shí)就是一個(gè)單點(diǎn)來完成這個(gè)任務(wù),在分布式系統(tǒng)中,我們可以用一個(gè)獨(dú)立的系統(tǒng)來完成這個(gè)工作。

      這里提供一個(gè)解決方案:

      我們把所有ID集中放在一個(gè)地方進(jìn)行管理,對(duì)每個(gè)ID序列獨(dú)立管理,每臺(tái)機(jī)器使用ID時(shí)都從這個(gè)ID生成器上進(jìn)行獲取。

      可能涉及的問題:

      ? ① 性能問題。每次都從遠(yuǎn)程獲取ID會(huì)有資源損耗。加入是一次取一段ID,然后緩存到本地,這樣就不需要每次都去遠(yuǎn)程機(jī)器獲取ID了,但是如果應(yīng)用取了一段ID之后宕機(jī)了,那就浪費(fèi)了一段可用ID;

      ② 生成器的穩(wěn)定性問題。ID生成器作為一個(gè)無狀態(tài)的集群存在,其可用性要靠整個(gè)集群來保證。

      ? ③存儲(chǔ)問題。底層存儲(chǔ)的選擇空間大,需要根據(jù)不同類型進(jìn)行對(duì)應(yīng)的容災(zāi)方案。有兩種方式可以解決這個(gè)問題:第一,我們?cè)诘讓邮褂靡粋€(gè)獨(dú)立的存儲(chǔ)來記錄每個(gè)ID序列當(dāng)前的最大值,并控制并發(fā)更新,這樣一來ID生成器的邏輯就很簡(jiǎn)單了。第二種是直接把ID生成器舍去,把相關(guān)的邏輯放到需要ID的應(yīng)用本身。不過我們不希望生成器之間還有通信,因此數(shù)據(jù)的ID并不是嚴(yán)格按照進(jìn)入數(shù)據(jù)庫(kù)的順序而增大的,在管理上也需要有額外的功能,這是需要進(jìn)行權(quán)衡的地方。

      1. 跨庫(kù)Join問題:**在分庫(kù)之后,如果需要Join的數(shù)據(jù)還放在一個(gè)庫(kù)里面,那就可以進(jìn)行Join操作。例如,我們根據(jù)用戶id進(jìn)行用戶相關(guān)信息的分庫(kù),那么如果查詢某個(gè)用戶在不同表中的一些關(guān)聯(lián)信息,還是可以進(jìn)行Join操作的。如果需要Join的數(shù)據(jù)已經(jīng)分布在多個(gè)庫(kù)當(dāng)中,那就需要完成跨庫(kù)Join的操作,這會(huì)比較麻煩,解決思路有以下幾種:

        ①在應(yīng)用層把原來數(shù)據(jù)庫(kù)中的Join操作分成多次數(shù)據(jù)庫(kù)操作。舉個(gè)例子,我們有用戶基本信息表,也有用戶出售的商品的信息表,需求是查出來登記手機(jī)號(hào)為152XXXXXXX的用戶在售的商品總數(shù)。這在單庫(kù)時(shí)用一個(gè)SQL的Join就解決了,而如果商品信息與用戶信息分離了,我們就需要現(xiàn)在應(yīng)用層根據(jù)手機(jī)號(hào)找到用戶id,然后再根據(jù)用戶id找到相關(guān)的商品總數(shù)。

        ? ②數(shù)據(jù)冗余。也就是對(duì)一些常用的信息進(jìn)行冗余,這樣就可以把原來需要Join的操作變?yōu)閱伪聿樵?。這需要結(jié)合具體業(yè)務(wù)場(chǎng)景。

        ? ③借助外部系統(tǒng)(例如搜索引擎)解決一些跨庫(kù)問題。

      2. 外鍵約束問題:外鍵約束的問題比較難解決,不能完全依賴數(shù)據(jù)庫(kù)本身來完成之前的功能。如果需要對(duì)分庫(kù)后的單庫(kù)做外鍵約束,就需要分庫(kù)后每個(gè)單庫(kù)的數(shù)據(jù)是內(nèi)聚的,否則就只能靠應(yīng)用層的判斷了、容錯(cuò)方式了。

SQL優(yōu)化

我們可以從四個(gè)方面來對(duì)數(shù)據(jù)庫(kù)進(jìn)行優(yōu)化:SQL及索引、數(shù)據(jù)庫(kù)表結(jié)構(gòu)、系統(tǒng)配置、硬件。這四種優(yōu)化成本依次升高,但是優(yōu)化效果卻依次降低。所以我們對(duì)數(shù)據(jù)庫(kù)的優(yōu)化應(yīng)該更關(guān)注成本低效果好的SQL及索引優(yōu)化和數(shù)據(jù)庫(kù)表結(jié)構(gòu)優(yōu)化。

優(yōu)化手段:

  • 開啟慢查詢?nèi)罩緦?duì)有效率問題的SQL進(jìn)行監(jiān)控;
  • 使用explain查看SQL的執(zhí)行計(jì)劃;
  • 建立合適的索引,盡量選擇在where從句、group by從句、order by從句、no從句中出現(xiàn)的列,索引字段越小越好,如果定義聯(lián)合索引要把離散度大的列放在前面;
  • 盡可能使用小的數(shù)據(jù)類型,盡量使用簡(jiǎn)單的數(shù)據(jù)類型(比如在MySQL中int要比varchar處理起來更簡(jiǎn)單),盡可能使用not null字段,少使用text等大的數(shù)據(jù)類型,一定要用的時(shí)候考慮分表;
  • 范式化
  • 表的垂直拆分,水平拆分。

參考資料:

《數(shù)據(jù)庫(kù)系統(tǒng)概論》(第5版)

《大型網(wǎng)站系統(tǒng)與Java中間件實(shí)踐》

http://www.cnblogs.com/zhongxinWang/p/4262650.html

http://blog.csdn.net/bluishglc/article/details/6161475

https://www.zhihu.com/question/30272728

http://www.cnblogs.com/fjdingsd/p/5273008.html

http://bbs.csdn.net/topics/120024254

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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