首頁/ 汽車/ 正文

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

作為SQL Boy,基礎部分不會有人不會吧?面試也不怎麼問,基礎掌握不錯的小夥伴可以

跳過

這一部分。當然,可能會現場寫一些SQL語句,SQ語句可以透過牛客、LeetCode、LintCode之類的網站來練習。

1。 什麼是內連線、外連線、交叉連線、笛卡爾積呢?

內連線(inner join):取得兩張表中滿足存在連線匹配關係的記錄。

外連線(outer join):不只取得兩張表中滿足存在連線匹配關係的記錄,還包括某張表(或兩張表)中不滿足匹配關係的記錄。

交叉連線(cross join):顯示兩張表所有記錄一一對應,沒有匹配關係進行篩選,它是笛卡爾積在SQL中的實現,如果A表有m行,B表有n行,那麼A和B交叉連線的結果就有m*n行。

笛卡爾積:是數學中的一個概念,例如集合A={a,b},集合B={1,2,3},那麼A✖️B={,}。

2。 那MySQL 的內連線、左連線、右連線有有什麼區別?

MySQL的連線主要分為內連線和外連線,外連線常用的有左連線、右連線。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

inner join 內連線,在兩張表進行連線查詢時,只保留兩張表中完全匹配的結果集

left join 在兩張表進行連線查詢時,會返回左表所有的行,即使在右表中沒有匹配的記錄。

right join 在兩張表進行連線查詢時,會返回右表所有的行,即使在左表中沒有匹配的記錄。

3。說一下資料庫的三大正規化?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

第一正規化:資料表中的每一列(每個欄位)都不可以再拆分。 例如使用者表,使用者地址還可以拆分成國家、省份、市,這樣才是符合第一正規化的。

第二正規化:在第一正規化的基礎上,非主鍵列完全依賴於主鍵,而不能是依賴於主鍵的一部分。 例如訂單表裡,儲存了商品資訊(商品價格、商品型別),那就需要把商品ID和訂單ID作為聯合主鍵,才滿足第二正規化。

第三正規化:在滿足第二正規化的基礎上,表中的非主鍵只依賴於主鍵,而不依賴於其他非主鍵。 例如訂單表,就不能儲存使用者資訊(姓名、地址)。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

三大正規化的作用是為了控制資料庫的冗餘,是對空間的節省,實際上,一般網際網路公司的設計都是反正規化的,透過冗餘一些資料,避免跨表跨庫,利用空間換時間,提高效能。

4。varchar與char的區別?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

char

char表示定長字串,長度是固定的;

如果插入資料的長度小於char的固定長度時,則用空格填充;

因為長度固定,所以存取速度要比varchar快很多,甚至能快50%,但正因為其長度固定,所以會佔據多餘的空間,是空間換時間的做法;

對於char來說,最多能存放的字元個數為255,和編碼無關

varchar

varchar表示可變長字串,長度是可變的;

插入的資料是多長,就按照多長來儲存;

varchar在存取方面與char相反,它存取慢,因為長度不固定,但正因如此,不佔據多餘的空間,是時間換空間的做法;

對於varchar來說,最多能存放的字元個數為65532

日常的設計,對於長度相對固定的字串,可以使用char,對於長度不確定的,使用varchar更合適一些。

5。blob和text有什麼區別?

blob用於儲存二進位制資料,而text用於儲存大字串。

blob沒有字符集,text有一個字符集,並且根據字符集的校對規則對值進行排序和比較

6。DATETIME和TIMESTAMP的異同?

相同點

兩個資料型別儲存時間的表現格式一致。均為 YYYY-MM-DD HH:MM:SS

兩個資料型別都包含「日期」和「時間」部分。

兩個資料型別都可以儲存微秒的小數秒(秒後6位小數秒)

區別

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

日期範圍

:DATETIME 的日期範圍是 1000-01-01 00:00:00。000000 到 9999-12-31 23:59:59。999999;TIMESTAMP 的時間範圍是1970-01-01 00:00:01。000000 UTC到 ``2038-01-09 03:14:07。999999 UTC

儲存空間

:DATETIME 的儲存空間為 8 位元組;TIMESTAMP 的儲存空間為 4 位元組

時區相關

:DATETIME 儲存時間與時區無關;TIMESTAMP 儲存時間與時區有關,顯示的值也依賴於時區

預設值

:DATETIME 的預設值為 null;TIMESTAMP 的欄位預設不為空(not null),預設值為當前時間(CURRENT_TIMESTAMP)

7。MySQL中 in 和 exists 的區別?

MySQL中的in語句是把外表和內表作hash 連線,而exists語句是對外表作loop迴圈,每次loop迴圈再對內表進行查詢。我們可能認為exists比in語句的效率要高,這種說法其實是不準確的,要區分情景:

如果查詢的兩個表大小相當,那麼用in和exists差別不大。

如果兩個表中一個較小,一個是大表,則子查詢表大的用exists,子查詢表小的用in。

not in 和not exists:如果查詢語句使用了not in,那麼內外表都進行全表掃描,沒有用到索引;而not extsts的子查詢依然能用到表上的索引。所以無論那個表大,用not exists都比not in要快。

8。MySQL裡記錄貨幣用什麼欄位型別比較好?

貨幣在資料庫中MySQL常用Decimal和Numric型別表示,這兩種型別被MySQL實現為同樣的型別。他們被用於儲存與貨幣有關的資料。

例如salary DECIMAL(9,2),9(precision)代表將被用於儲存值的總的小數位數,而2(scale)代表將被用於儲存小數點後的位數。儲存在salary列中的值的範圍是從-9999999。99到9999999。99。

DECIMAL和NUMERIC值作為字串儲存,而不是作為二進位制浮點數,以便儲存那些值的小數精度。

之所以不使用float或者double的原因:因為float和double是以二進位制儲存的,所以有一定的誤差。

9。MySQL怎麼儲存emoji?

MySQL可以直接使用字串儲存emoji。

但是需要注意的,utf8 編碼是不行的,MySQL中的utf8是閹割版的 utf8,它最多隻用 3 個位元組儲存字元,所以儲存不了表情。那該怎麼辦?

需要使用utf8mb4編碼。

alter table blogs modify content text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci not null;

10。drop、delete與truncate的區別?

三者都表示刪除,但是三者有一些差別:

delete

truncate

drop

型別

屬於DML

屬於DDL

屬於DDL

回滾

可回滾

不可回滾

不可回滾

刪除內容

表結構還在,刪除表的全部或者一部分資料行

表結構還在,刪除表中的所有資料

從資料庫中刪除表,所有資料行,索引和許可權也會被刪除

刪除速度

刪除速度慢,需要逐行刪除

刪除速度快

刪除速度最快

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

11。UNION與UNION ALL的區別?

如果使用UNION ALL,不會合並重復的記錄行

效率 UNION 高於 UNION ALL

12。count(1)、count(*) 與 count(列名) 的區別?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

執行效果

count(*)包括了所有的列,相當於行數,在統計結果的時候,不會忽略列值為NULL

count(1)包括了忽略所有列,用1代表程式碼行,在統計結果的時候,不會忽略列值為NULL

count(列名)只包括列名那一列,在統計結果的時候,會忽略列值為空(這裡的空不是隻空字串或者0,而是表示null)的計數,即某個欄位值為NULL時,不統計。

執行速度

列名為主鍵,count(列名)會比count(1)快

列名不為主鍵,count(1)會比count(列名)快

如果表多個列並且沒有主鍵,則 count(1) 的執行效率優於 count(*)

如果有主鍵,則 select count(主鍵)的執行效率是最優的

如果表只有一個欄位,則 select count(*)最優。

13。一條SQL查詢語句的執行順序?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

FROM

:對FROM子句中的左表和右表執行笛卡兒積(Cartesianproduct),產生虛擬表VT1

ON

:對虛擬表VT1應用ON篩選,只有那些符合的行才被插入虛擬表VT2中

JOIN

:如果指定了OUTER JOIN(如LEFT OUTER JOIN、RIGHT OUTER JOIN),那麼保留表中未匹配的行作為外部行新增到虛擬表VT2中,產生虛擬表VT3。如果FROM子句包含兩個以上表,則對上一個連線生成的結果表VT3和下一個表重複執行步驟1)~步驟3),直到處理完所有的表為止

WHERE

:對虛擬表VT3應用WHERE過濾條件,只有符合的記錄才被插入虛擬表VT4中

GROUP BY

:根據GROUP BY子句中的列,對VT4中的記錄進行分組操作,產生VT5

CUBE|ROLLUP

:對錶VT5進行CUBE或ROLLUP操作,產生表VT6

HAVING

:對虛擬表VT6應用HAVING過濾器,只有符合的記錄才被插入虛擬表VT7中。

SELECT

:第二次執行SELECT操作,選擇指定的列,插入到虛擬表VT8中

DISTINCT

:去除重複資料,產生虛擬表VT9

ORDER BY

:將虛擬表VT9中的記錄按照進行排序操作,產生虛擬表VT10。11)

LIMIT

:取出指定行的記錄,產生虛擬表VT11,並返回給查詢使用者

資料庫架構

14。說說 MySQL 的基礎架構?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

MySQL邏輯架構圖主要分三層:

客戶端:最上層的服務並不是MySQL所獨有的,大多數基於網路的客戶端/伺服器的工具或者服務都有類似的架構。比如連線處理、授權認證、安全等等。

Server層:大多數MySQL的核心服務功能都在這一層,包括查詢解析、分析、最佳化、快取以及所有的內建函式(例如,日期、時間、數學和加密函式),所有跨儲存引擎的功能都在這一層實現:儲存過程、觸發器、檢視等。

儲存引擎層:第三層包含了儲存引擎。儲存引擎負責MySQL中資料的儲存和提取。Server層透過API與儲存引擎進行通訊。這些介面遮蔽了不同儲存引擎之間的差異,使得這些差異對上層的查詢過程透明。

15。一條 SQL 查詢語句在 MySQL 中如何執行的?

先檢查該語句是否有許可權,如果沒有許可權,直接返回錯誤資訊,如果有許可權會先查詢快取 (MySQL8。0 版本以前)。

如果沒有快取,分析器進行語法分析,提取 sql 語句中 select 等關鍵元素,然後判斷 sql 語句是否有語法錯誤,比如關鍵詞是否正確等等。

語法解析之後,MySQL的伺服器會對查詢的語句進行最佳化,確定執行的方案。

完成查詢最佳化後,按照生成的執行計劃呼叫資料庫引擎介面,返回執行結果。

儲存引擎

16。MySQL有哪些常見儲存引擎?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

主要儲存引擎以及功能如下:

功能

MylSAM

MEMORY

InnoDB

儲存限制

256TB

RAM

64TB

支援事務

No

No

Yes

支援全文索引

Yes

No

Yes

支援樹索引

Yes

Yes

Yes

支援雜湊索引

No

Yes

Yes

支援資料快取

No

N/A

Yes

支援外來鍵

No

No

Yes

MySQL5。5之前,預設儲存引擎是MylSAM,5。5之後變成了InnoDB。

InnoDB支援的雜湊索引是自適應的,InnoDB會根據表的使用情況自動為表生成雜湊索引,不能人為干預是否在一張表中生成雜湊索引。

MySQL 5。6開始InnoDB支援全文索引。

17。那儲存引擎應該怎麼選擇?

大致上可以這麼選擇:

大多數情況下,使用預設的InnoDB就夠了。如果要提供提交、回滾和恢復的事務安全(ACID 相容)能力,並要求實現併發控制,InnoDB 就是比較靠前的選擇了。

如果資料表主要用來插入和查詢記錄,則 MyISAM 引擎提供較高的處理效率。

如果只是臨時存放資料,資料量不大,並且不需要較高的資料安全性,可以選擇將資料儲存在記憶體的 MEMORY 引擎中,MySQL 中使用該引擎作為臨時表,存放查詢的中間結果。

使用哪一種引擎可以根據需要靈活選擇,因為儲存引擎是基於表的,所以一個數據庫中多個表可以使用不同的引擎以滿足各種效能和實際需求。使用合適的儲存引擎將會提高整個資料庫的效能。

18。InnoDB和MylSAM主要有什麼區別?

PS:MySQL8。0都開始慢慢流行了,如果不是面試,MylSAM其實可以不用怎麼了解。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

1. 儲存結構

:每個MyISAM在磁碟上儲存成三個檔案;InnoDB所有的表都儲存在同一個資料檔案中(也可能是多個檔案,或者是獨立的表空間檔案),InnoDB表的大小隻受限於作業系統檔案的大小,一般為2GB。

2. 事務支援

:MyISAM不提供事務支援;InnoDB提供事務支援事務,具有事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全特性。

3 最小鎖粒度

:MyISAM只支援表級鎖,更新時會鎖住整張表,導致其它查詢和更新都會被阻塞InnoDB支援行級鎖。

4. 索引型別

:MyISAM的索引為聚簇索引,資料結構是B樹;InnoDB的索引是非聚簇索引,資料結構是B+樹。

5. 主鍵必需

:MyISAM允許沒有任何索引和主鍵的表存在;InnoDB如果沒有設定主鍵或者非空唯一索引,

就會自動生成一個6位元組的主鍵(使用者不可見)

,資料是主索引的一部分,附加索引儲存的是主索引的值。

6. 表的具體行數

:MyISAM儲存了表的總行數,如果select count(

) from table;會直接取出出該值; InnoDB沒有儲存表的總行數,如果使用select count(

) from table;就會遍歷整個表;但是在加了wehre條件後,MyISAM和InnoDB處理的方式都一樣。

7. 外來鍵支援

:MyISAM不支援外來鍵;InnoDB支援外來鍵。

日誌

19。MySQL日誌檔案有哪些?分別介紹下作用?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

MySQL日誌檔案有很多,包括 :

錯誤日誌

(error log):錯誤日誌檔案對MySQL的啟動、執行、關閉過程進行了記錄,能幫助定位MySQL問題。

慢查詢日誌

(slow query log):慢查詢日誌是用來記錄執行時間超過 long_query_time 這個變數定義的時長的查詢語句。透過慢查詢日誌,可以查找出哪些查詢語句的執行效率很低,以便進行最佳化。

一般查詢日誌

(general log):一般查詢日誌記錄了所有對MySQL資料庫請求的資訊,無論請求是否正確執行。

二進位制日誌

(bin log):關於二進位制日誌,它記錄了資料庫所有執行的DDL和DML語句(除了資料查詢語句select、show等),以事件形式記錄並儲存在二進位制檔案中。

還有兩個InnoDB儲存引擎特有的日誌檔案:

重做日誌

(redo log):重做日誌至關重要,因為它們記錄了對於InnoDB儲存引擎的事務日誌。

回滾日誌

(undo log):回滾日誌同樣也是InnoDB引擎提供的日誌,顧名思義,回滾日誌的作用就是對資料進行回滾。當事務對資料庫進行修改,InnoDB引擎不僅會記錄redo log,還會生成對應的undo log日誌;如果事務執行失敗或呼叫了rollback,導致事務需要回滾,就可以利用undo log中的資訊將資料回滾到修改之前的樣子。

20。binlog和redo log有什麼區別?

bin log會記錄所有與資料庫有關的日誌記錄,包括InnoDB、MyISAM等儲存引擎的日誌,而redo log只記InnoDB儲存引擎的日誌。

記錄的內容不同,bin log記錄的是關於一個事務的具體操作內容,即該日誌是邏輯日誌。而redo log記錄的是關於每個頁(Page)的更改的物理情況。

寫入的時間不同,bin log僅在事務提交前進行提交,也就是隻寫磁碟一次。而在事務進行的過程中,卻不斷有redo ertry被寫入redo log中。

寫入的方式也不相同,redo log是迴圈寫入和擦除,bin log是追加寫入,不會覆蓋已經寫的檔案。

21。一條更新語句怎麼執行的瞭解嗎?

更新語句的執行是Server層和引擎層配合完成,資料除了要寫入表中,還要記錄相應的日誌。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

執行器先找引擎獲取ID=2這一行。ID是主鍵,儲存引擎檢索資料,找到這一行。如果ID=2這一行所在的資料頁本來就在記憶體中,就直接返回給執行器;否則,需要先從磁碟讀入記憶體,然後再返回。

執行器拿到引擎給的行資料,把這個值加上1,比如原來是N,現在就是N+1,得到新的一行資料,再呼叫引擎介面寫入這行新資料。

引擎將這行新資料更新到記憶體中,同時將這個更新操作記錄到redo log裡面,此時redo log處於prepare狀態。然後告知執行器執行完成了,隨時可以提交事務。

執行器生成這個操作的binlog,並把binlog寫入磁碟。

執行器呼叫引擎的提交事務介面,引擎把剛剛寫入的redo log改成提交(commit)狀態,更新完成。

從上圖可以看出,MySQL在執行更新語句的時候,在服務層進行語句的解析和執行,在引擎層進行資料的提取和儲存;同時在服務層對binlog進行寫入,在InnoDB內進行redo log的寫入。

不僅如此,在對redo log寫入時有兩個階段的提交,一是binlog寫入之前prepare狀態的寫入,二是binlog寫入之後commit狀態的寫入。

22。那為什麼要兩階段提交呢?

為什麼要兩階段提交呢?直接提交不行嗎?

我們可以假設不採用兩階段提交的方式,而是採用“單階段”進行提交,即要麼先寫入redo log,後寫入binlog;要麼先寫入binlog,後寫入redo log。這兩種方式的提交都會導致原先資料庫的狀態和被恢復後的資料庫的狀態不一致。

先寫入redo log,後寫入binlog:

在寫完redo log之後,資料此時具有crash-safe能力,因此係統崩潰,資料會恢復成事務開始之前的狀態。但是,若在redo log寫完時候,binlog寫入之前,系統發生了宕機。此時binlog沒有對上面的更新語句進行儲存,導致當使用binlog進行資料庫的備份或者恢復時,就少了上述的更新語句。從而使得id=2這一行的資料沒有被更新。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

先寫入binlog,後寫入redo log:

寫完binlog之後,所有的語句都被儲存,所以透過binlog複製或恢復出來的資料庫中id=2這一行的資料會被更新為a=1。但是如果在redo log寫入之前,系統崩潰,那麼redo log中記錄的這個事務會無效,導致實際資料庫中id=2這一行的資料並沒有更新。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

簡單說,redo log和binlog都可以用於表示事務的提交狀態,而兩階段提交就是讓這兩個狀態保持邏輯上的一致。

23。redo log怎麼刷入磁碟的知道嗎?

redo log的寫入不是直接落到磁碟,而是在記憶體中設定了一片稱之為redo log buffer的連續記憶體空間,也就是redo 日誌緩衝區。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

什麼時候會刷入磁碟?

在如下的一些情況中,log buffer的資料會刷入磁碟:

log buffer 空間不足時

log buffer 的大小是有限的,如果不停的往這個有限大小的 log buffer 裡塞入日誌,很快它就會被填滿。如果當前寫入 log buffer 的redo 日誌量已經佔滿了 log buffer 總容量的大約

一半

左右,就需要把這些日誌重新整理到磁碟上。

事務提交時

在事務提交時,為了保證永續性,會把log buffer中的日誌全部刷到磁碟。注意,這時候,除了本事務的,可能還會刷入其它事務的日誌。

後臺執行緒輸入

有一個後臺執行緒,大約每秒都會重新整理一次log buffer中的redo log到磁碟。

正常關閉伺服器時

觸發checkpoint規則

重做日誌快取、重做日誌檔案都是以

塊(block)

的方式進行儲存的,稱之為

重做日誌塊(redo log block)

,塊的大小是固定的512位元組。我們的redo log它是固定大小的,可以看作是一個邏輯上的

log group

,由一定數量的

log block

組成。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

它的寫入方式是從頭到尾開始寫,寫到末尾又回到開頭迴圈寫。

其中有兩個標記位置:

write pos是當前記錄的位置,一邊寫一邊後移,寫到第3號檔案末尾後就回到0號檔案開頭。checkpoint是當前要擦除的位置,也是往後推移並且迴圈的,擦除記錄前要把記錄更新到磁碟。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

當write_pos追上checkpoint時,表示redo log日誌已經寫滿。這時候就不能接著往裡寫資料了,需要執行checkpoint規則騰出可寫空間。

所謂的

checkpoint規則

,就是checkpoint觸發後,將buffer中日誌頁都刷到磁碟。

SQL 最佳化

24。慢SQL如何定位呢?

慢SQL的監控主要透過兩個途徑:

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

慢查詢日誌

:開啟MySQL的慢查詢日誌,再透過一些工具比如mysqldumpslow去分析對應的慢查詢日誌,當然現在一般的雲廠商都提供了視覺化的平臺。

服務監控

:可以在業務的基建中加入對慢SQL的監控,常見的方案有位元組碼插樁、連線池擴充套件、ORM框架過程,對服務執行中的慢SQL進行監控和告警。

25。有哪些方式最佳化慢SQL?

慢SQL的最佳化,主要從兩個方面考慮,SQL語句本身的最佳化,以及資料庫設計的最佳化。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

避免不必要的列

這個是老生常談,但還是經常會出的情況,SQL查詢的時候,應該只查詢需要的列,而不要包含額外的列,像slect * 這種寫法應該儘量避免。

分頁最佳化

在資料量比較大,分頁比較深的情況下,需要考慮分頁的最佳化。

例如:

select * from table where type = 2 and level = 9 order by id asc limit 190289,10;

最佳化方案:

延遲關聯

先透過where條件提取出主鍵,在將該表與原資料表關聯,透過主鍵id提取資料行,而不是透過原來的二級索引提取資料行

例如:

select a。* from table a, (select id from table where type = 2 and level = 9 order by id asc limit 190289,10 ) b where a。id = b。id

書籤方式

書籤方式就是找到limit第一個引數對應的主鍵值,根據這個主鍵值再去過濾並limit

例如:

select * from table where id > (select * from table where type = 2 and level = 9 order by id asc limit 190

索引最佳化

合理地設計和使用索引,是最佳化慢SQL的利器。

利用覆蓋索引

InnoDB使用非主鍵索引查詢資料時會回表,但是如果索引的葉節點中已經包含要查詢的欄位,那它沒有必要再回表查詢了,這就叫覆蓋索引

例如對於如下查詢:

select name from test where city=‘上海’

我們將被查詢的欄位建立到聯合索引中,這樣查詢結果就可以直接從索引中獲取

alter table test add index idx_city_name (city, name);

低版本避免使用or查詢

在 MySQL 5。0 之前的版本要儘量避免使用 or 查詢,可以使用 union 或者子查詢來替代,因為早期的 MySQL 版本使用 or 查詢可能會導致索引失效,高版本引入了索引合併,解決了這個問題。

避免使用 != 或者 <> 運算子

SQL中,不等於運算子會導致查詢引擎放棄查詢索引,引起全表掃描,即使比較的欄位上有索引

解決方法:透過把不等於運算子改成or,可以使用索引,避免全表掃描

例如,把column<>’aaa’,改成column>’aaa’ or column<’aaa’,就可以使用索引了

適當使用字首索引

適當地使用字首所云,可以降低索引的空間佔用,提高索引的查詢效率。

比如,郵箱的字尾都是固定的“@xxx。com”,那麼類似這種後面幾位為固定值的欄位就非常適合定義為字首索引

alter table test add index index2(email(6));

PS:需要注意的是,字首索引也存在缺點,MySQL無法利用字首索引做order by和group by 操作,也無法作為覆蓋索引

避免列上函式運算

要避免在列欄位上進行算術運算或其他表示式運算,否則可能會導致儲存引擎無法正確使用索引,從而影響了查詢的效率

select * from test where id + 1 = 50;select * from test where month(updateTime) = 7;

正確使用聯合索引

使用聯合索引的時候,注意最左匹配原則。

JOIN最佳化

最佳化子查詢

儘量使用 Join 語句來替代子查詢,因為子查詢是巢狀查詢,而巢狀查詢會新建立一張臨時表,而臨時表的建立與銷燬會佔用一定的系統資源以及花費一定的時間,同時對於返回結果集比較大的子查詢,其對查詢效能的影響更大

小表驅動大表

關聯查詢的時候要拿小表去驅動大表,因為關聯的時候,MySQL內部會遍歷驅動表,再去連線被驅動表。

比如left join,左表就是驅動表,A表小於B表,建立連線的次數就少,查詢速度就被加快了。

select name from A left join B ;

適當增加冗餘欄位

增加冗餘欄位可以減少大量的連表查詢,因為多張表的連表查詢效能很低,所有可以適當的增加冗餘欄位,以減少多張表的關聯查詢,這是以空間換時間的最佳化策略

避免使用JOIN關聯太多的表

《阿里巴巴Java開發手冊》規定不要join超過三張表,第一join太多降低查詢的速度,第二join的buffer會佔用更多的記憶體。

如果不可避免要join多張表,可以考慮使用資料異構的方式異構到ES中查詢。

排序最佳化

利用索引掃描做排序

MySQL有兩種方式生成有序結果:其一是對結果集進行排序的操作,其二是按照索引順序掃描得出的結果自然是有序的

但是如果索引不能覆蓋查詢所需列,就不得不每掃描一條記錄回表查詢一次,這個讀操作是隨機IO,通常會比順序全表掃描還慢

因此,在設計索引時,儘可能使用同一個索引既滿足排序又用於查詢行

例如:

——建立索引(date,staff_id,customer_id)select staff_id, customer_id from test where date = ‘2010-01-01’ order by staff_id,customer_id;

只有當索引的列順序和ORDER BY子句的順序完全一致,並且所有列的排序方向都一樣時,才能夠使用索引來對結果做排序

UNION最佳化

條件下推

MySQL處理union的策略是先建立臨時表,然後將各個查詢結果填充到臨時表中最後再來做查詢,很多最佳化策略在union查詢中都會失效,因為它無法利用索引

最好手工將where、limit等子句下推到union的各個子查詢中,以便最佳化器可以充分利用這些條件進行最佳化

此外,除非確實需要伺服器去重,一定要使用union all,如果不加all關鍵字,MySQL會給臨時表加上distinct選項,這會導致對整個臨時表做唯一性檢查,代價很高。

26。怎麼看執行計劃(explain),如何理解其中各個欄位的含義?

explain是sql最佳化的利器,除了最佳化慢sql,平時的sql編寫,也應該先explain,檢視一下執行計劃,看看是否還有最佳化的空間。

直接在 select 語句之前增加explain關鍵字,就會返回執行計劃的資訊。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

id

列:MySQL會為每個select語句分配一個唯一的id值

select_type

列,查詢的型別,根據關聯、union、子查詢等等分類,常見的查詢型別有SIMPLE、PRIMARY。

table

列:表示 explain 的一行正在訪問哪個表。

type

列:最重要的列之一。表示關聯型別或訪問型別,即 MySQL 決定如何查詢表中的行。

效能從最優到最差分別為:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

system

system: 當表僅有一行記錄時(系統表),資料量很少,往往不需要進行磁碟IO,速度非常快

const

const:表示查詢時命中 primary key 主鍵或者 unique 唯一索引,或者被連線的部分是一個常量(const)值。這類掃描效率極高,返回資料量少,速度非常快。

eq_ref

eq_ref:查詢時命中主鍵primary key 或者 unique key索引, type 就是 eq_ref。

ref_or_null

ref_or_null:這種連線型別類似於 ref,區別在於 MySQL會額外搜尋包含NULL值的行。

index_merge

index_merge:使用了索引合併最佳化方法,查詢使用了兩個以上的索引。

unique_subquery

unique_subquery:替換下面的 IN子查詢,子查詢返回不重複的集合。

index_subquery

index_subquery:區別於unique_subquery,用於非唯一索引,可以返回重複值。

range

range:使用索引選擇行,僅檢索給定範圍內的行。簡單點說就是針對一個有索引的欄位,給定範圍檢索資料。在where語句中使用 bettween。。。and、<、>、<=、in 等條件查詢 type 都是 range。

index

index:Index 與ALL 其實都是讀全表,區別在於index是遍歷索引樹讀取,而ALL是從硬碟中讀取。

ALL

就不用多說了,全表掃描。

possible_keys

列:顯示查詢可能使用哪些索引來查詢,使用索引最佳化sql的時候比較重要。

key

列:這一列顯示 mysql 實際採用哪個索引來最佳化對該表的訪問,判斷索引是否失效的時候常用。

key_len

列:顯示了 MySQL使用

ref

列:ref 列展示的就是與索引列作等值匹配的值,常見的有:const(常量),func,NULL,欄位名。

rows

列:這也是一個重要的欄位,MySQL查詢最佳化器根據統計資訊,估算SQL要查到結果集需要掃描讀取的資料行數,這個值非常直觀顯示SQL的效率好壞,原則上rows越少越好。

Extra

列:顯示不適合在其它列的額外資訊,雖然叫額外,但是也有一些重要的資訊:

Using index:表示MySQL將使用覆蓋索引,以避免回表

Using where:表示會在儲存引擎檢索之後再進行過濾

Using temporary :表示對查詢結果排序時會使用一個臨時表。

索引

索引可以說是MySQL面試中的重中之重,一定要徹底拿下。

27。能簡單說一下索引的分類嗎?

從三個不同維度對索引分類:

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

例如從基本使用使用的角度來講:

主鍵索引: InnoDB主鍵是預設的索引,資料列不允許重複,不允許為NULL,一個表只能有一個主鍵。

唯一索引: 資料列不允許重複,允許為NULL值,一個表允許多個列建立唯一索引。

普通索引: 基本的索引型別,沒有唯一性的限制,允許為NULL值。

組合索引:多列值組成一個索引,用於組合搜尋,效率大於索引合併

28。為什麼使用索引會加快查詢?

傳統的查詢方法,是按照表的順序遍歷的,不論查詢幾條資料,MySQL需要將表的資料從頭到尾遍歷一遍。

在我們新增完索引之後,MySQL一般透過BTREE演算法生成一個索引檔案,在查詢資料庫時,找到索引檔案進行遍歷,在比較小的索引資料裡查詢,然後對映到對應的資料,能大幅提升查詢的效率。

和我們透過書的目錄,去查詢對應的內容,一樣的道理。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

29。建立索引有哪些注意點?

索引雖然是sql效能最佳化的利器,但是索引的維護也是需要成本的,所以建立索引,也要注意:

索引應該建在查詢應用頻繁的欄位

在用於 where 判斷、 order 排序和 join 的(on)欄位上建立索引。

索引的個數應該適量

索引需要佔用空間;更新時候也需要維護。

區分度低的欄位,例如性別,不要建索引。

離散度太低的欄位,掃描的行數降低的有限。

頻繁更新的值,不要作為主鍵或者索引

維護索引檔案需要成本;還會導致頁分裂,IO次數增多。

組合索引把雜湊性高(區分度高)的值放在前面

為了滿足最左字首匹配原則

建立組合索引,而不是修改單列索引。

組合索引代替多個單列索引(對於單列索引,MySQL基本只能使用一個索引,所以經常使用多個條件查詢時更適合使用組合索引)

過長的欄位,使用字首索引。 當欄位值比較長的時候,建立索引會消耗很多的空間,搜尋起來也會很慢。我們可以透過擷取欄位的前面一部分內容建立索引,這個就叫字首索引。

不建議用無序的值(例如身份證、UUID )作為索引

當主鍵具有不確定性,會造成葉子節點頻繁分裂,出現磁碟儲存的碎片化

30。索引哪些情況下會失效呢?

查詢條件包含or,可能導致索引失效

如果欄位型別是字串,where時一定用引號括起來,否則會因為隱式型別轉換,索引失效

like萬用字元可能導致索引失效。

聯合索引,查詢時的條件列不是聯合索引中的第一個列,索引失效。

在索引列上使用mysql的內建函式,索引失效。

對索引列運算(如,+、-、*、/),索引失效。

索引欄位上使用(!= 或者 < >,not in)時,可能會導致索引失效。

索引欄位上使用is null, is not null,可能導致索引失效。

左連線查詢或者右連線查詢查詢關聯的欄位編碼格式不一樣,可能導致索引失效。

MySQL最佳化器估計使用全表掃描要比使用索引快,則不使用索引。

31。索引不適合哪些場景呢?

資料量比較少的表不適合加索引

更新比較頻繁的欄位也不適合加索引

離散低的欄位不適合加索引(如性別)

32。索引是不是建的越多越好呢?

當然不是。

索引會佔據磁碟空間

索引雖然會提高查詢效率,但是會降低更新表的效率

。比如每次對錶進行增刪改操作,MySQL不僅要儲存資料,還有儲存或者更新對應的索引檔案。

33。MySQL索引用的什麼資料結構瞭解嗎?

MySQL的預設儲存引擎是InnoDB,它採用的是B+樹結構的索引。

B+樹:只有葉子節點才會儲存資料,非葉子節點只儲存鍵值。葉子節點之間使用雙向指標連線,最底層的葉子節點形成了一個雙向有序連結串列。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

在這張圖裡,有兩個重點:

最外面的方塊,的塊我們稱之為一個磁碟塊,可以看到每個磁碟塊包含幾個資料項(粉色所示)和指標(黃色/灰色所示),如根節點磁碟包含資料項17和35,包含指標P1、P2、P3,P1表示小於17的磁碟塊,P2表示在17和35之間的磁碟塊,P3表示大於35的磁碟塊。真實的資料存在於葉子節點即3、4、5……、65。非葉子節點只不儲存真實的資料,只儲存指引搜尋方向的資料項,如17、35並不真實存在於資料表中。

葉子節點之間使用雙向指標連線,最底層的葉子節點形成了一個雙向有序連結串列,可以進行範圍查詢。

34。那一棵B+樹能儲存多少條資料呢?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

假設索引欄位是 bigint 型別,長度為 8 位元組。指標大小在 InnoDB 原始碼中設定為 6 位元組,這樣一共 14 位元組。非葉子節點(一頁)可以儲存 16384/14=1170 個這樣的 單元(鍵值+指標),代表有 1170 個指標。

樹深度為 2 的時候,有 1170^2 個葉子節點,可以儲存的資料為 1170

1170

16=

21902400

在查詢資料時一次頁的查詢代表一次 IO,也就是說,一張 2000 萬左右的表,查詢資料最多需要訪問 3 次磁碟。

所以在 InnoDB 中 B+ 樹深度一般為 1-3 層,它就能滿足千萬級的資料儲存。

35。為什麼要用 B+ 樹,而不用普通二叉樹?

可以從幾個維度去看這個問題,查詢是否夠快,效率是否穩定,儲存資料多少,以及查詢磁碟次數。

為什麼不用普通二叉樹?

普通二叉樹存在退化的情況,如果它退化成連結串列,相當於全表掃描。平衡二叉樹相比於二叉查詢樹來說,查詢效率更穩定,總體的查詢速度也更快。

為什麼不用平衡二叉樹呢?

讀取資料的時候,是從磁碟讀到記憶體。如果樹這種資料結構作為索引,那每查詢一次資料就需要從磁碟中讀取一個節點,也就是一個磁碟塊,但是平衡二叉樹可是每個節點只儲存一個鍵值和資料的,如果是 B+ 樹,可以儲存更多的節點資料,樹的高度也會降低,因此讀取磁碟的次數就降下來啦,查詢效率就快。

36。為什麼用 B+ 樹而不用 B 樹呢?

B+相比較B樹,有這些優勢:

它是 B Tree 的變種,B Tree 能解決的問題,它都能解決。

B Tree 解決的兩大問題:每個節點儲存更多關鍵字;路數更多

掃庫、掃表能力更強

如果我們要對錶進行全表掃描,只需要遍歷葉子節點就可以 了,不需要遍歷整棵 B+Tree 拿到所有的資料。

B+Tree 的磁碟讀寫能力相對於 B Tree 來說更強,IO次數更少

根節點和枝節點不儲存資料區, 所以一個節點可以儲存更多的關鍵字,一次磁碟載入的關鍵字更多,IO次數更少。

排序能力更強

因為葉子節點上有下一個資料區的指標,資料形成了連結串列。

效率更加穩定

B+Tree 永遠是在葉子節點拿到資料,所以 IO 次數是穩定的。

37。Hash 索引和 B+ 樹索引區別是什麼?

B+ 樹可以進行範圍查詢,Hash 索引不能。

B+ 樹支援聯合索引的最左側原則,Hash 索引不支援。

B+ 樹支援 order by 排序,Hash 索引不支援。

Hash 索引在等值查詢上比 B+ 樹效率更高。

B+ 樹使用 like 進行模糊查詢的時候,like 後面(比如 % 開頭)的話可以起到最佳化的作用,Hash 索引根本無法進行模糊查詢。

38。聚簇索引與非聚簇索引的區別?

首先理解聚簇索引不是一種新的索引,而是而是一種

資料儲存方式

。 聚簇表示資料行和相鄰的鍵值緊湊地儲存在一起。我們熟悉的兩種儲存引擎——MyISAM採用的是非聚簇索引,InnoDB採用的是聚簇索引。

可以這麼說:

索引的資料結構是樹,聚簇索引的索引和資料儲存在一棵樹上,樹的葉子節點就是資料,非聚簇索引索引和資料不在一棵樹上。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

一個表中只能擁有一個聚簇索引,而非聚簇索引一個表可以存在多個。

聚簇索引,索引中鍵值的邏輯順序決定了表中相應行的物理順序;索引,索引中索引的邏輯順序與磁碟上行的物理儲存順序不同。

聚簇索引:物理儲存按照索引排序;非聚集索引:物理儲存不按照索引排序;

39。回表瞭解嗎?

在InnoDB儲存引擎裡,利用輔助索引查詢,先透過輔助索引找到主鍵索引的鍵值,再透過主鍵值查出主鍵索引裡面沒有符合要求的資料,它比基於主鍵索引的查詢多掃描了一棵索引樹,這個過程就叫回表。

例如:select * from user where name = ‘張三’;

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

40。覆蓋索引瞭解嗎?

在輔助索引裡面,不管是單列索引還是聯合索引,如果 select 的資料列只用輔助索引中就能夠取得,不用去查主鍵索引,這時候使用的索引就叫做覆蓋索引,避免了回表。

比如,select name from user where name = ‘張三’;

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

41。什麼是最左字首原則/最左匹配原則?

注意:最左字首原則、最左匹配原則、最左字首匹配原則這三個都是一個概念。

最左匹配原則

:在InnoDB的聯合索引中,查詢的時候只有匹配了前一個/左邊的值之後,才能匹配下一個。

根據最左匹配原則,我們建立了一個組合索引,如 (a1,a2,a3),相當於建立了(a1)、(a1,a2)和 (a1,a2,a3) 三個索引。

為什麼不從最左開始查,就無法匹配呢?

比如有一個user表,我們給 name 和 age 建立了一個組合索引。

ALTER TABLE user add INDEX comidx_name_phone (name,age);

組合索引在 B+Tree 中是複合的資料結構,它是按照從左到右的順序來建立搜尋樹的 (name 在左邊,age 在右邊)。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

從這張圖可以看出來,name 是有序的,age 是無序的。當 name 相等的時候, age 才是有序的。

這個時候我們使用where name= ‘張三‘ and age = ‘20 ‘去查詢資料的時候, B+Tree 會優先比較 name 來確定下一步應該搜尋的方向,往左還是往右。如果 name 相同的時候再比較age。但是如果查詢條件沒有 name,就不知道下一步應該查哪個 節點,因為建立搜尋樹的時候 name 是第一個比較因子,所以就沒用上索引。

42。什麼是索引下推最佳化?

索引條件下推最佳化(Index Condition Pushdown (ICP) )是MySQL5。6新增的,用於最佳化資料查詢。

不使用索引條件下推最佳化時儲存引擎透過索引檢索到資料,然後返回給MySQL Server,MySQL Server進行過濾條件的判斷。

當使用索引條件下推最佳化時,如果存在某些被索引的列的判斷條件時,MySQL Server將這一部分判斷條件

下推

給儲存引擎,然後由儲存引擎透過判斷索引是否符合MySQL Server傳遞的條件,只有當索引符合條件時才會將資料檢索出來返回給MySQL伺服器。

例如一張表,建了一個聯合索引(name, age),查詢語句:select * from t_user where name like ‘張%’ and age=10;,由於name使用了範圍查詢,根據最左匹配原則:

不使用ICP,引擎層查詢到name like ‘張%’的資料,再由Server層去過濾age=10這個條件,這樣一來,就回表了兩次,浪費了聯合索引的另外一個欄位age。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

但是,使用了索引下推最佳化,把where的條件放到了引擎層執行,直接根據name like ‘張%’ and age=10的條件進行過濾,減少了回表的次數。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

索引條件下推最佳化可以減少儲存引擎查詢基礎表的次數,也可以減少MySQL伺服器從儲存引擎接收資料的次數。

43。MySQL中有哪幾種鎖,列舉一下?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

如果按鎖粒度劃分,有以下3種:

表鎖: 開銷小,加鎖快;鎖定力度大,發生鎖衝突機率高,併發度最低;不會出現死鎖。

行鎖: 開銷大,加鎖慢;會出現死鎖;鎖定粒度小,發生鎖衝突的機率低,併發度高。

頁鎖: 開銷和加鎖速度介於表鎖和行鎖之間;會出現死鎖;鎖定粒度介於表鎖和行鎖之間,併發度一般

如果按照相容性,有兩種,

共享鎖(S Lock),也叫讀鎖(read lock),相互不阻塞。

排他鎖(X Lock),也叫寫鎖(write lock),排它鎖是阻塞的,在一定時間內,只有一個請求能執行寫入,並阻止其它鎖讀取正在寫入的資料。

44。說說InnoDB裡的行鎖實現?

我們拿這麼一個使用者表來表示行級鎖,其中插入了4行資料,主鍵值分別是1,6,8,12,現在簡化它的聚簇索引結構,只保留資料記錄。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

InnoDB的行鎖的主要實現如下:

Record Lock 記錄鎖

記錄鎖就是直接鎖定某行記錄。當我們使用唯一性的索引(包括唯一索引和聚簇索引)進行等值查詢且精準匹配到一條記錄時,此時就會直接將這條記錄鎖定。例如select * from t where id =6 for update;就會將id=6的記錄鎖定。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

Gap Lock 間隙鎖

間隙鎖(Gap Locks) 的間隙指的是兩個記錄之間邏輯上尚未填入資料的部分,是一個

左開右開空間

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

間隙鎖就是鎖定某些間隙區間的。當我們使用用等值查詢或者範圍查詢,並且沒有命中任何一個record,此時就會將對應的間隙區間鎖定。例如select * from t where id =3 for update;或者select * from t where id > 1 and id < 6 for update;就會將(1,6)區間鎖定。

Next-key Lock 臨鍵鎖

臨鍵指的是間隙加上它右邊的記錄組成的

左開右閉區間

。比如上述的(1,6]、(6,8]等。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

臨鍵鎖就是記錄鎖(Record Locks)和間隙鎖(Gap Locks)的結合,即除了鎖住記錄本身,還要再鎖住索引之間的間隙。當我們使用範圍查詢,並且命中了部分record記錄,此時鎖住的就是臨鍵區間。注意,臨鍵鎖鎖住的區間會包含最後一個record的右邊的臨鍵區間。例如select * from t where id > 5 and id <= 7 for update;會鎖住(4,7]、(7,+∞)。mysql預設行鎖型別就是臨鍵鎖(Next-Key Locks)。當使用唯一性索引,等值查詢匹配到一條記錄的時候,臨鍵鎖(Next-Key Locks)會退化成記錄鎖;沒有匹配到任何記錄的時候,退化成間隙鎖。

間隙鎖(Gap Locks)和臨鍵鎖(Next-Key Locks)都是用來解決幻讀問題的,在已提交讀(READ COMMITTED)隔離級別下,間隙鎖(Gap Locks)和臨鍵鎖(Next-Key Locks)都會失效!

上面是行鎖的三種實現演算法,除此之外,在行上還存在插入意向鎖。

Insert Intention Lock 插入意向鎖

一個事務在插入一條記錄時需要判斷一下插入位置是不是被別的事務加了意向鎖 ,如果有的話,插入操作需要等待,直到擁有 gap鎖 的那個事務提交。但是事務在等待的時候也需要在記憶體中生成一個 鎖結構 ,表明有事務想在某個 間隙 中插入新記錄,但是現在在等待。這種型別的鎖命名為 Insert Intention Locks ,也就是插入意向鎖 。

假如我們有個T1事務,給(1,6)區間加上了意向鎖,現在有個T2事務,要插入一個數據,id為4,它會獲取一個(1,6)區間的插入意向鎖,又有有個T3事務,想要插入一個數據,id為3,它也會獲取一個(1,6)區間的插入意向鎖,但是,這兩個插入意向鎖鎖不會互斥。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

45。意向鎖是什麼知道嗎?

意向鎖是一個表級鎖,不要和插入意向鎖搞混。

意向鎖的出現是為了支援InnoDB的多粒度鎖,它解決的是表鎖和行鎖共存的問題。

當我們需要給一個表加表鎖的時候,我們需要根據去判斷表中有沒有資料行被鎖定,以確定是否能加成功。

假如沒有意向鎖,那麼我們就得遍歷表中所有資料行來判斷有沒有行鎖;

有了意向鎖這個表級鎖之後,則我們直接判斷一次就知道表中是否有資料行被鎖定了。

有了意向鎖之後,要執行的事務A在申請行鎖(寫鎖)之前,資料庫會自動先給事務A申請表的意向排他鎖。當事務B去申請表的互斥鎖時就會失敗,因為表上有意向排他鎖之後事務B申請表的互斥鎖時會被阻塞。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

46。MySQL的樂觀鎖和悲觀鎖瞭解嗎?

悲觀鎖

(Pessimistic Concurrency Control):

悲觀鎖認為被它保護的資料是極其不安全的,每時每刻都有可能被改動,一個事務拿到悲觀鎖後,其他任何事務都不能對該資料進行修改,只能等待鎖被釋放才可以執行。

資料庫中的行鎖,表鎖,讀鎖,寫鎖均為悲觀鎖。

樂觀鎖(Optimistic Concurrency Control)

樂觀鎖認為資料的變動不會太頻繁。

樂觀鎖通常是透過在表中增加一個版本(version)或時間戳(timestamp)來實現,其中,版本最為常用。

事務在從資料庫中取資料時,會將該資料的版本也取出來(v1),當事務對資料變動完畢想要將其更新到表中時,會將之前取出的版本v1與資料中最新的版本v2相對比,如果v1=v2,那麼說明在資料變動期間,沒有其他事務對資料進行修改,此時,就允許事務對錶中的資料進行修改,並且修改時version會加1,以此來表明資料已被變動。

如果,v1不等於v2,那麼說明資料變動期間,資料被其他事務改動了,此時不允許資料更新到表中,一般的處理辦法是通知使用者讓其重新操作。不同於悲觀鎖,樂觀鎖通常是由開發者實現的。

47。MySQL 遇到過死鎖問題嗎,你是如何解決的?

排查死鎖的一般步驟是這樣的:

(1)檢視死鎖日誌 show engine innodb status;

(2)找出死鎖 sql

(3)分析 sql 加鎖情況

(4)模擬死鎖案發

(5)分析死鎖日誌

(6)分析死鎖結果

當然,這只是一個簡單的流程說明,實際上生產中的死鎖千奇百怪,排查和解決起來沒那麼簡單。

事務

48。MySQL 事務的四大特性說一下?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

原子性:事務作為一個整體被執行,包含在其中的對資料庫的操作要麼全部被執行,要麼都不執行。

一致性:指在事務開始之前和事務結束以後,資料不會被破壞,假如 A 賬戶給 B 賬戶轉 10 塊錢,不管成功與否,A 和 B 的總金額是不變的。

隔離性:多個事務併發訪問時,事務之間是相互隔離的,即一個事務不影響其它事務執行效果。簡言之,就是事務之間是進水不犯河水的。

永續性:表示事務完成以後,該事務對資料庫所作的操作更改,將持久地儲存在資料庫之中。

49。那ACID靠什麼保證的呢?

事務的

隔離性

是透過資料庫鎖的機制實現的。

事務的

一致性

由undo log來保證:undo log是邏輯日誌,記錄了事務的insert、update、deltete操作,回滾的時候做相反的delete、update、insert操作來恢復資料。

事務的

原子性

永續性

由redo log來保證:redolog被稱作重做日誌,是物理日誌,事務提交的時候,必須先將事務的所有日誌寫入redo log持久化,到事務的提交操作才算完成。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

50。事務的隔離級別有哪些?MySQL 的預設隔離級別是什麼?

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

讀未提交(Read Uncommitted)

讀已提交(Read Committed)

可重複讀(Repeatable Read)

序列化(Serializable)

MySQL預設的事務隔離級別是可重複讀 (Repeatable Read)。

51。什麼是幻讀,髒讀,不可重複讀呢?

事務 A、B 交替執行,事務 A 讀取到事務 B 未提交的資料,這就是

髒讀

在一個事務範圍內,兩個相同的查詢,讀取同一條記錄,卻返回了不同的資料,這就是

不可重複讀

事務 A 查詢一個範圍的結果集,另一個併發事務 B 往這個範圍中插入 / 刪除了資料,並靜悄悄地提交,然後事務 A 再次查詢相同的範圍,兩次讀取得到的結果集不一樣了,這就是

幻讀

不同的隔離級別,在併發事務下可能會發生的問題:

隔離級別

髒讀

不可重複讀

幻讀

Read Uncommited 讀取未提交

Read Commited 讀取已提交

Repeatable Read 可重複讀

Serialzable 可序列化

52。事務的各個隔離級別都是如何實現的?

讀未提交

讀未提交,就不用多說了,採取的是讀不加鎖原理。

事務讀不加鎖,不阻塞其他事務的讀和寫

事務寫阻塞其他事務寫,但不阻塞其他事務讀;

讀取已提交&可重複讀

讀取已提交和可重複讀級別利用了ReadView和MVCC,也就是每個事務只能讀取它能看到的版本(ReadView)。

READ COMMITTED:每次讀取資料前都生成一個ReadView

REPEATABLE READ : 在第一次讀取資料時生成一個ReadView

序列化

序列化的實現採用的是讀寫都加鎖的原理。

序列化的情況下,對於同一行事務,寫會加寫鎖,讀會加讀鎖。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。

53。MVCC瞭解嗎?怎麼實現的?

MVCC(Multi Version Concurrency Control),中文名是多版本併發控制,簡單來說就是透過維護資料歷史版本,從而解決併發訪問情況下的讀一致性問題。關於它的實現,要抓住幾個關鍵點,

隱式欄位、undo日誌、版本鏈、快照讀&當前讀、Read View

版本鏈

對於InnoDB儲存引擎,每一行記錄都有兩個隱藏列

DB_TRX_ID、DB_ROLL_PTR

DB_TRX_ID,事務ID,每次修改時,都會把該事務ID複製給DB_TRX_ID;

DB_ROLL_PTR,回滾指標,指向回滾段的undo日誌。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

假如有一張user表,表中只有一行記錄,當時插入的事務id為80。此時,該條記錄的示例圖如下:

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

接下來有兩個DB_TRX_ID分別為100、200的事務對這條記錄進行update操作,整個過程如下:

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

由於每次變動都會先把undo日誌記錄下來,並用DB_ROLL_PTR指向undo日誌地址。因此可以認為,

對該條記錄的修改日誌串聯起來就形成了一個版本鏈,版本鏈的頭節點就是當前記錄最新的值

。如下:

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

ReadView

對於Read Committed和Repeatable Read隔離級別來說,都需要讀取已經提交的事務所修改的記錄,也就是說如果版本鏈中某個版本的修改沒有提交,那麼該版本的記錄時不能被讀取的。所以需要確定在Read Committed和Repeatable Read隔離級別下,版本鏈中哪個版本是能被當前事務讀取的。於是就引入了ReadView這個概念來解決這個問題。

Read View就是事務執行

快照讀

時,產生的讀檢視,相當於某時刻表記錄的一個快照,透過這個快照,我們可以獲取:

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

m_ids :表示在生成 ReadView 時當前系統中活躍的讀寫事務的事務id 列表。

min_trx_id :表示在生成 ReadView 時當前系統中活躍的讀寫事務中最小的 事務id ,也就是 m_ids 中的最小值。

max_trx_id :表示生成 ReadView 時系統中應該分配給下一個事務的 id 值。

creator_trx_id :表示生成該 ReadView 的事務的 事務id

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

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

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

如果被訪問版本的 DB_TRX_ID 屬性值大於 ReadView 中的 max_trx_id 值,表明生成該版本的事務在當前事務生成 ReadView 後才開啟,所以該版本不可以被當前事務訪問。

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

如果某個版本的資料對當前事務不可見的話,那就順著版本鏈找到下一個版本的資料,繼續按照上邊的步驟判斷可見性,依此類推,直到版本鏈中的最後一個版本。如果最後一個版本也不可見的話,那麼就意味著該條記錄對該事務完全不可見,查詢結果就不包含該記錄。

在 MySQL 中, READ COMMITTED 和 REPEATABLE READ 隔離級別的的一個非常大的區別就是它們生成ReadView的時機不同。

READ COMMITTED 是

每次讀取資料前都生成一個ReadView

,這樣就能保證自己每次都能讀到其它事務提交的資料;REPEATABLE READ 是在

第一次讀取資料時生成一個ReadView

,這樣就能保證後續讀取的結果完全一致。

高可用/效能

54。資料庫讀寫分離了解嗎?

讀寫分離的基本原理是將資料庫讀寫操作分散到不同的節點上,下面是基本架構圖:

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

讀寫分離的基本實現是:

資料庫伺服器搭建主從叢集,一主一從、一主多從都可以。

資料庫主機負責讀寫操作,從機只負責讀操作。

資料庫主機透過複製將資料同步到從機,每臺數據庫伺服器都儲存了所有的業務資料。

業務伺服器將寫操作發給資料庫主機,將讀操作發給資料庫從機。

55。那讀寫分離的分配怎麼實現呢?

將讀寫操作區分開來,然後訪問不同的資料庫伺服器,一般有兩種方式:程式程式碼封裝和中介軟體封裝。

程式程式碼封裝

程式程式碼封裝指在程式碼中抽象一個數據訪問層(所以有的文章也稱這種方式為 “中間層封裝” ) ,實現讀寫操作分離和資料庫伺服器連線的管理。例如,基於 Hibernate 進行簡單封裝,就可以實現讀寫分離:

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

目前開源的實現方案中,淘寶的 TDDL (Taobao Distributed Data Layer, 外號:頭都大了)是比較有名的。

中介軟體封裝

中介軟體封裝指的是獨立一套系統出來,實現讀寫操作分離和資料庫伺服器連線的管理。中介軟體對業務伺服器提供 SQL 相容的協議,業務伺服器無須自己進行讀寫分離。

對於業務伺服器來說,訪問中介軟體和訪問資料庫沒有區別,事實上在業務伺服器看來,中介軟體就是一個數據庫伺服器。

其基本架構是:

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

56。主從複製原理了解嗎?

master資料寫入,更新binlog

master建立一個dump執行緒向slave推送binlog

slave連線到master的時候,會建立一個IO執行緒接收binlog,並記錄到relay log中繼日誌中

slave再開啟一個sql執行緒讀取relay log事件並在slave執行,完成同步

slave記錄自己的binglog

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

57。主從同步延遲怎麼處理?

主從同步延遲的原因

一個伺服器開放N個連結給客戶端來連線的,這樣有會有大併發的更新操作, 但是從伺服器的裡面讀取 binlog 的執行緒僅有一個,當某個 SQL 在從伺服器上執行的時間稍長 或者由於某個 SQL 要進行鎖表就會導致,主伺服器的 SQL 大量積壓,未被同步到從伺服器裡。這就導致了主從不一致, 也就是主從延遲。

主從同步延遲的解決辦法

解決主從複製延遲有幾種常見的方法:

寫操作後的讀操作指定發給資料庫主伺服器

例如,註冊賬號完成後,登入時讀取賬號的讀操作也發給資料庫主伺服器。這種方式和業務強繫結,對業務的侵入和影響較大,如果哪個新來的程式設計師不知道這樣寫程式碼,就會導致一個bug。

讀從機失敗後再讀一次主機

這就是通常所說的 “二次讀取” ,二次讀取和業務無繫結,只需要對底層資料庫訪問的 API 進行封裝即可,實現代價較小,不足之處在於如果有很多二次讀取,將大大增加主機的讀操作壓力。例如,駭客暴力破解賬號,會導致大量的二次讀取操作,主機可能頂不住讀操作的壓力從而崩潰。

關鍵業務讀寫操作全部指向主機,非關鍵業務採用讀寫分離

例如,對於一個使用者管理系統來說,註冊 + 登入的業務讀寫操作全部訪問主機,使用者的介紹、爰好、等級等業務,可以採用讀寫分離,因為即使使用者改了自己的自我介紹,在查詢時卻看到了自我介紹還是舊的,業務影響與不能登入相比就小很多,還可以忍受。

58。你們一般是怎麼分庫的呢?

垂直分庫:以表為依據,按照業務歸屬不同,將不同的表拆分到不同的庫中。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

水平分庫:以欄位為依據,按照一定策略(hash、range 等),將一個庫中的資料拆分到多個庫中。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

59。那你們是怎麼分表的?

水平分表:以欄位為依據,按照一定策略(hash、range 等),將一個表中的資料拆分到多個表中。

垂直分表:以欄位為依據,按照欄位的活躍性,將表中欄位拆到不同的表(主表和擴充套件表)中。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

60。水平分表有哪幾種路由方式?

什麼是路由呢?就是資料應該分到哪一張表。

水平分表主要有三種路由方式:

範圍路由

:選取有序的資料列 (例如,整形、時間戳等) 作為路由的條件,不同分段分散到不同的資料庫表中。

我們可以觀察一些支付系統,發現只能查一年範圍內的支付記錄,這個可能就是支付公司按照時間進行了分表。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

範圍路由設計的複雜點主要體現在分段大小的選取上,分段太小會導致切分後子表數量過多,增加維護複雜度;分段太大可能會導致單表依然存在效能問題,一般建議分段大小在 100 萬至2000 萬之間,具體需要根據業務選取合適的分段大小。

範圍路由的優點是可以隨著資料的增加平滑地擴充新的表。例如,現在的使用者是 100 萬,如果增加到 1000 萬,只需要增加新的表就可以了,原有的資料不需要動。範圍路由的一個比較隱含的缺點是分佈不均勻,假如按照 1000 萬來進行分表,有可能某個分段實際儲存的資料量只有 1000 條,而另外一個分段實際儲存的資料量有 900 萬條。

Hash 路由

:選取某個列 (或者某幾個列組合也可以) 的值進行 Hash 運算,然後根據 Hash 結果分散到不同的資料庫表中。

同樣以訂單 id 為例,假如我們一開始就規劃了 4個數據庫表,路由演算法可以簡單地用 id % 4 的值來表示資料所屬的資料庫表編號,id 為 12的訂單放到編號為 50的子表中,id為 13的訂單放到編號為 61的字表中。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

Hash 路由設計的複雜點主要體現在初始表數量的選取上,表數量太多維護比較麻煩,表數量太少又可能導致單表效能存在問題。而用了 Hash 路由後,增加子表數量是非常麻煩的,所有資料都要重分佈。Hash 路由的優缺點和範圍路由基本相反,Hash 路由的優點是表分佈比較均勻,缺點是擴充新的表很麻煩,所有資料都要重分佈。

配置路由

:配置路由就是路由表,用一張獨立的表來記錄路由資訊。同樣以訂單id 為例,我們新增一張 order_router 表,這個表包含 orderjd 和 tablejd 兩列 , 根據 orderjd 就可以查詢對應的 table_id。

配置路由設計簡單,使用起來非常靈活,尤其是在擴充表的時候,只需要遷移指定的資料,然後修改路由表就可以了。

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

配置路由的缺點就是必須多查詢一次,會影響整體效能;而且路由表本身如果太大(例如,幾億條資料) ,效能同樣可能成為瓶頸,如果我們再次將路由表分庫分表,則又面臨一個死迴圈式的路由演算法選擇問題。

61。不停機擴容怎麼實現?

實際上,不停機擴容,實操起來是個非常麻煩而且很有風險的操作,當然,面試回答起來就簡單很多。

第一階段:線上雙寫,查詢走老庫

建立好新的庫表結構,資料寫入久庫的同時,也寫入拆分的新庫

資料遷移,使用資料遷移程式,將舊庫中的歷史資料遷移到新庫

使用定時任務,新舊庫的資料對比,把差異補齊

第二階段:線上雙寫,查詢走新庫

完成了歷史資料的同步和校驗

把對資料的讀切換到新庫

第三階段:舊庫下線

舊庫不再寫入新的資料

經過一段時間,確定舊庫沒有請求之後,就可以下線老庫

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

62。常用的分庫分表中介軟體有哪些?

sharding-jdbc

Mycat

63。那你覺得分庫分表會帶來什麼問題呢?

從分庫的角度來講:

事務的問題

使用關係型資料庫,有很大一點在於它保證事務完整性。

而分庫之後單機事務就用不上了,必須使用分散式事務來解決。

跨庫 JOIN 問題

在一個庫中的時候我們還可以利用 JOIN 來連表查詢,而跨庫了之後就無法使用 JOIN 了。

此時的解決方案就是

在業務程式碼中進行關聯

,也就是先把一個表的資料查出來,然後透過得到的結果再去查另一張表,然後利用程式碼來關聯得到最終的結果。

這種方式實現起來稍微比較複雜,不過也是可以接受的。

還有可以

適當的冗餘一些欄位

。比如以前的表就儲存一個關聯 ID,但是業務時常要求返回對應的 Name 或者其他欄位。這時候就可以把這些欄位冗餘到當前表中,來去除需要關聯的操作。

還有一種方式就是

資料異構

,透過binlog同步等方式,把需要跨庫join的資料異構到ES等儲存結構中,透過ES進行查詢。

從分表的角度來看:

跨節點的 count,order by,group by 以及聚合函式問題

只能由業務程式碼來實現或者用中介軟體將各表中的資料彙總、排序、分頁然後返回。

資料遷移,容量規劃,擴容等問題

資料的遷移,容量如何規劃,未來是否可能再次需要擴容,等等,都是需要考慮的問題。

ID 問題

資料庫表被切分後,不能再依賴資料庫自身的主鍵生成機制,所以需要一些手段來保證全域性主鍵唯一。

還是自增,只不過自增步長設定一下。比如現在有三張表,步長設定為3,三張表 ID 初始值分別是1、2、3。 這樣第一張表的 ID 增長是 1、4、7。第二張表是2、5、8。第三張表是3、6、9,這樣就不會重複了。

UUID,這種最簡單,但是不連續的主鍵插入會導致嚴重的頁分裂,效能比較差。

分散式 ID,比較出名的就是 Twitter 開源的 sonwflake 雪花演算法

運維

64。百萬級別以上的資料如何刪除?

關於索引:由於索引需要額外的維護成本,因為索引檔案是單獨存在的檔案,所以當我們對資料的增加,修改,刪除,都會產生額外的對索引檔案的操作,這些操作需要消耗額外的IO,會降低增/改/刪的執行效率。

所以,在我們刪除資料庫百萬級別資料的時候,查詢MySQL官方手冊得知刪除資料的速度和建立的索引數量是成正比的。

所以我們想要刪除百萬資料的時候可以先刪除索引

然後刪除其中無用資料

刪除完成後重新建立索引建立索引也非常快

65。百萬千萬級大表如何新增欄位?

當線上的資料庫資料量到達幾百萬、上千萬的時候,加一個欄位就沒那麼簡單,因為可能會長時間鎖表。

大表新增欄位,通常有這些做法:

透過中間錶轉換過去

建立一個臨時的新表,把舊錶的結構完全複製過去,新增欄位,再把舊錶資料複製過去,刪除舊錶,新表命名為舊錶的名稱,這種方式可能回丟掉一些資料。

用pt-online-schema-change

pt-online-schema-change是percona公司開發的一個工具,它可以線上修改表結構,它的原理也是透過中間表。

先在從庫新增 再進行主從切換

如果一張表資料量大且是熱表(讀寫特別頻繁),則可以考慮先在從庫新增,再進行主從切換,切換後再將其他幾個節點上新增欄位。

66。MySQL 資料庫 cpu 飆升的話,要怎麼處理呢?

排查過程:

(1)使用 top 命令觀察,確定是 mysqld 導致還是其他原因。

(2)如果是 mysqld 導致的,show processlist,檢視 session 情況,確定是不是有消耗資源的 sql 在執行。

(3)找出消耗高的 sql,看看執行計劃是否準確, 索引是否缺失,資料量是否太大。

處理:

(1)kill 掉這些執行緒 (同時觀察 cpu 使用率是否下降),

(2)進行相應的調整 (比如說加索引、改 sql、改記憶體引數)

(3)重新跑這些 SQL。

其他情況:

也有可能是每個 sql 消耗資源並不多,但是突然之間,有大量的 session 連進來導致 cpu 飆升,這種情況就需要跟應用一起來分析為何連線數會激增,再做出相應的調整,比如說限制連線數等

小夥伴們有興趣想了解內容和更多相關學習資料的請點贊收藏+評論轉發+關注我,後面會有很多幹貨。

我有一些面試題、架構、設計類資料可以說是程式設計師面試必備!所有資料都整理到網盤了,需要的話歡迎下載!私信我回復【07】即可免費獲取

面渣逆襲:MySQL六十六問,兩萬字+五十圖詳解

連結:https://juejin。cn/post/7111555503301328909

相關文章

頂部