數據庫中的面試題你能接幾招

數據庫中的面試題你能接幾招

(附答案,不帶答案的面試題都是耍流氓)

1. 事務的特性

ACID:

​ 原子性, 一致性, 隔離性, 持久性

2. innodb如何結果幻讀

在不可重複讀的隔離級別下使用間隙鎖

3. 什麼是間隙鎖

InnoDB採用MVCC來支持高併發,並且實現了4個標準的隔離級別。其默認的隔離級別是可重複讀。當隔離級別是可重複讀的時候,是會發生幻讀的問題的。那麼MySQL如何解決這個問題呢?

在可重複讀隔離級別下,MySQL通過間隙鎖策略來防止幻讀的出現。間隙鎖使得InnoDB不僅鎖定查詢鎖涉及的行,還會對索引中的間隙進行鎖定,以防止幻影行的插入。但是間隙鎖更容易產生死鎖問題。

4. 索引的結構

hash, B樹, B+ 樹

原文地址:https://zhuanlan.zhihu.com/p/98148305

​ 索引一詞相信大家都很熟悉,很多人都知道mysql中的索引主要以B+樹爲主,但是爲什麼呢?

​ 索引是一種數據結構,用於幫助我們在大量數據中快速定位到我們想要查找的數據,索引最形象的比喻就是圖書的目錄了。注意這裏的大量,數據量大了才顯得有意義,如果我們的數據量很少,直接對全數據檢索也很快,沒有必要費力氣建立索引再去查找,索引在mysql數據庫中分爲三類:B+樹索引,Hash索引,全文索引。本次我們主要介紹innodb存儲引擎中的B+tree索引。B+樹索引需要了解二叉樹和平衡二叉樹。這裏的內容請大家自行了解。

​ 平衡二叉樹保證了樹的構造是平衡的,當我們插入活刪除數據導致不滿足平衡二叉樹不平衡時,平衡二叉樹會進行調整樹上的節點來保持平衡。平衡二叉樹相對於二叉樹來說,查詢效率更穩定,總體的查找速度也更快。

B樹:

​ 因爲內存的易失性,一般情況下,我們都會選擇將User表中的數據和索引存儲在磁盤這種外圍設備中,但是和內存相比,從磁盤中讀取數據的速度會慢上百倍千倍甚至萬倍,所以我們應當儘量減少從磁盤中讀取數據的次數,另外,從磁盤中讀取數據時,都是按照磁盤塊來讀取的,並不是一條一條讀的。如果我們能把儘量多的數據放進磁盤塊中,那一次磁盤讀取操作就會讀取更多數據,那我們查找數據的時間也會大幅度降低。如果我們用樹這種數據結構作爲索引的數據結構,那我們沒查找一次就需要從磁盤中讀取一個節點,也就是我們說的一個磁盤塊,我們都知道平衡二叉樹可是每個節點只能存儲一個鍵值和數據,那說明什麼?說明每個磁盤塊僅僅存儲一個鍵值和數據。那我們要存儲海量數據呢?可以想象到二叉樹的節點非常多,高度可會及其高,我們查找數據時也會進行很多次的磁盤IO,我們查找數據的效率將會極低。

​ 爲了解決平衡二叉樹的這個弊端,我們應該尋找一種單個節點可以存儲多個鍵值和數據的平衡樹,也就是我們接下來要說的B樹。

​ B樹就是平衡樹的意思(Balance Tree),

在這裏插入圖片描述
​ 圖中的p節點爲指向自己點的指針,二叉查找樹和平衡二叉樹其實也有,因爲圖的美觀性,被省略了。途中的每個節點稱爲頁,頁就是我們上面說的磁盤塊,在mysql中數據讀取的基本單位都是頁,所以我們這裏叫頁更符合mysql中索引的底層數據結構。從上圖可以看出,B樹相對於平衡二叉樹,每個節點存儲了更多的鍵值和數據,並且每個節點擁有更多的子節點,子節點的個數一般稱爲階,上述圖中的B樹爲3階B樹,高度也會很低。

​ 基於這個特性,B樹查找數據讀取磁盤的次數將會很少,數據的查找效率也會比平衡二叉樹搞很多。假設我們要查找id=28的用戶,那麼我們在上圖B樹中查找的流程如下:

​ 先找到根節點也就是頁1,判斷28在鍵值17和35之間,那麼我們根據頁1中的P2指針找到頁3

​ 將38和頁面中的鍵值比較,28在26和30之間,我們根據頁3中的p2指針找到頁8

​ 將28和頁8中的鍵值相比較,發現有匹配的鍵值28,鍵值28對應的用於信息爲(28,bv)

B+樹:

​ B+樹是對B樹的進一步優化,先來看一下結構圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kDUueSXC-1593145457913)(../../Desktop/md/assets/1582513238372.png)]

​ 根據上圖我們來看一下和B樹有什麼不同:

1. B+樹的非葉子節點是不存儲數據的,僅存儲鍵值,而B樹節點中不僅存儲鍵值也會存儲數據。之所以這麼做是因爲在數據庫中頁的大小是固定的,innodb中頁的大小默認是16k,如果不存儲數據,那麼就會存儲更多的鍵值,相當於數據階數(節點的子節點數)就會更大,樹就會更矮,如此一來,我們查找數據進行的磁盤io操作次數會再次減少,查詢的效率會更快,另外B+樹的階數是等於鍵值數量的,如果我們的B+樹一個節點可以存儲1000個鍵值,那麼三層B+樹可以存儲1000x1000x1000=10億個數據。一般根節點是常駐內存的,所以一般我們查找10億數據,只需要2次磁盤的操作。
  1. 因爲B+樹索引的所有數據均存儲在葉子節點上,而且數據時按照順序排列的,那麼B+樹使得範圍查找,排序查找,分組查找一級去重查找變得異常簡單。而B樹因爲數據分散在各個節點,要實現這一點是很不容易的。其實B+樹中各個頁之間是通過雙向鏈表鏈接的,葉子節點中的數據時通過單向鏈表鏈接的。其實上面的B樹我們也可以對各個節點加上鍊表,其實這些不是他們之間的區別,是因爲在mysql的innodb存儲引擎中,索引就是這樣存儲的。也就是說圖上的B+樹索引就是innodb中B+樹索引真正的實現方式,準確的說,應該是聚集索引。

通過上圖我們可以看到,在innodb中,我們通過數據頁之間通過雙向鏈表連接,以及葉子節點中數據之間通過單向鏈表連接的方式可以找到表中所有的數據。

​ MYISAM中的B+樹索引實現和innodb中的略有不同, 在MYISAM中,B+樹索引的葉子節點並不存儲數據,而是存儲數據的文件地址。

5. 聚集索引和非聚集索引

在mysql中,B+樹索引按照存儲方式的不同分爲聚集索引和非聚集索引。

聚集索引: 以innodb作爲存儲引擎的表,表中的數據都會有一個主鍵,即使你不想創建主鍵,系統也會幫你創建一個隱式的主鍵。這是因爲innodb是把數據存放在B+樹中的,二B+樹的鍵值就是主鍵,在B+樹的葉子節點中,存儲了表中所有的數據,這種以主鍵作爲B+樹索引的鍵值二構建的B+樹索引,我們稱之爲聚集索引

非聚集索引:以主鍵以外的列主作爲鍵值構建的B+樹索引,我們稱之爲非聚集索引。非聚集索引與聚集索引的區別在於非聚集索引的葉子節點不存儲表中的數據,而是存儲該列對應的主鍵,想要查找數據我們還需要根據主鍵再去聚集索引中進行查找,這個再根據聚集索引查找數據的過程,我們稱爲回表。

舉例: 根據聚集索引查找數據:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hJ1M4dFn-1593145457915)(../../Desktop/md/assets/1582524182456.png)]

假設我們要查找id>=18並且id<40的數據,對應的sql爲select * from user where id>=18 and id <40 其中id爲主鍵,具體的查找過程如下:

  1. 一般根節點都是常駐內存的,也就是說頁1已經在內存中了,因此不需要到磁盤中讀取數據,直接從內存中讀取即可。從內存中讀取到頁1,要查找這個id>=18 and id <40的範圍值,我們首先需要找到id=18的鍵值,從頁1中我們可以找到鍵值18,此時我們需要根據指針p2,定位到頁3.
  2. 要從頁3中查找數據,我們就需要拿着p2指針去磁盤中進行讀取頁3,從磁盤中讀取頁3後降頁3放入內存中,然後進行查找,我們可以找到鍵值18,然後在拿到頁3中的指針p1,定位到頁8
  3. 同樣的頁8也不在內存中,我們需要再去磁盤中獎頁8也讀取到內存中,因爲頁中的數據是鏈表進行連接的,而且鍵值是按照順序存放的,此時可以根據二分查找法定位到鍵值18.此時因爲已經找到數據頁了,此時我們已經找到一條滿足條件的數據了,就是鍵值18對應的數據。因爲是範圍查找,而且此時所有的數據又都存在葉子節點,並且是有序排列的,那麼我們就可以對頁8中的數據依次遍歷查找並匹配滿足條件的數據。我們可以一直找到鍵值爲22的數據,然後頁8中就沒有數據了,此時我們需要拿着頁8中的p指針去讀取頁9中的數據。
  4. 因爲頁9不在內存中,就又會加載頁9到內存中,並通過和頁8一樣的方式進行數據的查找,知道將頁12加載到內存中,發現41大於40,此時不滿足條件,那麼查找到此爲止。最終我們查到了所有滿足條件的數據。

在這裏插入圖片描述

利用非聚集索引查找:
在這裏插入圖片描述

讀者看到這張圖的時候可能會蒙,這是啥東西啊?怎麼都是數字。
如果有這種感覺,請仔細看下圖中紅字的解釋。什麼?還看不懂?那我再來解釋下吧。首先,這個非聚集索引表示的是用戶幸運數字的索引(爲什麼是幸運數字?一時興起想起來的:-)),此時表結構是這樣的。
idnameluckyNum1zs232ls7
在葉子節點中,不在存儲所有的數據了,存儲的是鍵值和主鍵。
對於葉子節點中的x-y,比如1-1。左邊的1表示的是索引的鍵值,右邊的1表示的是主鍵值。如果我們要找到幸運數字爲33的用戶信息,對應的sql語句爲select * from user where luckNum=33。
查找的流程跟聚集索引一樣,這裏就不詳細介紹了。我們最終會找到主鍵值47,找到主鍵後我們需要再到聚集索引中查找具體對應的數據信息,此時又回到了聚集索引的查找流程。
下面看下具體的查找流程圖:

在MyISAM中,聚集索引和非聚集索引的葉子節點都會存儲數據的文件地址。

6. 如何顯示的給innodb添加讀寫鎖

select… lock in share mode;

select …for update;

7. 什麼是髒讀,不可重複讀,幻讀,丟失修改

  • 髒讀(Dirty read): 當一個事務正在訪問數據並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時另外一個事務也訪問了這個數據,然後使用了這個數據。因爲這個數據是還沒有提交的數據,那麼另外一個事務讀到的這個數據是“髒數據”,依據“髒數據”所做的操作可能是不正確的。
  • 丟失修改(Lost to modify): 指在一個事務讀取一個數據時,另外一個事務也訪問了該數據,那麼在第一個事務中修改了這個數據後,第二個事務也修改了這個數據。這樣第一個事務內的修改結果就被丟失,因此稱爲丟失修改。 例如:事務1讀取某表中的數據A=20,事務2也讀取A=20,事務1修改A=A-1,事務2也修改A=A-1,最終結果A=19,事務1的修改被丟失。
  • 不可重複讀(Unrepeatableread): 指在一個事務內多次讀同一數據。在這個事務還沒有結束時,另一個事務也訪問該數據。那麼,在第一個事務中的兩次讀數據之間,由於第二個事務的修改導致第一個事務兩次讀取的數據可能不太一樣。這就發生了在一個事務內兩次讀到的數據是不一樣的情況,因此稱爲不可重複讀。
  • 幻讀(Phantom read): 幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行數據,接着另一個併發事務(T2)插入了一些數據時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱爲幻讀。

8. 不可重複讀和幻讀的區別:

不可重複讀的重點是修改,幻讀的重點在於新增或者刪除。

例1(同樣的條件, 你讀取過的數據, 再次讀取出來發現值不一樣了 ):事務1中的A先生讀取自己的工資爲 1000的操作還沒完成,事務2中的B先生就修改了A的工資爲2000,導 致A再讀自己的工資時工資變爲 2000;這就是不可重複讀。

例2(同樣的條件, 第1次和第2次讀出來的記錄數不一樣 ):假某工資單表中工資大於3000的有4人,事務1讀取了所有工資大於3000的人,共查到4條記錄,這時事務2 又插入了一條工資大於3000的記錄,事務1再次讀取時查到的記錄就變爲了5條,這樣就導致了幻讀。

9. Mvcc和 事務隔離級別的關係

原文:https://www.zhihu.com/question/279538775

早期數據庫不論讀取還是寫入,都用鎖來實現。但是鎖會帶來性能的問題。人們嘗試各種優化方案。寫入和讀取的優化方式不同。

對於數據庫寫入操作,沒有特別好的辦法,因爲無論如何要避免併發修改一個數據,就得靠鎖。不同的數據庫對於寫入操作都會加悲觀鎖(比如MySQL是X鎖)。爲了避免X鎖帶來的性能問題,人們在合適的場合會選擇用樂觀鎖來優化。有的數據庫內建樂觀鎖,但是有的沒有(比如MySQL就沒有),所以需要開發人員自己在數據表裏加version列,自己寫業務代碼實現。

順便提一句,樂觀鎖並不一定總是比悲觀鎖性能表現更好,這要看競爭的程度。如果數據訪問競爭的非常厲害,樂觀鎖只會讓CPU和IO白白浪費而已。

對於讀取,優化就是MVCC。現在主流的商業數據庫都是基於MVCC,如MySQL InnoDB和Postgres。MVCC的意思用簡單的話講就是對數據庫的任何修改的提交都不會直接覆蓋之前的數據,而是產生一個新的版本與老版本共存,使得讀取時可以完全不加鎖。這樣讀某一個數據時,事務可以根據隔離級別選擇要讀取哪個版本的數據。過程中完全不需要加鎖。

這樣,實現兩個隔離級別就非常容易:

  • Read Committed - 一個事務讀取數據時總是讀這個數據最近一次被commit的版本
  • Repeatable Read - 一個事務讀取數據時總是讀取當前事務開始之前最後一次被commit的版本(所以底層實現時需要比較當前事務和數據被commit的版本號)。

舉個簡單的例子:

  1. 一個事務A(txnId=100)修改了數據X,使得X=1,並且commit了
  2. 另外一個事務B(txnId=101)開始嘗試讀取X,但是還X=1。但B沒有提交。
  3. 第三個事務C(txnId=102)修改了數據X,使得X=2。並且提交了
  4. 事務B又一次讀取了X。這時
  • 如果事務B是Read Committed。那麼就讀取X的最新commit的版本,也就是X=2
  • 如果事務B是Repeatable Read。那麼讀取的就是當前事務(txnId=101)之前X的最新版本,也就是X被txnId=100提交的版本,即X=1。

注意,這裏B不論是Read Committed,還是Repeatable Read,都不會被鎖,都能立刻拿到結果。這也就是MVCC存在的意義。

在基於MVCC的數據庫實現中,根本就不需要出現Read Uncommitted這種情況。Read Uncommitted是早期數據庫,讀寫都基於鎖進行實現的產物。在實際業務中Read Uncommitted毫無意義(如果真有意義,你咋不去用NoSQL數據庫?)因此:

  • 對於Postgres,Read Committed和Read Uncommitted是一樣的。
  • 對於Oracle,壓根就沒有Read Uncommitted這個級別。

所以從實際的角度出發,我想所有人都忘記有“Read Uncommitted”這件事。

MVCC並不是萬靈藥。大量的業務問題的關鍵點在於,你在提交一個事務那一剎那,你提交事務的所有修改依賴的讀取是否都還有效。對於這種場景,無論是Read Committed還是Repeatable Read都沒有什麼卵用。比如扣庫存就是這樣典型的業務場景。

在這種場景下

  • 在MySQL InnoDB,使用者會使用select ... for update手工加鎖。或者乾脆用Serializable隔離級別。
  • 在Postgres,Postgres的Repeatable Read在提交時會提供一個“提交的修改的依賴是否被修改“的檢測(好繞口,但就是這個意思)。如果依賴已經被改掉了,當前事務提交一定會失敗。

10. mysql中的數據庫引用有哪幾種

MyISAM: 不支持外鍵; 表鎖,插入數據時鎖整個表,查表總行數時,不需要全表掃描

Innodb: 支持外鍵,行鎖, 查表總行數時,全表掃描

11. 索引覆蓋與回表

我們上面提到了,數據庫的索引分爲了聚集索引和非聚集索引,聚集索引一般都是我們的主鍵索引,通過主鍵索引,在b+樹的葉子節點上可以直接得到數據。 而非聚集索引的葉子節點上存儲的並不是行數據本身,而是聚集索引(主鍵)。所以當我們使用非聚集索引查詢數據的時候,率先得到的是數據的聚集索引(主鍵),然後通過聚集索引再去定位數據項,這個過程就叫做回表。所以聚集索引查詢數據是不需要回表的,查詢效率更高。

索引覆蓋: 可以簡單理解成我們要查的數據直接就是索引, 不需要再去回表。

比如表中age,name 都是普通索引。

我們查詢 select age, name from table; 只需要掃描索引樹就完全可以得到結果,也不需要獲取整條數據,所需要的數據在索引中全部包括了,這就是索引覆蓋。

12. 索引的優缺點,什麼時候使用索引,什麼時候不能使用索引

索引最大的好處是提高查詢速度,
缺點是更新數據時效率低,因爲要同時更新索引
對數據進行頻繁查詢進建立索引,如果要頻繁更改數據不建議使用索引

13. 索引的底層實現(B+樹,爲何不採用紅黑樹,B樹)重點

樹 區別
紅黑樹 增加,刪除,紅黑樹會進行頻繁的調整,來保證紅黑樹的性質,浪費時間
B樹也就是B-樹 B樹,查詢性能不穩定,查詢結果高度不致,每個結點保存指向真實數據的指針,相比 B+樹每一層每屋存儲的元素更多,顯得更高一點。
B+樹 B+樹相比較於另外兩種樹,顯得更矮更寬,查詢層次更淺

14. 索引最左前綴問題:

如果對三個字段建立聯合索引,如果第二個字段沒有使用索引,第三個字段也使用不到索引了

15. 索引分類及索引失效條件

普通索引:最基本的索引,沒有任何限制

唯一索引:與"普通索引"類似,不同的就是:索引列的值必須唯一,但允許有空值。

主鍵索引:它是一種特殊的唯一索引,不允許有空值。

前文索引: 針對較大的數據,生成全文索引很耗時好空間。

組合索引:爲了更多的提高mysql效率可建立組合索引,遵循”最左前綴“原則

索引失效條件:

​ 條件是or,如果還想讓or條件生效,給or每個字段加個索引
​ like查詢,以%開發
​ 內部函數
​ 對索引列進行計算
​ is null不會用,is not null 會用

16. char 和 varchar區別

Char: 用於字符長度固定的,比如性別, 手機號

varchar:用於字符串長度經常變化的

17. 數據庫三範式

強調的是列的原子性,即數據庫表的每一列都是不可分割的原子數據項。

非主鍵屬性,完全依賴於主鍵屬性。所謂完全依賴是指不能存在僅依賴主關鍵字一部分的屬性。

任何非主屬性不依賴於其它非主屬性。非主鍵屬性無傳遞依賴

18. sql優化

大原則: 儘量命中索引,避免全表掃描。

  1. 聯合索引要注意最左前綴的原則
  2. 索引上不要進行任何操作(計算,函數,類型轉換),否則會導致全表掃描
  3. 儘量使用索引覆蓋(只訪問索引的查詢),減少select*
  4. like以通配符開始,索引會失效,導致全表掃描。如 name like “%abc”
  5. 字符串查詢要使用單引號,否則導致索引失效 如where name = 1000
  6. 少用in或or, 使用時不一定會命中索引,mysql內部會進行評估,看是否使用索引
  7. 查詢儘量不要使用select * 而是select 具體字段
  8. 如果知道查詢結果只有一條或者只要最大/最小一條,建議limit 1
  9. 儘量避免在where字句中使用or連接條件(可使用union all)
  10. Limit 優化: order by + 索引 + limit
  11. 使用where條件查詢,避免返回多餘的行
  12. 不要在索引上使用內置函數,會導致索引失效
  13. 使用關聯的時候,保證主表的數據量儘量小
  14. 儘量避免在where字句中使用!= 或 <>

19. 介紹一下悲觀鎖和樂觀鎖

樂觀鎖:對加鎖持有一種樂觀的態度,即先進行業務操作,不到最後一步不進行加鎖,"樂觀"的認爲加鎖一定會成功的,在最後一步更新數據的時候在進行加鎖。樂觀鎖的實現方式一般爲每一條數據加一個版本號,update時判斷這個版本號是否和數據庫裏的一致,提交數據前後是否衝突。update是單線程的,及如果一個線程對一條數據進行update操作,會獲得鎖,其他線程如果要對同一條數據操作會阻塞,直到這個線程update成功後釋放鎖。

悲觀鎖:悲觀鎖對數據加鎖持有一種悲觀的態度。因此,在整個數據處理過程中,將數據處於鎖定狀態。悲觀鎖的實現,往往依靠數據庫提供的鎖機制。備註,在MySQL中使用悲觀鎖,必須關閉MySQL的自動提交,set
autocommit=0。MySQL默認使用自動提交autocommit模式,也即你執
行一個更新操作,MySQL會自動將結果提交

20. 介紹一下排它鎖和共享鎖

首先說明:數據庫的增刪改操作默認都會加排他鎖,而查詢不會加任何鎖。都是行級鎖
共享鎖:對某一資源加共享鎖,自身可以讀該資源,其他人也以讀該資源(也可以再繼續加共享鎖,即 共享鎖可多個共存),但無法修改。要想修改就必須等所有共享鎖都釋放完之後。語法爲:
select * from table lock in share mode

排他鎖:對某一資源加排他鎖,自身可以進行增刪改查,其他人無法進行任何操作。語法爲:
select * from table for update

21. redolog 和 undolog

  1. redo log通常是物理日誌,記錄的是數據頁的物理修改,而不是某一行或某幾行修改成怎樣怎樣,它用來恢復提交後的物理數據頁(恢復數據頁,且只能恢復到最後一次提交的位置)。
  2. undo用來回滾行記錄到某個版本。undo log一般是邏輯日誌,根據每行記錄進行記錄。

更多面試及技術資料,請關注公衆號 java_breeze
在這裏插入圖片描述

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