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

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語句的消耗起到非常關鍵的作用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容