數據庫面試問題集錦

摘要:

  本文對面試/筆試過程中經常會被問到的一些關於數據庫(MySQL)的問題進行了梳理和總結,包括數據庫索引、數據庫鎖、數據庫事務和MySQL優化等基礎知識點,一方面方便自己溫故知新,另一方面也希望爲找工作的同學們提供一個複習參考。關於這塊內容的初步瞭解和掌握,大家可以閱讀《深入淺出MySQL-數據庫開發優化與管理》和《數據庫系統概念(美 Abraham Silbersch 著;楊冬青 李紅燕 唐世 譯)》兩本書。


版權聲明:

  本文原創作者:書呆子Rico 
  作者博客地址:http://blog.csdn.net/justloveyou_/


1、數據庫範式

  • 第一範式:列不可分,eg:【聯繫人】(姓名,性別,電話),一個聯繫人有家庭電話和公司電話,那麼這種表結構設計就沒有達到 1NF;

  • 第二範式:有主鍵,保證完全依賴。eg:訂單明細表【OrderDetail】(OrderID,ProductID,UnitPrice,Discount,Quantity,ProductName),Discount(折扣),Quantity(數量)完全依賴(取決)於主鍵(OderID,ProductID),而 UnitPrice,ProductName 只依賴於 ProductID,不符合2NF;

  • 第三範式:無傳遞依賴(非主鍵列 A 依賴於非主鍵列 B,非主鍵列 B 依賴於主鍵的情況),eg:訂單表【Order】(OrderID,OrderDate,CustomerID,CustomerName,CustomerAddr,CustomerCity)主鍵是(OrderID),CustomerName,CustomerAddr,CustomerCity 直接依賴的是 CustomerID(非主鍵列),而不是直接依賴於主鍵,它是通過傳遞才依賴於主鍵,所以不符合 3NF。


2、數據庫索引

  索引是對數據庫表中一個或多個列的值進行排序的數據結構,以協助快速查詢、更新數據庫表中數據。索引的實現通常使用B_TREE及其變種。索引加速了數據訪問,因爲存儲引擎不會再去掃描整張表得到需要的數據;相反,它從根節點開始,根節點保存了子節點的指針,存儲引擎會根據指針快速尋找數據。

           索引.png-36.9kB

  上圖顯示了一種索引方式。左邊是數據庫中的數據表,有col1和col2兩個字段,一共有15條記錄;右邊是以col2列爲索引列的B_TREE索引,每個節點包含索引的鍵值和對應數據表地址的指針,這樣就可以都過B_TREE在 O(logn) 的時間複雜度內獲取相應的數據,這樣明顯地加快了檢索的速度。


1). 索引的底層實現原理和優化

  在數據結構中,我們最爲常見的搜索結構就是二叉搜索樹和AVL樹(高度平衡的二叉搜索樹,爲了提高二叉搜索樹的效率,減少樹的平均搜索長度)了。然而,無論二叉搜索樹還是AVL樹,當數據量比較大時,都會由於樹的深度過大而造成I/O讀寫過於頻繁,進而導致查詢效率低下,因此對於索引而言,多叉樹結構成爲不二選擇。特別地,B-Tree的各種操作能使B樹保持較低的高度,從而保證高效的查找效率。


(1). B-Tree(平衡多路查找樹)

  B_TREE是一種平衡多路查找樹,是一種動態查找效率很高的樹形結構。B_TREE中所有結點的孩子結點的最大值稱爲B_TREE的階,B_TREE的階通常用m表示,簡稱爲m叉樹。一般來說,應該是m>=3。一顆m階的B_TREE或是一顆空樹,或者是滿足下列條件的m叉樹:

  • 樹中每個結點最多有m個孩子結點;

  • 若根結點不是葉子節點,則根結點至少有2個孩子結點;

  • 除根結點外,其它結點至少有(m/2的上界)個孩子結點;

  • 結點的結構如下圖所示,其中,n爲結點中關鍵字個數,(m/2的上界)-1 <= n <= m-1;di(1<=i<=n)爲該結點的n個關鍵字值的第i個,且di< d(i+1);ci(0<=i<=n)爲該結點孩子結點的指針,且ci所指向的節點的關鍵字均大於或等於di且小於d(i+1);

              B-Tree結點的結構.png-1.7kB

  • 所有的葉結點都在同一層上,並且不帶信息(可以看作是外部結點或查找失敗的結點,實際上這些結點不存在,指向這些結點的指針爲空)。


  下圖是一棵4階B_TREE,4叉樹結點的孩子結點的個數範圍[2,4]。其中,有2個結點有4個孩子結點,有1個結點有3個孩子結點,有5個結點有2個孩子結點。

          4階B_TREE.jpg-24.1kB

  B_TREE的查找類似二叉排序樹的查找,所不同的是B-樹每個結點上是多關鍵碼的有序表,在到達某個結點時,先在有序表中查找,若找到,則查找成功;否則,到按照對應的指針信息指向的子樹中去查找,當到達葉子結點時,則說明樹中沒有對應的關鍵碼。由於B_TREE的高檢索效率,B-樹主要應用在文件系統和數據庫中,對於存儲在硬盤上的大型數據庫文件,可以極大程度減少訪問硬盤次數,大幅度提高數據檢索效率。


(2). B+Tree : InnoDB存儲引擎的索引實現

  B+Tree是應文件系統所需而產生的一種B_TREE樹的變形樹。一棵m階的B+樹和m階的B_TREE的差異在於以下三點:

  • n 棵子樹的結點中含有n個關鍵碼;

  • 所有的葉子結點中包含了全部關鍵碼的信息,及指向含有這些關鍵碼記錄的指針,且葉子結點本身依關鍵碼的大小自小而大的順序鏈接;

  • 非終端結點可以看成是索引部分,結點中僅含有其子樹根結點中最大(或最小)關鍵碼。


  下圖爲一棵3階的B+樹。通常在B+樹上有兩個頭指針,一個指向根節點,另一個指向關鍵字最小的葉子節點。因此可以對B+樹進行兩種查找運算:一種是從最小關鍵字起順序查找,另一種是從根節點開始,進行隨機查找。 
在B+樹上進行隨機查找、插入和刪除的過程基本上與B-樹類似。只是在查找時,若非終端結點上的關鍵碼等於給定值,並不終止,而是繼續向下直到葉子結點。因此,對於B+樹,不管查找成功與否,每次查找都是走了一條從根到葉子結點的路徑。

          一棵3階的B+樹.jpg-29.9kB


(3). 爲什麼說B+-tree比B 樹更適合實際應用中操作系統的文件索引和數據庫索引?

  • B+tree的磁盤讀寫代價更低:B+tree的內部結點並沒有指向關鍵字具體信息的指針(紅色部分),因此其內部結點相對B 樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的需要查找的關鍵字也就越多,相對來說IO讀寫次數也就降低了;

  • B+tree的查詢效率更加穩定:由於內部結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引,所以,任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當;

  • 數據庫索引採用B+樹而不是B樹的主要原因:B+樹只要遍歷葉子節點就可以實現整棵樹的遍歷,而且在數據庫中基於範圍的查詢是非常頻繁的,而B樹只能中序遍歷所有節點,效率太低。


(4). 文件索引和數據庫索引爲什麼使用B+樹?

  文件與數據庫都是需要較大的存儲,也就是說,它們都不可能全部存儲在內存中,故需要存儲到磁盤上。而所謂索引,則爲了數據的快速定位與查找,那麼索引的結構組織要儘量減少查找過程中磁盤I/O的存取次數,因此B+樹相比B樹更爲合適。數據庫系統巧妙利用了局部性原理與磁盤預讀原理,將一個節點的大小設爲等於一個頁,這樣每個節點只需要一次I/O就可以完全載入,而紅黑樹這種結構,高度明顯要深的多,並且由於邏輯上很近的節點(父子)物理上可能很遠,無法利用局部性。最重要的是,B+樹還有一個最大的好處:方便掃庫。B樹必須用中序遍歷的方法按序掃庫,而B+樹直接從葉子結點挨個掃一遍就完了,B+樹支持range-query非常方便,而B樹不支持,這是數據庫選用B+樹的最主要原因。


2). 索引的優點

  • 大大加快數據的檢索速度,這也是創建索引的最主要的原因;

  • 加速表和表之間的連接;

  • 在使用分組和排序子句進行數據檢索時,同樣可以顯著減少查詢中分組和排序的時間;

  • 通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性;


3). 什麼情況下設置了索引但無法使用?

  • 以“%(表示任意0個或多個字符)”開頭的LIKE語句,模糊匹配;

  • OR語句前後沒有同時使用索引;

  • 數據類型出現隱式轉化(如varchar不加單引號的話可能會自動轉換爲int型);

  • 對於多列索引,必須滿足 最左匹配原則 (eg:多列索引col1、col2和col3,則 索引生效的情形包括 col1或col1,col2或col1,col2,col3)。


4). 什麼樣的字段適合創建索引?

  • 經常作查詢選擇的字段

  • 經常作表連接的字段

  • 經常出現在order by, group by, distinct 後面的字段


5). 創建索引時需要注意什麼?

  • 非空字段:應該指定列爲NOT NULL,除非你想存儲NULL。在mysql中,含有空值的列很難進行查詢優化,因爲它們使得索引、索引的統計信息以及比較運算更加複雜。你應該用0、一個特殊的值或者一個空串代替空值;

  • 取值離散大的字段:(變量各個取值之間的差異程度)的列放到聯合索引的前面,可以通過count()函數查看字段的差異值,返回值越大說明字段的唯一值越多字段的離散程度高;

  • 索引字段越小越好:數據庫的數據存儲以頁爲單位一頁存儲的數據越多一次IO操作獲取的數據越大效率越高。


6). 索引的缺點

  • 時間方面:創建索引和維護索引要耗費時間,具體地,當對錶中的數據進行增加、刪除和修改的時候,索引也要動態的維護,這樣就降低了數據的維護速度;

  • 空間方面:索引需要佔物理空間。


7). 索引的分類

  • 普通索引和唯一性索引:索引列的值的唯一性

  • 單個索引和複合索引:索引列所包含的列數

  • 聚簇索引與非聚簇索引:聚簇索引按照數據的物理存儲進行劃分的。對於一堆記錄來說,使用聚集索引就是對這堆記錄進行堆劃分,即主要描述的是物理上的存儲。正是因爲這種劃分方法,導致聚簇索引必須是唯一的。聚集索引可以幫助把很大的範圍,迅速減小範圍。但是查找該記錄,就要從這個小範圍中Scan了;而非聚集索引是把一個很大的範圍,轉換成一個小的地圖,然後你需要在這個小地圖中找你要尋找的信息的位置,最後通過這個位置,再去找你所需要的記錄。


8). 主鍵、自增主鍵、主鍵索引與唯一索引概念區別

主鍵:指字段 唯一不爲空值 的列;

主鍵索引:指的就是主鍵,主鍵是索引的一種,是唯一索引的特殊類型。創建主鍵的時候,數據庫默認會爲主鍵創建一個唯一索引;

自增主鍵:字段類型爲數字、自增、並且是主鍵;

唯一索引:索引列的值必須唯一,但允許有空值。主鍵是唯一索引,這樣說沒錯;但反過來說,唯一索引也是主鍵就錯誤了,因爲唯一索引允許空值,主鍵不允許有空值,所以不能說唯一索引也是主鍵。


9). 主鍵就是聚集索引嗎?主鍵和索引有什麼區別?

  主鍵是一種特殊的唯一性索引,其可以是聚集索引,也可以是非聚集索引。在SQLServer中,主鍵的創建必須依賴於索引,默認創建的是聚集索引,但也可以顯式指定爲非聚集索引。InnoDB作爲MySQL存儲引擎時,默認按照主鍵進行聚集,如果沒有定義主鍵,InnoDB會試着使用唯一的非空索引來代替。如果沒有這種索引,InnoDB就會定義隱藏的主鍵然後在上面進行聚集。所以,對於聚集索引來說,你創建主鍵的時候,自動就創建了主鍵的聚集索引。


3、數據庫事務

  事務是一個不可分割的數據庫操作序列,也是數據庫併發控制的基本單位,其執行的結果必須使數據庫從一種一致性狀態變到另一種一致性狀態。


(1). 事務的特徵

  • 原子性(Atomicity):事務所包含的一系列數據庫操作要麼全部成功執行,要麼全部回滾;

  • 一致性(Consistency):事務的執行結果必須使數據庫從一個一致性狀態到另一個一致性狀態;

  • 隔離性(Isolation):併發執行的事務之間不能相互影響;

  • 持久性(Durability):事務一旦提交,對數據庫中數據的改變是永久性的。


(2). 事務併發帶來的問題

  • 髒讀:一個事務讀取了另一個事務未提交的數據;

  • 不可重複讀:不可重複讀的重點是修改,同樣條件下兩次讀取結果不同,也就是說,被讀取的數據可以被其它事務修改;

  • 幻讀:幻讀的重點在於新增或者刪除,同樣條件下兩次讀出來的記錄數不一樣。


(3). 隔離級別

  隔離級別決定了一個session中的事務可能對另一個session中的事務的影響。ANSI標準定義了4個隔離級別,MySQL的InnoDB都支持,分別是:

  • READ UNCOMMITTED:最低級別的隔離,通常又稱爲dirty read,它允許一個事務讀取另一個事務還沒commit的數據,這樣可能會提高性能,但是會導致髒讀問題;

  • READ COMMITTED:在一個事務中只允許對其它事務已經commit的記錄可見,該隔離級別不能避免不可重複讀問題;

  • REPEATABLE READ:在一個事務開始後,其他事務對數據庫的修改在本事務中不可見,直到本事務commit或rollback。但是,其他事務的insert/delete操作對該事務是可見的,也就是說,該隔離級別並不能避免幻讀問題。在一個事務中重複select的結果一樣,除非本事務中update數據庫。

  • SERIALIZABLE:最高級別的隔離,只允許事務串行執行。

      MySQL默認的隔離級別是REPEATABLE READ。


(4)、mysql的事務支持

  MySQL的事務支持不是綁定在MySQL服務器本身,而是與存儲引擎相關:

  • MyISAM:不支持事務,用於只讀程序提高性能;
  • InnoDB:支持ACID事務、行級鎖、併發;
  • Berkeley DB:支持事務。

4、實踐中如何優化MySQL

  實踐中,MySQL的優化主要涉及SQL語句及索引的優化、數據表結構的優化、系統配置的優化和硬件的優化四個方面,如下圖所示:

              Mysql性能優化-82.5kB


1)、SQL語句及索引的優化

(1). SQL語句的優化

  SQL語句的優化主要包括三個問題,即如何發現有問題的SQL、如何分析SQL的執行計劃以及如何優化SQL,下面將逐一解釋。


a. 怎麼發現有問題的SQL?(通過MySQL慢查詢日誌對有效率問題的SQL進行監控)

  MySQL的慢查詢日誌是MySQL提供的一種日誌記錄,它用來記錄在MySQL中響應時間超過閥值的語句,具體指運行時間超過long_query_time值的SQL,則會被記錄到慢查詢日誌中。long_query_time的默認值爲10,意思是運行10s以上的語句。慢查詢日誌的相關參數如下所示:

        慢查詢日誌相關參數.png-20.4kB

  通過MySQL的慢查詢日誌,我們可以查詢出執行的次數多佔用的時間長的SQL、可以通過pt_query_disgest(一種mysql慢日誌分析工具)分析Rows examine(MySQL執行器需要檢查的行數)項去找出IO大的SQL以及發現未命中索引的SQL,對於這些SQL,都是我們優化的對象。


b. 通過explain查詢和分析SQL的執行計劃

  使用 EXPLAIN 關鍵字可以知道MySQL是如何處理你的SQL語句的,以便分析查詢語句或是表結構的性能瓶頸。通過explain命令可以得到表的讀取順序、數據讀取操作的操作類型、哪些索引可以使用、哪些索引被實際使用、表之間的引用以及每張表有多少行被優化器查詢等問題。當擴展列extra出現Using filesort和Using temporay,則往往表示SQL需要優化了。


c. SQL語句的優化

  • 優化insert語句:一次插入多值;

  • 應儘量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描;

  • 應儘量避免在 where 子句中對字段進行null值判斷,否則將導致引擎放棄使用索引而進行全表掃描;

  • 優化嵌套查詢:子查詢可以被更有效率的連接(Join)替代;

  • 很多時候用 exists 代替 in 是一個好的選擇。


1)、索引優化

  建議在經常作查詢選擇的字段、經常作表連接的字段以及經常出現在order by、group by、distinct 後面的字段中建立索引。但必須注意以下幾種可能會引起索引失效的情形:

  • 以“%(表示任意0個或多個字符)”開頭的LIKE語句,模糊匹配;

  • OR語句前後沒有同時使用索引;

  • 數據類型出現隱式轉化(如varchar不加單引號的話可能會自動轉換爲int型);

  • 對於多列索引,必須滿足最左匹配原則(eg,多列索引col1、col2和col3,則 索引生效的情形包括col1或col1,col2或col1,col2,col3)。


2). 數據庫表結構的優化

  數據庫表結構的優化包括選擇合適數據類型、表的範式的優化、表的垂直拆分和表的水平拆分等手段。


(1). 選擇合適數據類型

  • 使用較小的數據類型解決問題;

  • 使用簡單的數據類型(mysql處理int要比varchar容易);

  • 儘可能的使用not null 定義字段;

  • 儘量避免使用text類型,非用不可時最好考慮分表;


(2). 表的範式的優化

  一般情況下,表的設計應該遵循三大範式。


(3). 表的垂直拆分

  把含有多個列的表拆分成多個表,解決表寬度問題,具體包括以下幾種拆分手段:

  • 把不常用的字段單獨放在同一個表中;

  • 把大字段獨立放入一個表中;

  • 把經常使用的字段放在一起; 
       
    這樣做的好處是非常明顯的,具體包括:拆分後業務清晰,拆分規則明確、系統之間整合或擴展容易、數據維護簡單。


(4). 表的水平拆分

  表的水平拆分用於解決數據表中數據過大的問題,水平拆分每一個表的結構都是完全一致的。一般地,將數據平分到N張表中的常用方法包括以下兩種:

  • 對ID進行hash運算,如果要拆分成5個表,mod(id,5)取出0~4個值;
  • 針對不同的hashID將數據存入不同的表中;

  表的水平拆分會帶來一些問題和挑戰,包括跨分區表的數據查詢、統計及後臺報表的操作等問題,但也帶來了一些切實的好處:

  • 表分割後可以降低在查詢時需要讀的數據和索引的頁數,同時也降低了索引的層數,提高查詢速度;

  • 表中的數據本來就有獨立性,例如表中分別記錄各個地區的數據或不同時期的數據,特別是有些數據常用,而另外一些數據不常用。

  • 需要把數據存放到多個數據庫中,提高系統的總體可用性(分庫,雞蛋不能放在同一個籃子裏)。


3). 系統配置的優化

  • 操作系統配置的優化:增加TCP支持的隊列數

  • mysql配置文件優化:Innodb緩存池設置(innodb_buffer_pool_size,推薦總內存的75%)和緩存池的個數(innodb_buffer_pool_instances)


4). 硬件的優化

  • CPU:核心數多並且主頻高的
  • 內存:增大內存
  • 磁盤配置和選擇:磁盤性能

5、NOSQL數據庫 —— Redis

  Redis是一款基於內存的且支持持久化、高性能的Key-Value NoSQL 數據庫,其支持豐富數據類型(string,list,set,sorted set,hash),常被用作緩存的解決方案。Redis具有以下顯著特點:

  • 速度快,因爲數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間複雜度都是O(1);

  • 支持豐富數據類型,支持string,list,set,sorted set,hash;

  • 支持事務,操作都是原子性,所謂的原子性就是對數據的更改要麼全部執行,要麼全部不執行;

  • 豐富的特性:可用於緩存,消息,按key設置過期時間,過期後將會自動刪除。


  Redis作查詢緩存需要注意考慮以下幾個問題,包括防止髒讀、序列化查詢結果、爲查詢結果生成一個標識和怎麼使用四個問題,具體如下:

(1). 防止髒讀

  對一張表的查詢結果放在一個哈希結構裏,當對這個表進行修改、刪除或者更新時,刪除該哈希結構。對這張表所有的操作方法,使用註解進行標記,例如:

<span style="color:#333333"><code style="margin-left:0px" class="language-text"><span style="color:#000000; margin-left:0px">Hash</span>   <span style="color:#000000; margin-left:0px">KEY</span>(表名)  <span style="color:#000000; margin-left:0px">k</span>(查詢結果標識) : <span style="color:#000000; margin-left:0px">v</span>()   <span style="color:#000000; margin-left:0px">k</span><span style="color:#000000; margin-left:0px">:v</span>   <span style="color:#000000; margin-left:0px">k</span><span style="color:#000000; margin-left:0px">:v</span>   <span style="color:#000000; margin-left:0px">k</span><span style="color:#000000; margin-left:0px">:v</span>   <span style="color:#000000; margin-left:0px">k</span><span style="color:#000000; margin-left:0px">:v</span></code></span>
  • 1
<span style="color:#333333"><code style="margin-left:0px" class="language-java"><span style="color:#880000; margin-left:0px">// 表示該方法需要執行 (緩存是否命中 ? 返回緩存並阻止方法調用 : 執行方法並緩存結果)的緩存邏輯</span>
<span style="color:#9b859d; margin-left:0px">@RedisCache</span>(type = JobPostModel.class)
JobPostModel selectByPrimaryKey(Integer id);

<span style="color:#880000; margin-left:0px">// 表示該方法需要執行清除緩存邏輯</span>
<span style="color:#9b859d; margin-left:0px">@RedisEvict</span>(type = JobPostModel.class)
<span style="color:#000088; margin-left:0px">int</span> deleteByPrimaryKey(Integer id);</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  我們緩存了查詢結果,那麼一旦數據庫中的數據發生變化,緩存的結果就不可用了。爲了實現這一保證,可以在執行相關表的更新查詢(update,delete,insert)查詢前,讓相關的緩存過期。這樣下一次查詢時程序就會重新從數據庫中讀取新數據緩存到redis中。那麼問題來了,在執行一條insert前我怎麼知道應該讓哪些緩存過期呢?對於Redis,我們可以使用Hash結構,讓一張表對應一個Hash,所有在這張表上的查詢都保存到該Hash下。這樣當表數據發生變動時,直接讓Set過期即可。我們可以自定義一個註解,在數據庫查詢方法上通過註解的屬性註明這個操作與哪些表相關,這樣在執行過期操作時,就能直接從註解中得知應該讓哪些Set過期了。


(2). 序列化查詢結果

  利用JDK自帶的ObjectInputStream/ObjectOutputStream將查詢結果序列化成字節序列,即需要考慮Redis的實際存儲問題。


(3). 爲查詢結果生成一個標識

  被調用的方法所在的類名,被調用的方法的方法名,該方法的參數三者共同標識一條查詢結果。也就是說,如果兩次查詢調用的類名、方法名和參數值相同,我們就可以確定這兩次查詢結果一定是相同的(在數據沒有變動的前提下)。因此,我們可以將這三個元素組合成一個字符串做爲key,就解決了標識問題。


(4). 以 AOP 方式使用Redis

  • 方法被調用之前,根據類名、方法名和參數值生成Key;

  • 通過Key向Redis發起查詢;

  • 如果緩存命中,則將緩存結果反序列化作爲方法調用的返回值 ,並將其直接返回;

  • 如果緩存未命中,則繼續向數據庫中查詢,並將查詢結果序列化存入redis中,同時將查詢結果返回。


  例如,插入刪除緩存邏輯如下:

<span style="color:#333333"><code style="margin-left:0px" class="language-java"><span style="color:#880000; margin-left:0px">/**
     * 在方法調用前清除緩存,然後調用業務方法
     *<span style="color:#4f4f4f; margin-left:0px"> @param</span> jp
     *<span style="color:#4f4f4f; margin-left:0px"> @return</span>
     *<span style="color:#4f4f4f; margin-left:0px"> @throws</span> Throwable
     */</span>
    <span style="color:#9b859d; margin-left:0px">@Around</span>(<span style="color:#009900; margin-left:0px">"execution(* com.fh.taolijie.dao.mapper.JobPostModelMapper.insert*(..))"</span> +
            <span style="color:#009900; margin-left:0px">"|| execution(* com.fh.taolijie.dao.mapper.JobPostModelMapper.update*(..))"</span> +
            <span style="color:#009900; margin-left:0px">"|| execution(* com.fh.taolijie.dao.mapper.JobPostModelMapper.delete*(..))"</span> +
            <span style="color:#009900; margin-left:0px">"|| execution(* com.fh.taolijie.dao.mapper.JobPostModelMapper.increase*(..))"</span> +
            <span style="color:#009900; margin-left:0px">"|| execution(* com.fh.taolijie.dao.mapper.JobPostModelMapper.decrease*(..))"</span> +
            <span style="color:#009900; margin-left:0px">"|| execution(* com.fh.taolijie.dao.mapper.JobPostModelMapper.complaint(..))"</span> +
            <span style="color:#009900; margin-left:0px">"|| execution(* com.fh.taolijie.dao.mapper.JobPostModelMapper.set*(..))"</span>)
    <span style="color:#000088; margin-left:0px">public</span> Object <span style="color:#009900; margin-left:0px">evictCache</span>(ProceedingJoinPoint jp) <span style="color:#000088; margin-left:0px">throws</span> Throwable {}</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

6、什麼是存儲過程?有哪些優缺點?

  存儲過程是事先經過編譯並存儲在數據庫中的一段SQL語句的集合。進一步地說,存儲過程是由一些T-SQL語句組成的代碼塊,這些T-SQL語句代碼像一個方法一樣實現一些功能(對單表或多表的增刪改查),然後再給這個代碼塊取一個名字,在用到這個功能的時候調用他就行了。存儲過程具有以下特點:

  • 存儲過程只在創建時進行編譯,以後每次執行存儲過程都不需再重新編譯,而一般 SQL 語句每執行一次就編譯一次,所以使用存儲過程可提高數據庫執行效率;

  • 當SQL語句有變動時,可以只修改數據庫中的存儲過程而不必修改代碼;

  • 減少網絡傳輸,在客戶端調用一個存儲過程當然比執行一串SQL傳輸的數據量要小;

  • 通過存儲過程能夠使沒有權限的用戶在控制之下間接地存取數據庫,從而確保數據的安全。


7、簡單說一說drop、delete與truncate的區別

  SQL中的drop、delete、truncate都表示刪除,但是三者有一些差別:

  • Delete用來刪除表的全部或者一部分數據行,執行delete之後,用戶需要提交(commmit)或者回滾(rollback)來執行刪除或者撤銷刪除, delete命令會觸發這個表上所有的delete觸發器;

  • Truncate刪除表中的所有數據,這個操作不能回滾,也不會觸發這個表上的觸發器,TRUNCATE比delete更快,佔用的空間更小;

  • Drop命令從數據庫中刪除表,所有的數據行,索引和權限也會被刪除,所有的DML觸發器也不會被觸發,這個命令也不能回滾。

    因此,在不再需要一張表的時候,用drop;在想刪除部分數據行時候,用delete;在保留表而刪除所有數據的時候用truncate。


8、 什麼叫視圖?遊標是什麼?

  視圖是一種虛擬的表,通常是有一個表或者多個表的行或列的子集,具有和物理表相同的功能,可以對視圖進行增,刪,改,查等操作。特別地,對視圖的修改不影響基本表。相比多表查詢,它使得我們獲取數據更容易。

  遊標是對查詢出來的結果集作爲一個單元來有效的處理。遊標可以定在該單元中的特定行,從結果集的當前行檢索一行或多行。可以對結果集當前行做修改。一般不使用遊標,但是需要逐條處理數據的時候,遊標顯得十分重要。

  在操作mysql的時候,我們知道MySQL檢索操作返回一組稱爲結果集的行。這組返回的行都是與 SQL語句相匹配的行(零行或多行)。使用簡單的 SELECT語句,例如,沒有辦法得到第一行、下一行或前 10行,也不存在每次一行地處理所有行的簡單方法(相對於成批地處理它們)。有時,需要在檢索出來的行中前進或後退一行或多行。這就是使用遊標的原因。遊標(cursor)是一個存儲在MySQL服務器上的數據庫查詢,它不是一條 SELECT語句,而是被該語句檢索出來的結果集。在存儲了遊標之後,應用程序可以根據需要滾動或瀏覽其中的數據。遊標主要用於交互式應用,其中用戶需要滾動屏幕上的數據,並對數據進行瀏覽或做出更改。


9、什麼是觸發器?

  觸發器是與表相關的數據庫對象,在滿足定義條件時觸發,並執行觸發器中定義的語句集合。觸發器的這種特性可以協助應用在數據庫端確保數據庫的完整性。


10、MySQL中的悲觀鎖與樂觀鎖的實現

  悲觀鎖與樂觀鎖是兩種常見的資源併發鎖設計思路,也是併發編程中一個非常基礎的概念。


(1). 悲觀鎖

  悲觀鎖的特點是先獲取鎖,再進行業務操作,即“悲觀”的認爲所有的操作均會導致併發安全問題,因此要先確保獲取鎖成功再進行業務操作。通常來講,在數據庫上的悲觀鎖需要數據庫本身提供支持,即通過常用的select … for update操作來實現悲觀鎖。當數據庫執行select … for update時會獲取被select中的數據行的行鎖,因此其他併發執行的select … for update如果試圖選中同一行則會發生排斥(需要等待行鎖被釋放),因此達到鎖的效果。select for update獲取的行鎖會在當前事務結束時自動釋放,因此必須在事務中使用。

  這裏需要特別注意的是,不同的數據庫對select… for update的實現和支持都是有所區別的,例如oracle支持select for update no wait,表示如果拿不到鎖立刻報錯,而不是等待,mysql就沒有no wait這個選項。另外,mysql還有個問題是: select… for update語句執行中所有掃描過的行都會被鎖上,這一點很容易造成問題。因此,如果在mysql中用悲觀鎖務必要確定使用了索引,而不是全表掃描。


(2). 樂觀鎖

  樂觀鎖的特點先進行業務操作,只在最後實際更新數據時進行檢查數據是否被更新過,若未被更新過,則更新成功;否則,失敗重試。樂觀鎖在數據庫上的實現完全是邏輯的,不需要數據庫提供特殊的支持。一般的做法是在需要鎖的數據上增加一個版本號或者時間戳,然後按照如下方式實現:

<span style="color:#333333"><code style="margin-left:0px" class="language-text">1. <span style="color:#000088; margin-left:0px">SELECT</span> data <span style="color:#000088; margin-left:0px">AS</span> old_data, version <span style="color:#000088; margin-left:0px">AS</span> old_version <span style="color:#000088; margin-left:0px">FROM</span> …;
2. 根據獲取的數據進行業務操作,得到new_data和new_version
3. <span style="color:#000088; margin-left:0px">UPDATE</span> <span style="color:#000088; margin-left:0px">SET</span> data = new_data, version = new_version <span style="color:#000088; margin-left:0px">WHERE</span> version = old_version
<span style="color:#000088; margin-left:0px">if</span> (updated <span style="color:#000088; margin-left:0px">row</span> > <span style="color:#006666; margin-left:0px">0</span>) {
    // 樂觀鎖獲取成功,操作完成
} <span style="color:#000088; margin-left:0px">else</span> {
    // 樂觀鎖獲取失敗,回滾並重試
}</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

  樂觀鎖是否在事務中其實都是無所謂的,其底層機制是這樣:在數據庫內部update同一行的時候是不允許併發的,即數據庫每次執行一條update語句時會獲取被update行的寫鎖,直到這一行被成功更新後才釋放。因此在業務操作進行前獲取需要鎖的數據的當前版本號,然後實際更新數據時再次對比版本號確認與之前獲取的相同,並更新版本號,即可確認這其間沒有發生併發的修改。如果更新失敗,即可認爲老版本的數據已經被併發修改掉而不存在了,此時認爲獲取鎖失敗,需要回滾整個業務操作並可根據需要重試整個過程。


(3). 悲觀鎖與樂觀鎖的應用場景

  一般情況下,讀多寫少更適合用樂觀鎖,讀少寫多更適合用悲觀鎖。樂觀鎖在不發生取鎖失敗的情況下開銷比悲觀鎖小,但是一旦發生失敗回滾開銷則比較大,因此適合用在取鎖失敗概率比較小的場景,可以提升系統併發性能。


11、JDBC 對事務的支持

  對於JDBC而言,每條單獨的語句都是一個事務,即每個語句後都隱含一個commit。實際上,Connection 提供了一個auto-commit的屬性來指定事務何時結束。當auto-commit爲true時,當每個獨立SQL操作的執行完畢,事務立即自動提交,也就是說,每個SQL操作都是一個事務;當auto-commit爲false時,每個事務都必須顯式調用commit方法進行提交,或者顯式調用rollback方法進行回滾。auto-commit默認爲true。

<span style="color:#333333"><code style="margin-left:0px" class="language-java"><span style="color:#000088; margin-left:0px">try</span> {  
    conn.setAutoCommit(<span style="color:#000088; margin-left:0px">false</span>);  <span style="color:#880000; margin-left:0px">//將自動提交設置爲false        </span>
    ps.executeUpdate(<span style="color:#009900; margin-left:0px">"修改SQL"</span>); <span style="color:#880000; margin-left:0px">//執行修改操作  </span>
    ps.executeQuery(<span style="color:#009900; margin-left:0px">"查詢SQL"</span>);  <span style="color:#880000; margin-left:0px">//執行查詢操作                 </span>
    conn.commit();      <span style="color:#880000; margin-left:0px">//當兩個操作成功後手動提交     </span>
} <span style="color:#000088; margin-left:0px">catch</span> (Exception e) {  
    conn.rollback();    <span style="color:#880000; margin-left:0px">//一旦其中一個操作出錯都將回滾,使兩個操作都不成功  </span>
    e.printStackTrace();  
} </code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  爲了能夠將多條SQL當成一個事務執行,必須首先通過Connection關閉auto-commit模式,然後通過Connection的setTransactionIsolation()方法設置事務的隔離級別,最後分別通過Connection的commit()方法和rollback()方法來提交事務和回滾事務。


12、MySQL存儲引擎中的MyISAM和InnoDB區別詳解

  在MySQL 5.5之前,MyISAM是mysql的默認數據庫引擎,其由早期的ISAM(Indexed Sequential Access Method:有索引的順序訪問方法)所改良。雖然MyISAM性能極佳,但卻有一個顯著的缺點: 不支持事務處理。不過,MySQL也導入了另一種數據庫引擎InnoDB,以強化參考完整性與併發違規處理機制,後來就逐漸取代MyISAM。

  InnoDB是MySQL的數據庫引擎之一,其由Innobase oy公司所開發,2006年五月由甲骨文公司併購。與傳統的ISAM、MyISAM相比,InnoDB的最大特色就是支持ACID兼容的事務功能,類似於PostgreSQL。目前InnoDB採用雙軌制授權,一是GPL授權,另一是專有軟件授權。具體地,MyISAM與InnoDB作爲MySQL的兩大存儲引擎的差異主要包括:

  • 存儲結構:每個MyISAM在磁盤上存儲成三個文件:第一個文件的名字以表的名字開始,擴展名指出文件類型。.frm文件存儲表定義,數據文件的擴展名爲.MYD (MYData),索引文件的擴展名是.MYI (MYIndex)。InnoDB所有的表都保存在同一個數據文件中(也可能是多個文件,或者是獨立的表空間文件),InnoDB表的大小隻受限於操作系統文件的大小,一般爲2GB。

  • 存儲空間:MyISAM可被壓縮,佔據的存儲空間較小,支持靜態表、動態表、壓縮表三種不同的存儲格式。InnoDB需要更多的內存和存儲,它會在主內存中建立其專用的緩衝池用於高速緩衝數據和索引。

  • 可移植性、備份及恢復:MyISAM的數據是以文件的形式存儲,所以在跨平臺的數據轉移中會很方便,同時在備份和恢復時也可單獨針對某個表進行操作。InnoDB免費的方案可以是拷貝數據文件、備份 binlog,或者用 mysqldump,在數據量達到幾十G的時候就相對痛苦了。

  • 事務支持:MyISAM強調的是性能,每次查詢具有原子性,其執行數度比InnoDB類型更快,但是不提供事務支持。InnoDB提供事務、外鍵等高級數據庫功能,具有事務提交、回滾和崩潰修復能力。

  • AUTO_INCREMENT:在MyISAM中,可以和其他字段一起建立聯合索引。引擎的自動增長列必須是索引,如果是組合索引,自動增長可以不是第一列,它可以根據前面幾列進行排序後遞增。InnoDB中必須包含只有該字段的索引,並且引擎的自動增長列必須是索引,如果是組合索引也必須是組合索引的第一列。

  • 表鎖差異:MyISAM只支持表級鎖,用戶在操作MyISAM表時,select、update、delete和insert語句都會給表自動加鎖,如果加鎖以後的表滿足insert併發的情況下,可以在表的尾部插入新的數據。InnoDB支持事務和行級鎖。行鎖大幅度提高了多用戶併發操作的新能,但是InnoDB的行鎖,只是在WHERE的主鍵是有效的,非主鍵的WHERE都會鎖全表的。

  • 全文索引:MyISAM支持 FULLTEXT類型的全文索引;InnoDB不支持FULLTEXT類型的全文索引,但是innodb可以使用sphinx插件支持全文索引,並且效果更好。

  • 表主鍵:MyISAM允許沒有任何索引和主鍵的表存在,索引都是保存行的地址。對於InnoDB,如果沒有設定主鍵或者非空唯一索引,就會自動生成一個6字節的主鍵(用戶不可見),數據是主索引的一部分,附加索引保存的是主索引的值。

  • 表的具體行數:MyISAM保存表的總行數,select count() from table;會直接取出出該值;而InnoDB沒有保存表的總行數,如果使用select count() from table;就會遍歷整個表,消耗相當大,但是在加了wehre條件後,myisam和innodb處理的方式都一樣。

  • CURD操作:在MyISAM中,如果執行大量的SELECT,MyISAM是更好的選擇。對於InnoDB,如果你的數據執行大量的INSERT或UPDATE,出於性能方面的考慮,應該使用InnoDB表。DELETE從性能上InnoDB更優,但DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除,在innodb上如果要清空保存有大量數據的表,最好使用truncate table這個命令。

  • 外鍵:MyISAM不支持外鍵,而InnoDB支持外鍵。

      通過上述的分析,基本上可以考慮使用InnoDB來替代MyISAM引擎了,原因是InnoDB自身很多良好的特點,比如事務支持、存儲過程、視圖、行級鎖、外鍵等等。尤其在併發很多的情況下,相信InnoDB的表現肯定要比MyISAM強很多。另外,必須需要注意的是,任何一種表都不是萬能的,合適的纔是最好的,才能最大的發揮MySQL的性能優勢。如果是不復雜的、非關鍵的Web應用,還是可以繼續考慮MyISAM的,這個具體情況具體考慮。


寫在後面:

  關於Mysql在大型網站中的應用及其演變歷程,感興趣的讀者請移步個人轉載文章《Mysql在大型網站的應用架構演變》


引用

數據庫和MySQL相關面試題目 
《數據庫(第一範式,第二範式,第三範式)》 
數據庫索引實現原理—B_TREE 
B-樹和B+樹的應用:數據搜索和數據庫索引 
從B樹、B+樹、B*樹談到R 樹 
快速理解聚集索引和非聚集索引 
MySQL性能優化一之優化的目的和方面 
MySQL慢查詢日誌總結 
Spring AOP + Redis緩存數據庫查詢 
【MySQL】悲觀鎖&樂觀鎖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章