1. 程式人生 > >資料庫面試問題集錦

資料庫面試問題集錦

摘要:

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

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是更好的選