面試資料系列(2): 資料庫

weixin_33861800發表於2017-10-25

1. ACID

  • 原子性(Atomicity)
      原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,因此事務的操作如果成功就必須要完全應用到資料庫,如果操作失敗則不能對資料庫有任何影響。
  • 一致性(Consistency)
      一致性是指事務必須使資料庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。
      拿轉賬來說,假設使用者A和使用者B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個使用者的錢相加起來應該還得是5000,這就是事務的一致性。
  • 隔離性(Isolation)
      隔離性是當多個使用者併發訪問資料庫時,比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所干擾,多個併發事務之間要相互隔離。
      即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始之前就已經結束,要麼在T1結束之後才開始,這樣每個事務都感覺不到有其他事務在併發地執行。
      關於事務的隔離性資料庫提供了多種隔離級別,稍後會介紹到。
  • 永續性(Durability)
      永續性是指一個事務一旦被提交了,那麼對資料庫中的資料的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。

2. 隔離問題

  • 髒讀
    髒讀是指在一個事務處理過程裡讀取了另一個未提交的事務中的資料。
      當一個事務正在多次修改某個資料,而在這個事務中這多次的修改都還未提交,這時一個併發的事務來訪問該資料,就會造成兩個事務得到的資料不一致。例如:使用者A向使用者B轉賬100元,對應SQL命令如下
update account set money=money+100 where name=’B’;  (此時A通知B)
update account set money=money - 100 where name=’A’;

當只執行第一條SQL時,A通知B檢視賬戶,B發現確實錢已到賬(此時即發生了髒讀),而之後無論第二條SQL是否執行,只要該事務不提交,則所有操作都將回滾,那麼當B以後再次檢視賬戶時就會發現錢其實並沒有轉。

  • 不可重複讀
    不可重複讀是指在對於資料庫中的某個資料,一個事務範圍內多次查詢卻返回了不同的資料值,這是由於在查詢間隔,被另一個事務修改並提交了。
      例如事務T1在讀取某一資料,而事務T2立馬修改了這個資料並且提交事務給資料庫,事務T1再次讀取該資料就得到了不同的結果,傳送了不可重複讀。
      不可重複讀和髒讀的區別是,髒讀是某一事務讀取了另一個事務未提交的髒資料,而不可重複讀則是讀取了前一事務提交的資料。
      在某些情況下,不可重複讀並不是問題,比如我們多次查詢某個資料當然以最後查詢得到的結果為主。但在另一些情況下就有可能發生問題,例如對於同一個資料A和B依次查詢就可能不同,A和B就可能打起來了……

  • 虛讀(幻讀)
      幻讀是事務非獨立執行時發生的一種現象。例如事務T1對一個表中所有的行的某個資料項做了從“1”修改為“2”的操作,這時事務T2又對這個表中插入了一行資料項,而這個資料項的數值還是為“1”並且提交給資料庫。而操作事務T1的使用者如果再檢視剛剛修改的資料,會發現還有一行沒有修改,其實這行是從事務T2中新增的,就好像產生幻覺一樣,這就是發生了幻讀。
      幻讀和不可重複讀都是讀取了另一條已經提交的事務(這點就髒讀不同),所不同的是不可重複讀查詢的都是同一個資料項,而幻讀針對的是一批資料整體(比如資料的個數)。

3. 隔離級別

MySQL資料庫為我們提供的四種隔離級別:
  ① Serializable (序列化):可避免髒讀、不可重複讀、幻讀的發生。
  ② Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生。
  ③ Read committed (讀已提交):可避免髒讀的發生。
  ④ Read uncommitted (讀未提交):最低階別,任何情況都無法保證。

以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,當然級別越高,執行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(類似於Java多執行緒中的鎖)使得其他的執行緒只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。在MySQL資料庫中預設的隔離級別為Repeatable read (可重複讀)。
  在MySQL資料庫中,支援上面四種隔離級別,預設的為Repeatable read (可重複讀);而在Oracle資料庫中,只支援Serializable (序列化)級別和Read committed (讀已提交)這兩種級別,其中預設的為Read committed級別。

4. 索引

索引加快了查詢速度,但是要付出代價。
比如表的插入和刪除速度會減慢,因為需要更新索引。
如果表需要不斷更新,索引很可能會導致performance問題。
還有空間代價。索引會佔用記憶體或磁碟空間。
單個索引比表小,因為它不存所有的表資料,而是存相應的指標。
但表越大,索引通常也會跟著變大。

索引的型別
A)聚集索引,表資料按照索引的順序來儲存的。對於聚集索引,葉子結點即儲存了真實的資料行,不再有另外單獨的資料頁。
B)非聚集索引,表資料儲存順序與索引順序無關。對於非聚集索引,葉結點包含索引欄位值及指向資料頁資料行的邏輯指標,該層緊鄰資料頁,其行數量與資料錶行資料量一致。
在一張表上只能建立一個聚集索引,因為真實資料的物理順序只可能是一種。如果一張表沒有聚集索引,那麼它被稱為“堆集”(Heap)。這樣的表中的資料行沒有特定的順序,所有的新行將被新增的表的末尾位置。

  • Hash索引
    找姓Smith的人,我們可以建一個hash表。hash表的key就是last_name,value可以是指向資料行的指標。
    這類索引就叫hash索引。很多資料庫都支援這裡索引。
    但是它不常用。為什麼?
    考慮另一個查詢:找所有45歲以下的人。hash索引可以處理等於關係,但不處理小於或大於關係。
    給你2個的hash索引,它無法判斷那個值更大,只能判斷它們是否相等。

  • bitmap索引
    它的讀取速度很快,但是比較佔儲存空間。適用於值稀疏分佈的列。

  • B-tree索引
    它允許對數階複雜度的查詢、插入和刪除。
    和hash索引不同之處在於,它存的資料是有序的,這樣能處理小於、大於和字首的查詢。
    非聚集索引與聚集索引相比:
    A)葉子結點並非資料結點
    B)葉子結點為每一真正的資料行儲存一個“鍵-指標”對
    C)葉子結點中還儲存了一個指標偏移量,根據頁指標及指標偏移量可以定位到具體的資料行。
    D)類似的,在除葉結點外的其它索引結點,儲存的也是類似的內容,只不過它是指向下一級的索引頁的。
    聚集索引是一種稀疏索引,資料頁上一級的索引頁儲存的是頁指標,而不是行指標。而對於非聚集索引,則是密集索引,在資料頁的上一級索引頁它為每一個資料行儲存一條索引記錄。
    對於根與中間級的索引記錄,它的結構包括:
    A)索引欄位值
    B)RowId(即對應資料頁的頁指標+指標偏移量)。在高層的索引頁中包含RowId是為了當索引允許重複值時,當更改資料時精確定位資料行。
    C)下一級索引頁的指標
    對於葉子層的索引物件,它的結構包括:
    A)索引欄位值
    B)RowId

  • B+Tree 與BTree區別
    結構上

    • B樹中關鍵字集合分佈在整棵樹中,葉節點中不包含任何關鍵字資訊,而B+樹關鍵字集合分佈在葉子結點中,非葉節點只是葉子結點中關鍵字的索引;
    • B樹中任何一個關鍵字只出現在一個結點中,而B+樹中的關鍵字必須出現在葉節點中,也可能在非葉結點中重複出現;

    效能上

    • B+樹比B樹更適合實際應用中作業系統的檔案索引和資料庫索引
    • B+樹的磁碟讀寫代價更低。B+樹的內部結點並沒有指向關鍵字具體資訊的指標,其內部結點比B樹小,盤塊能容納的結點中關鍵字數量更多,一次性讀入記憶體中可以查詢的關鍵字也就越多,相對的,IO讀寫次數也就降低了。而IO讀寫次數是影響索引檢索效率的最大因素。
    • B+樹的查詢效率更加穩定。B樹搜尋有可能會在非葉子結點結束,越靠近根節點的記錄查詢時間越短,只要找到關鍵字即可確定記錄的存在,其效能等價於在關鍵字全集內做一次二分查詢。而在B+樹中,順序檢索比較明顯,隨機檢索時,任何關鍵字的查詢都必須走一條從根節點到葉節點的路,所有關鍵字的查詢路徑長度相同,導致每一個關鍵字的查詢效率相當。
    • (資料庫索引採用B+樹的主要原因是)B-樹在提高了磁碟IO效能的同時並沒有解決元素遍歷的效率低下的問題。B+樹的葉子節點使用指標順序連線在一起,只要遍歷葉子節點就可以實現整棵樹的遍歷。而且在資料庫中基於範圍的查詢是非常頻繁的,而B樹不支援這樣的操作,或者說效率太低。

5. 正規化

  • 第一正規化(1NF)是指在關係模型中,對域新增的一個規範要求,所有的域都應該是原子性的,即資料庫表的每一列都是不可分割的原子資料項,而不能是集合,陣列,記錄等非原子資料項。即實體中的某個屬性有多個值時,必須拆分為不同的屬性。在符合第一正規化(1NF)表中的每個域值只能是實體的一個屬性或一個屬性的一部分。簡而言之,第一正規化就是無重複的域。

  • 第二正規化(2NF)非碼屬性必須完全依賴於候選碼,要求資料庫表中的每個例項或記錄必須可以被唯一地區分。

  • 第三正規化(3NF),非主屬性不依賴於其它非主屬性(在2NF基礎上消除傳遞依賴),不得有冗餘

6. 引擎對比

  • InnoDB:
    1.ACID事務支援、系統崩潰修復能力和多版本併發控制(即MVCC Multi-Version Concurrency Control)的行級鎖;
    2.支援自增長列(auto_increment),自增長列的值不能為空;
    3.該引擎儲存引擎支援外來鍵(foreign key)

  • MyISAM:
    1.索引和欄位管理;
    2.MyISAM強調了快速讀取操作,主要用於高負載的select,這可能也是MySQL深受Web開發的主要原因:在Web開發中進行的大量資料操作都是讀取操作,所以大多數虛擬主機提供商和Internet平臺提供商(Internet Presence Provider,IPP)只允許使用MyISAM格式
    MyISAM型別的表支援三種不同的儲存結構:靜態型、動態型、壓縮型。
    靜態型:指定義的表列的大小是固定(即不含有:xblob、xtext、varchar等長度可變的資料型別),這樣MySQL就會自動使用靜態MyISAM格式。使用靜態格式的表的效能比較高,因為在維護和訪問以預定格式儲存資料時需要的開銷很低;但這種高效能是以空間為代價換來的,因為在定義的時候是固定的,所以不管列中的值有多大,都會以最大值為準,佔據了整個空間。
    動態型:如果列(即使只有一列)定義為動態的(xblob, xtext, varchar等資料型別),這時MyISAM就自動使用動態型,雖然動態型的表佔用了比靜態型表較少的空間,但帶來了效能的降低,因為如果某個欄位的內容發生改變則其位置很可能需要移動,這樣就會導致碎片的產生,隨著資料變化的增多,碎片也隨之增加,資料訪問效能會隨之降低。
    對於因碎片增加而降低資料訪問性這個問題,有兩種解決辦法:
    a、儘可能使用靜態資料型別;
    b、經常使用optimize table table_name語句整理表的碎片,恢復由於表資料的更新和刪除導致的空間丟失。如果儲存引擎不支援 optimize table table_name則可以轉儲並重新載入資料,這樣也可以減少碎片;
    壓縮型:如果在資料庫中建立在整個生命週期內只讀的表,則應該使用MyISAM的壓縮型表來減少空間的佔用。

7.執行順序

select過程:from->where->group by->having->order by->limit

8.Join

三類:
1.INNER JOIN(內連線,或等值連線):取得兩個表中存在連線匹配關係的記錄。
2.LEFT JOIN(左連線):取得左表(table1)完全記錄,即是右表(table2)並無對應匹配記錄。
3.RIGHT JOIN(右連線):與 LEFT JOIN 相反,取得右表(table2)完全記錄,即是左表(table1)並無匹配對應記錄。
注意:mysql不支援Full join,不過可以通過UNION 關鍵字來合併 LEFT JOIN 與 RIGHT JOIN來模擬FULL join.

Mysql Join內部實現:NestedLoopJoin
參考:http://www.cnblogs.com/ggjucheng/archive/2012/11/15/2772148.html
顧名思義,NestedLoopJoin實際上就是通過驅動表的結果集作為迴圈基礎資料,然後一條一條的通過該結果集中的資料作為過濾條件到下一個表中查詢資料,然後合併結果。如果還有第三個參與Join,則再通過前兩個表的Join結果集作為迴圈基礎資料,再一次通過迴圈查詢條件到第三個表中查詢資料,如此往復。

Join優化:

  • 儘可能減少Join語句中的NestedLoop的迴圈總次數;如何減少NestedLoop的迴圈總次數?最有效的辦法只有一個,那就是讓驅動表的結果集儘可能的小,這也正是優化基本原則之一“永遠用小結果集驅動大的結果集”。

  • 保證被驅動表上Join條件欄位已經被索引的目的,正是針對上面兩點的考慮,只有讓被驅動表的Join條件欄位被索引了,才能保證迴圈中每次查詢都能夠消耗較少的資源,這也正是優化內層迴圈的實際優化方法。

  • 當無法保證被驅動表的Join條件欄位被索引且記憶體資源充足的前提下,不要太吝惜JoinBuffer的設定;當在某些特殊的環境中,我們的Join必須是All,Index,range或者是index_merge型別的時候,JoinBuffer就會派上用場了。在這種情況下,JoinBuffer的大小將對整個Join語句的消耗起到非常關鍵的作用。

相關文章