BATJTMD 面試必問的 MySQL,你懂了嗎?

前言

今天不整那些花裏胡哨、虛頭巴腦的前言了,直接進入正題懟起來。

 

正文

二狗:不多BB,先懟幾道常問的大題目。MySQL 的事務隔離級別有哪些?分別用於解決什麼問題?

主要用於解決髒讀、不可重複讀、幻讀。

髒讀:一個事務讀取到另一個事務還未提交的數據。

不可重複讀:在一個事務中多次讀取同一個數據時,結果出現不一致。

幻讀:在一個事務中使用相同的 SQL 兩次讀取,第二次讀取到了其他事務新插入的行。

不可重複讀注重於數據的修改,而幻讀注重於數據的插入。

 

隔離級別

髒讀

不可重複讀

幻讀

讀未提交(Read Uncommitted)

讀已提交(Read Committed)

可重複讀(Repeatable Read)

串行化(Serializable)

 

二狗:MySQL 的可重複讀怎麼實現的?

使用 MVCC 實現的,即 Mutil-Version Concurrency Control,多版本併發控制。關於 MVCC,比較常見的說法如下,包括《高性能 MySQL》也是這麼介紹的。

 

InnoDB 在每行記錄後面保存兩個隱藏的列,分別保存了數據行的創建版本號刪除版本號。每開始一個新的事務,系統版本號都會遞增。事務開始時刻的版本號會作爲事務的版本號,用來和查詢到的每行記錄的版本號對比。在可重複讀級別下,MVCC是如何操作的:

SELECT:必須同時滿足以下兩個條件,才能查詢到。1)只查版本號早於當前版本的數據行;2)行的刪除版本要麼未定義,要麼大於當前事務版本號。

INSERT:爲插入的每一行保存當前系統版本號作爲創建版本號。

DELETE:爲刪除的每一行保存當前系統版本號作爲刪除版本號。

UPDATE:插入一條新數據,保存當前系統版本號作爲創建版本號。同時保存當前系統版本號作爲原來的數據行刪除版本號。

 

MVCC 只作用於 RC(Read Committed)和 RR(Repeatable Read)級別,因爲 RU(Read Uncommitted)總是讀取最新的數據版本,而不是符合當前事務版本的數據行。而 Serializable 則會對所有讀取的行都加鎖。這兩種級別都不需要 MVCC 的幫助。

 

最初我也是堅信這個說法的,但是後面發現在某些場景下這個說法其實有點問題。

 

舉個簡單的例子來說:如果線程1和線程2先後開啓了事務,事務版本號爲1和2,如果在線程2開啓事務的時候,線程1還未提交事務,則此時線程2的事務是不應該看到線程1的事務修改的內容的。

 

但是如果按上面的這種說法,由於線程1的事務版本早於線程2的事務版本,所以線程2的事務是可以看到線程1的事務修改內容的。

 

二狗:好像是有這個問題,那究竟是怎麼實現的?

實際上,InnoDB 會在每行記錄後面增加三個隱藏字段:

DB_ROW_ID:行ID,隨着插入新行而單調遞增,如果有主鍵,則不會包含該列。

DB_TRX_ID:記錄插入或更新該行的事務的事務ID。

DB_ROLL_PTR:回滾指針,指向 undo log 記錄。每次對某條記錄進行改動時,該列會存一個指針,可以通過這個指針找到該記錄修改前的信息 。當某條記錄被多次修改時,該行記錄會存在多個版本,通過DB_ROLL_PTR 鏈接形成一個類似版本鏈的概念。

 

接下來進入正題,以 RR 級別爲例:每開啓一個事務時,系統會給該事務會分配一個事務 Id,在該事務執行第一個 select 語句的時候,會生成一個當前時間點的事務快照 ReadView,主要包含以下幾個屬性:

  • trx_ids:生成 ReadView 時當前系統中活躍的事務 Id 列表,就是還未執行事務提交的。

  • up_limit_id:低水位,取 trx_ids 中最小的那個,trx_id 小於該值都能看到。

  • low_limit_id:高水位,生成 ReadView 時系統將要分配給下一個事務的id值,trx_id 大於等於該值都不能看到。

  • creator_trx_id:生成該 ReadView 的事務的事務 Id。

 

有了這個ReadView,這樣在訪問某條記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見:

1)如果被訪問版本的trx_id與ReadView中的creator_trx_id值相同,意味着當前事務在訪問它自己修改過的記錄,所以該版本可以被當前事務訪問。

2)如果被訪問版本的trx_id小於ReadView中的up_limit_id值,表明生成該版本的事務在當前事務生成ReadView前已經提交,所以該版本可以被當前事務訪問。

3)如果被訪問版本的trx_id大於ReadView中的low_limit_id值,表明生成該版本的事務在當前事務生成ReadView後纔開啓,所以該版本不可以被當前事務訪問。

4)如果被訪問版本的trx_id屬性值在ReadView的up_limit_id和low_limit_id之間,那就需要判斷一下trx_id屬性值是不是在trx_ids列表中。如果在,說明創建ReadView時生成該版本的事務還是活躍的,該版本不可以被訪問;如果不在,說明創建ReadView時生成該版本的事務已經被提交,該版本可以被訪問。

 

在進行判斷時,首先會拿記錄的最新版本來比較,如果該版本無法被當前事務看到,則通過記錄的 DB_ROLL_PTR 找到上一個版本,重新進行比較,直到找到一個能被當前事務看到的版本。

 

而對於刪除,其實就是一種特殊的更新,InnoDB 用一個額外的標記位 delete_bit 標識是否刪除。當我們在進行判斷時,會檢查下 delete_bit 是否被標記,如果是,則跳過該版本,通過 DB_ROLL_PTR 拿到下一個版本進行判斷。

 

以上內容是對於 RR 級別來說,而對於 RC 級別,其實整個過程幾乎一樣,唯一不同的是生成 ReadView 的時機,RR 級別只在事務開始時生成一次,之後一直使用該 ReadView。而 RC 級別則在每次 select 時,都會生成一個 ReadView。

 

二狗:那 MVCC 解決了幻讀了沒有?

幻讀:在一個事務中使用相同的 SQL 兩次讀取,第二次讀取到了其他事務新插入的行,則稱爲發生了幻讀。

例如:

1)事務1第一次查詢:select * from user where id < 10 時查到了 id = 1 的數據

2)事務2插入了 id = 2 的數據

3)事務1使用同樣的語句第二次查詢時,查到了 id = 1、id = 2 的數據,出現了幻讀。

 

談到幻讀,首先我們要引入“當前讀”和“快照讀”的概念,聰明的你一定通過名字猜出來了:

快照讀:生成一個事務快照(ReadView),之後都從這個快照獲取數據。普通 select 語句就是快照讀。

當前讀:讀取數據的最新版本。常見的 update/insert/delete、還有 select ... for update、select ... lock in share mode 都是當前讀。

 

對於快照讀,MVCC 因爲因爲從 ReadView 讀取,所以必然不會看到新插入的行,所以天然就解決了幻讀的問題。

 

而對於當前讀的幻讀,MVCC 是無法解決的。需要使用 Gap Lock 或 Next-Key Lock(Gap Lock + Record Lock)來解決。

 

其實原理也很簡單,用上面的例子稍微修改下以觸發當前讀:select * from user where id < 10 for update,當使用了 Gap Lock 時,Gap 鎖會鎖住 id < 10 的整個範圍,因此其他事務無法插入 id < 10 的數據,從而防止了幻讀。

 

二狗:那經常有人說 Repeatable Read 解決了幻讀是什麼情況?

SQL 標準中規定的 RR 並不能消除幻讀,但是 MySQL 的 RR 可以,靠的就是 Gap 鎖。在 RR 級別下,Gap 鎖是默認開啓的,而在 RC 級別下,Gap 鎖是關閉的。

 

二狗:小夥子不錯,大活都給你搞下來了,接下來看下基礎扎不紮實。什麼是索引?

MySQL 官方對索引的定義爲:索引(Index)是幫助 MySQL 高效獲取數據的數據結構。簡單的理解,索引類似於字典裏面的目錄。

 

二狗:常見的索引類型有哪些?

常見的索引類型有:hash、b樹、b+樹。

 

hash:底層就是 hash 表。進行查找時,根據 key 調用hash 函數獲得對應的 hashcode,根據 hashcode 找到對應的數據行地址,根據地址拿到對應的數據。

 

B樹:B樹是一種多路搜索樹,n 路搜索樹代表每個節點最多有 n 個子節點。每個節點存儲 key + 指向下一層節點的指針+ 指向 key 數據記錄的地址。查找時,從根結點向下進行查找,直到找到對應的key。

 

B+樹:B+樹是b樹的變種,主要區別在於:B+樹的非葉子節點只存儲 key + 指向下一層節點的指針。另外,B+樹的葉子節點之間通過指針來連接,構成一個有序鏈表,因此對整棵樹的遍歷只需要一次線性遍歷葉子結點即可。

 

二狗:爲什麼MySQL數據庫要用B+樹存儲索引?而不用紅黑樹、Hash、B樹?

紅黑樹:如果在內存中,紅黑樹的查找效率比B樹更高,但是涉及到磁盤操作,B樹就更優了。因爲紅黑樹是二叉樹,數據量大時樹的層數很高,從樹的根結點向下尋找的過程,每讀1個節點,都相當於一次IO操作,因此紅黑樹的I/O操作會比B樹多的多。

 

hash 索引:如果只查詢單個值的話,hash 索引的效率非常高。但是 hash 索引有幾個問題:1)不支持範圍查詢;2)不支持索引值的排序操作;3)不支持聯合索引的最左匹配規則。

 

B樹索引:B樹索相比於B+樹,在進行範圍查詢時,需要做局部的中序遍歷,可能要跨層訪問,跨層訪問代表着要進行額外的磁盤I/O操作;另外,B樹的非葉子節點存放了數據記錄的地址,會導致存放的節點更少,樹的層數變高。

 

二狗:MySQL 中的索引葉子節點存放的是什麼?

MyISAM和InnoDB都是採用的B+樹作爲索引結構,但是葉子節點的存儲上有些不同。

 

MyISAM:主鍵索引和輔助索引(普通索引)的葉子節點都是存放 key 和 key 對應數據行的地址。在MyISAM 中,主鍵索引和輔助索引沒有任何區別。

 

InnoDB:主鍵索引存放的是 key 和 key 對應的數據行。輔助索引存放的是 key 和 key 對應的主鍵值。因此在使用輔助索引時,通常需要檢索兩次索引,首先檢索輔助索引獲得主鍵值,然後用主鍵值到主鍵索引中檢索獲得記錄。

 

二狗:什麼是聚簇索引(聚集索引)?

聚簇索引並不是一種單獨的索引類型,而是一種數據存儲方式。聚簇索引將索引和數據行放到了一塊,找到索引也就找到了數據。因爲無需進行回表操作,所以效率很高。

 

InnoDB 中必然會有,且只會有一個聚簇索引。通常是主鍵,如果沒有主鍵,則優先選擇非空的唯一索引,如果唯一索引也沒有,則會創建一個隱藏的row_id 作爲聚簇索引。至於爲啥會只有一個聚簇索引,其實很簡單,因爲我們的數據只會存儲一份。

 

而非聚簇索引則將數據存儲和索引分開,找到索引後,需要通過對應的地址找到對應的數據行。MyISAM 的索引方式就是非聚簇索引。

 

二狗:什麼是回表查詢?

InnoDB 中,對於主鍵索引,只需要走一遍主鍵索引的查詢就能在葉子節點拿到數據。

而對於普通索引,葉子節點存儲的是 key + 主鍵值,因此需要再走一次主鍵索引,通過主鍵索引找到行記錄,這就是所謂的回表查詢,先定位主鍵值,再定位行記錄。

 

二狗:走普通索引,一定會出現回表查詢嗎?

不一定,如果查詢語句所要求的字段全部命中了索引,那麼就不必再進行回表查詢。

很容易理解,有一個 user 表,主鍵爲 id,name 爲普通索引,則再執行:select id, name from user where name = 'joonwhee' 時,通過name 的索引就能拿到 id 和 name了,因此無需再回表去查數據行了。

 

二狗:那你知道什麼是覆蓋索引(索引覆蓋)嗎?

覆蓋索引是 SQL-Server 中的一種說法,上面講的例子其實就實現了覆蓋索引。具體的:當索引上包含了查詢語句中的所有列時,我們無需進行回表查詢就能拿到所有的請求數據,因此速度會很快。

 

當explain的輸出結果Extra字段爲Using index時,則代表觸發覆蓋索引。以上面的例子爲例:

 

 

二狗:聯合索引(複合索引)的底層實現?最佳左前綴原則?

聯合索引底層還是使用B+樹索引,並且還是隻有一棵樹,只是此時的排序會:首先按照第一個索引排序,在第一個索引相同的情況下,再按第二個索引排序,依次類推。

 

這也是爲什麼有“最佳左前綴原則”的原因,因爲右邊(後面)的索引都是在左邊(前面)的索引排序的基礎上進行排序的,如果沒有左邊的索引,單獨看右邊的索引,其實是無序的。

 

還是以字典爲例,我們如果要查第2個字母爲 k 的,通過目錄是無法快速找的,因爲首字母 A - Z 裏面都可能包含第2個字母爲 k 的。

 

二狗:union 和 union all 的區別

union all:對兩個結果集直接進行並集操作,記錄可能有重複,不會進行排序。

union:對兩個結果集進行並集操作,會進行去重,記錄不會重複,按字段的默認規則排序。

因此,從效率上說,UNION ALL 要比 UNION 更快。

 

二狗:B+樹中一個節點到底多大合適?

1頁或頁的倍數最爲合適。因爲如果一個節點的大小小於1頁,那麼讀取這個節點的時候其實也會讀出1頁,造成資源的浪費。所以爲了不造成浪費,所以最後把一個節點的大小控制在1頁、2頁、3頁等倍數頁大小最爲合適。

 

這裏說的“頁”是 MySQL 自定義的單位(和操作系統類似),MySQL 的 Innodb 引擎中1頁的默認大小是16k,可以使用命令SHOW GLOBAL STATUS LIKE 'Innodb_page_size' 查看。

 

 

二狗:那 MySQL 中B+樹的一個節點大小爲多大呢?

在 MySQL 中 B+ 樹的一個節點大小爲“1頁”,也就是16k。

 

二狗:爲什麼一個節點爲1頁就夠了?

Innodb中,B+樹中的一個節點存儲的內容是:

  • 非葉子節點:key + 指針

  • 葉子節點:數據行(key 通常是數據的主鍵)

 

對於葉子節點:我們假設1行數據大小爲1k(對於普通業務絕對夠了),那麼1頁能存16條數據。

對於非葉子節點:key 使用 bigint 則爲8字節,指針在 MySQL 中爲6字節,一共是14字節,則16k能存放 16 * 1024 / 14 = 1170個。那麼一顆高度爲3的B+樹能存儲的數據爲:1170 * 1170 * 16 = 21902400(千萬級)。

 

所以在 InnoDB 中B+樹高度一般爲3層時,就能滿足千萬級的數據存儲。在查找數據時一次頁的查找代表一次IO,所以通過主鍵索引查詢通常只需要1-3次 IO 操作即可查找到數據。千萬級別對於一般的業務來說已經足夠了,所以一個節點爲1頁,也就是16k是比較合理的。

 

二狗:什麼是 Buffer Pool?

Buffer Pool 是 InnoDB 維護的一個緩存區域,用來緩存數據和索引在內存中,主要用來加速數據的讀寫,如果 Buffer Pool 越大,那麼 MySQL 就越像一個內存數據庫,默認大小爲 128M。

 

InnoDB 會將那些熱點數據和一些 InnoDB 認爲即將訪問到的數據存在 Buffer Pool 中,以提升數據的讀取性能。

 

InnoDB 在修改數據時,如果數據的頁在 Buffer Pool 中,則會直接修改 Buffer Pool,此時我們稱這個頁爲髒頁,InnoDB 會以一定的頻率將髒頁刷新到磁盤,這樣可以儘量減少磁盤I/O,提升性能。

 

二狗:InnoDB 四大特性知道嗎?

插入緩衝(insert buffer):

索引是存儲在磁盤上的,所以對於索引的操作需要涉及磁盤操作。如果我們使用自增主鍵,那麼在插入主鍵索引(聚簇索引)時,只需不斷追加即可,不需要磁盤的隨機 I/O。但是如果我們使用的是普通索引,大概率是無序的,此時就涉及到磁盤的隨機 I/O,而隨機I/O的性能是比較差的(Kafka 官方數據:磁盤順序I/O的性能是磁盤隨機I/O的4000~5000倍)。

 

因此,InnoDB 存儲引擎設計了 Insert Buffer ,對於非聚集索引的插入或更新操作,不是每一次直接插入到索引頁中,而是先判斷插入的非聚集索引頁是否在緩衝池(Buffer pool)中,若在,則直接插入;若不在,則先放入到一個 Insert Buffer 對象中,然後再以一定的頻率和情況進行 Insert Buffer 和輔助索引頁子節點的 merge(合併)操作,這時通常能將多個插入合併到一個操作中(因爲在一個索引頁中),這就大大提高了對於非聚集索引插入的性能。

插入緩衝的使用需要滿足以下兩個條件:1)索引是輔助索引;2)索引不是唯一的。

 

因爲在插入緩衝時,數據庫不會去查找索引頁來判斷插入的記錄的唯一性。如果去查找肯定又會有隨機讀取的情況發生,從而導致 Insert Buffer 失去了意義。

 

二次寫(double write):

髒頁刷盤風險:InnoDB 的 page size一般是16KB,操作系統寫文件是以4KB作爲單位,那麼每寫一個 InnoDB 的 page 到磁盤上,操作系統需要寫4個塊。於是可能出現16K的數據,寫入4K 時,發生了系統斷電或系統崩潰,只有一部分寫是成功的,這就是 partial page write(部分頁寫入)問題。這時會出現數據不完整的問題。

這時是無法通過 redo log 恢復的,因爲 redo log 記錄的是對頁的物理修改,如果頁本身已經損壞,重做日誌也無能爲力。

 

doublewrite 就是用來解決該問題的。doublewrite 由兩部分組成,一部分爲內存中的 doublewrite buffer,其大小爲2MB,另一部分是磁盤上共享表空間中連續的128個頁,即2個區(extent),大小也是2M。

爲了解決 partial page write 問題,當 MySQL 將髒數據刷新到磁盤的時候,會進行以下操作:

1)先將髒數據複製到內存中的 doublewrite buffer

2)之後通過 doublewrite buffer 再分2次,每次1MB寫入到共享表空間的磁盤上(順序寫,性能很高)

3)完成第二步之後,馬上調用 fsync 函數,將doublewrite buffer中的髒頁數據寫入實際的各個表空間文件(離散寫)。

 

如果操作系統在將頁寫入磁盤的過程中發生崩潰,InnoDB 再次啓動後,發現了一個 page 數據已經損壞,InnoDB 存儲引擎可以從共享表空間的 doublewrite 中找到該頁的一個最近的副本,用於進行數據恢復了。

 

自適應哈希索引(adaptive hash index):

哈希(hash)是一種非常快的查找方法,一般情況下查找的時間複雜度爲 O(1)。但是由於不支持範圍查詢等條件的限制,InnoDB 並沒有採用 hash 索引,但是如果能在一些特殊場景下使用 hash 索引,則可能是一個不錯的補充,而 InnoDB 正是這麼做的。

 

具體的,InnoDB 會監控對錶上索引的查找,如果觀察到某些索引被頻繁訪問,索引成爲熱數據,建立哈希索引可以帶來速度的提升,則建立哈希索引,所以稱之爲自適應(adaptive)的。自適應哈希索引通過緩衝池的 B+ 樹構造而來,因此建立的速度很快。而且不需要將整個表都建哈希索引,InnoDB 會自動根據訪問的頻率和模式來爲某些頁建立哈希索引。

 

預讀(read ahead):

InnoDB 在 I/O 的優化上有個比較重要的特性爲預讀,當 InnoDB 預計某些 page 可能很快就會需要用到時,它會異步地將這些 page 提前讀取到緩衝池(buffer pool)中,這其實有點像空間局部性的概念。

 

空間局部性(spatial locality):如果一個數據項被訪問,那麼與他地址相鄰的數據項也可能很快被訪問。

 

InnoDB使用兩種預讀算法來提高I/O性能:線性預讀(linear read-ahead)和隨機預讀(randomread-ahead)。

 

其中,線性預讀以 extent(塊,1個 extent 等於64個 page)爲單位,而隨機預讀放到以 extent 中的 page 爲單位。線性預讀着眼於將下一個extent 提前讀取到 buffer pool 中,而隨機預讀着眼於將當前 extent 中的剩餘的 page 提前讀取到 buffer pool 中。

 

線性預讀(Linear read-ahead):線性預讀方式有一個很重要的變量 innodb_read_ahead_threshold,可以控制 Innodb 執行預讀操作的觸發閾值。如果一個 extent 中的被順序讀取的 page 超過或者等於該參數變量時,Innodb將會異步的將下一個 extent 讀取到 buffer pool中,innodb_read_ahead_threshold 可以設置爲0-64(一個 extend 上限就是64頁)的任何值,默認值爲56,值越高,訪問模式檢查越嚴格。

 

隨機預讀(Random read-ahead): 隨機預讀方式則是表示當同一個 extent 中的一些 page 在 buffer pool 中發現時,Innodb 會將該 extent 中的剩餘 page 一併讀到 buffer pool中,由於隨機預讀方式給 Innodb code 帶來了一些不必要的複雜性,同時在性能也存在不穩定性,在5.5中已經將這種預讀方式廢棄。要啓用此功能,請將配置變量設置 innodb_random_read_ahead 爲ON。

 

二狗:說說共享鎖和排他鎖?

共享鎖又稱爲讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是隻能讀不能修改。

 

排他鎖又稱爲寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其他鎖並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務可以對數據就行讀取和修改。

 

常見的幾種 SQL 語句的加鎖情況如下:

select * from table:不加鎖

update/insert/delete:排他鎖

select * from table where id = 1 for update:id爲索引,加排他鎖

select * from table where id = 1 lock in share mode:id爲索引,加共享鎖

 

二狗:說說數據庫的行鎖和表鎖?

行鎖:操作時只鎖某一(些)行,不對其它行有影響。開銷大,加鎖慢;會出現死鎖;鎖定粒度小,發生鎖衝突的概率低,併發度高。

 

表鎖:即使操作一條記錄也會鎖住整個表。開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突概率高,併發度最低。

 

頁鎖:操作時鎖住一頁數據(16kb)。開銷和加鎖速度介於表鎖和行鎖之間;會出現死鎖;鎖定粒度介於表鎖和行鎖之間,併發度一般。

 

InnoDB 有行鎖和表鎖,MyIsam 只有表鎖。

 

二狗:InnoDB 的行鎖是怎麼實現的?

InnoDB 行鎖是通過索引上的索引項來實現的。意味者:只有通過索引條件檢索數據,InnoDB 纔會使用行級鎖,否則,InnoDB將使用表鎖!

 

對於主鍵索引:直接鎖住鎖住主鍵索引即可。

對於普通索引:先鎖住普通索引,接着鎖住主鍵索引,這是因爲一張表的索引可能存在多個,通過主鍵索引才能確保鎖是唯一的,不然如果同時有2個事務對同1條數據的不同索引分別加鎖,那就可能存在2個事務同時操作一條數據了。

 

二狗:InnoDB 鎖的算法有哪幾種?

Record lock:記錄鎖,單條索引記錄上加鎖,鎖住的永遠是索引,而非記錄本身。

Gap lock:間隙鎖,在索引記錄之間的間隙中加鎖,或者是在某一條索引記錄之前或者之後加鎖,並不包括該索引記錄本身。

Next-key lock:Record lock 和 Gap lock 的結合,即除了鎖住記錄本身,也鎖住索引之間的間隙。

 

二狗:MySQL 如何實現悲觀鎖和樂觀鎖?

樂觀鎖:更新時帶上版本號(cas更新)

悲觀鎖:使用共享鎖和排它鎖,select...lock in share mode,select…for update。

 

二狗:InnoDB 和 MyISAM 的區別?

對比項

InnoDB

MyIsam

事務

支持

不支持

鎖類型

行鎖、表鎖

表鎖

緩存

緩存索引和數據

只緩存索引

主鍵

必須有,用於實現聚簇索引

可以沒有

索引

B+樹,主鍵是聚簇索引

B+樹,非聚簇索引

select count(*) from table

較慢,掃描全表

賊快,用一個變量保存了表的行數,只需讀出該變量即可

hash索引

支持

不支持

記錄存儲順序

按主鍵大小有序插入

按記錄插入順序保存

外鍵

支持

不支持

全文索引

5.7 支持

支持

關注點

事務

性能

 

二狗:存儲引擎的選擇?

沒有特殊情況,使用 InnoDB 即可。如果表中絕大多數都只是讀查詢,可以考慮 MyISAM。

 

二狗:explain 用過嗎,有哪些字段分別是啥意思?

explain 字段有:

  • id:標識符

  • select_type:查詢的類型

  • table:輸出結果集的表

  • partitions:匹配的分區

  • type:表的連接類型

  • possible_keys:查詢時,可能使用的索引

  • key:實際使用的索引

  • key_len:使用的索引字段的長度

  • ref:列與索引的比較

  • rows:估計要檢查的行數

  • filtered:按表條件過濾的行百分比

  • Extra:附加信息

 

二狗:type 中有哪些常見的值?

按類型排序,從好到壞,常見的有:const > eq_ref > ref > range > index > ALL。

  • const:通過主鍵或唯一鍵查詢,並且結果只有1行(也就是用等號查詢)。因爲僅有一行,所以優化器的其餘部分可以將這一行中的列值視爲常量。

  • eq_ref:通常出現於兩表關聯查詢時,使用主鍵或者非空唯一鍵關聯,並且查詢條件不是主鍵或唯一鍵的等號查詢。

  • ref:通過普通索引查詢,並且使用的等號查詢。

  • range:索引的範圍查找(>=、<、in 等)。

  • index:全索引掃描。

  • All:全表掃描

 

二狗:explain 主要關注哪些字段?

主要關注 type、key、row、extra 等字段。主要是看是否使用了索引,是否掃描了過多的行數,是否出現 Using temporary、Using filesort 等一些影響性能的主要指標。

 

二狗:如何做慢 SQL 優化?

首先要搞明白慢的原因是什麼:是查詢條件沒有命中索引?還是 load 了不需要的數據列?還是數據量太大?所以優化也是針對這三個方向來的。

  • 首先用 explain 分析語句的執行計劃,查看使用索引的情況,是不是查詢沒走索引,如果可以加索引解決,優先採用加索引解決。

  • 分析語句,看看是否存在一些導致索引失效的用法,是否 load 了額外的數據,是否加載了許多結果中並不需要的列,對語句進行分析以及重寫。

  • 如果對語句的優化已經無法進行,可以考慮表中的數據量是否太大,如果是的話可以進行垂直拆分或者水平拆分。

 

二狗:說說 MySQL 的主從複製?

MySQL主從複製涉及到三個線程,一個運行在主節點(Log Dump Thread),其餘兩個(I/O Thread,SQL Thread)運行在從節點,如下圖所示

主從複製默認是異步的模式,具體過程如下。

1)從節點上的I/O 線程連接主節點,並請求從指定日誌文件(bin log file)的指定位置(bin log position,或者從最開始的日誌)之後的日誌內容;

2)主節點接收到來自從節點的 I/O請求後,讀取指定文件的指定位置之後的日誌信息,返回給從節點。返回信息中除了日誌所包含的信息之外,還包括本次返回的信息的 bin-log file 以及 bin-log position;從節點的 I/O 進程接收到內容後,將接收到的日誌內容更新到 relay log 中,並將讀取到的 bin log file(文件名)和position(位置)保存到 master-info 文件中,以便在下一次讀取的時候能夠清楚的告訴 Master “我需要從某個bin-log 的哪個位置開始往後的日誌內容”;

3)從節點的 SQL 線程檢測到 relay-log 中新增加了內容後,會解析 relay-log 的內容,並在本數據庫中執行。

 

二狗:異步複製,主庫宕機後,數據可能丟失?

可以使用半同步複製或全同步複製。

 

半同步複製:

修改語句寫入bin log後,不會立即給客戶端返回結果。而是首先通過log dump 線程將 binlog 發送給從節點,從節點的 I/O 線程收到 binlog 後,寫入到 relay log,然後返回 ACK 給主節點,主節點 收到 ACK 後,再返回給客戶端成功。

 

半同步複製的特點:

  • 確保事務提交後 binlog 至少傳輸到一個從庫

  • 不保證從庫應用完這個事務的 binlog

  • 性能有一定的降低,響應時間會更長

  • 網絡異常或從庫宕機,卡主主庫,直到超時或從庫恢復

 

全同步複製:主節點和所有從節點全部執行了該事務並確認纔會向客戶端返回成功。因爲需要等待所有從庫執行完該事務才能返回,所以全同步複製的性能必然會收到嚴重的影響。

 

二狗:主庫寫壓力大,從庫複製很可能出現延遲?

可以使用並行複製(並行是指從庫多個SQL線程並行執行 relay log),解決從庫複製延遲的問題。

MySQL 5.7 中引入基於組提交的並行複製,其核心思想:一個組提交的事務都是可以並行回放,因爲這些事務都已進入到事務的 prepare 階段,則說明事務之間沒有任何衝突(否則就不可能提交)。

判斷事務是否處於一個組是通過 last_committed 變量,last_committed 表示事務提交的時候,上次事務提交的編號,如果事務具有相同的 last_committed,則表示這些事務都在一組內,可以進行並行的回放。

 

推薦閱讀

面試阿里,HashMap 這一篇就夠了  

面試必問的線程池,你懂了嗎?

 

我不能保證所寫的每個地方都是對的,但是至少能保證所寫每一句話、每一行代碼都經過了認真的推敲、仔細的斟酌。

當你的才華還撐不起你的野心的時候,你就應該靜下心來學習。

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