掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
最近后臺好多小伙伴發(fā)私信咨詢阿Q,問馬上就是金九銀十的面試黃金期了,該如何在三個月內(nèi)突擊一下,以便更好地通過面試呢?

阿Q的想法就是需要對自己掌握的知識進行歸納整理,系統(tǒng)的、分類的去復(fù)習(xí)相關(guān)的知識點。這樣也能更好的把自己陌生的內(nèi)容框出來,在暴漏自己短板的同時,更針對性的做準(zhǔn)備。
為了節(jié)省大家的時間,阿Q決定幫大家先將 MySQL 面試相關(guān)的知識進行匯總整理,一改往日知識點東一棒槌西一榔頭的問題。那么,你準(zhǔn)備好迎接 MySQL 連環(huán)炮了嘛?
1、三大范式
2、DML 語句和 DDL 語句區(qū)別
3、主鍵和外鍵的區(qū)別
4、drop、delete、truncate 區(qū)別
5、基礎(chǔ)架構(gòu)
6、MyISAM 和 InnoDB 有什么區(qū)別?
7、推薦自增id作為主鍵問題
8、為什么 MySQL 的自增主鍵不連續(xù)
9、redo log 是做什么的?
10、redo log 的刷盤時機
11、redo log 是怎么記錄日志的
12、什么是 binlog
13、binlog 記錄格式
14、binlog 寫入機制
15、redolog 和 binlog 的區(qū)別是什么
16、兩階段提交
17、什么是 undo log.
18、什么是 relaylog
19、索引
20、Hash 索引
21、B樹和B+ 樹
22、主鍵索引
23、二級索引
24、聚簇索引與非聚簇索引
25、回表
26、覆蓋索引和聯(lián)合索引
27、最左前綴匹配原則
28、索引下推
29、隱式轉(zhuǎn)換
30、普通索引和唯一索引該怎么選擇?
31、避免索引失效
32、建立索引的規(guī)則
33、事務(wù)極其特性
34、并發(fā)事務(wù)帶來的問題
35、事務(wù)的隔離級別
36、MVCC
37、Mysql 中的鎖
38、查詢語句執(zhí)行過程
39、更新語句執(zhí)行過程
40、sql 優(yōu)化
41、主從同步數(shù)據(jù)
42、主從延遲要怎么解決
43、為什么不要使用長事務(wù)
1NF(第一范式):屬性(對應(yīng)于表中的字段)不能再被分割,也就是這個字段只能是一個值,不能再分為多個其他的字段了。1NF 是所有關(guān)系型數(shù)據(jù)庫的最基本要求 ,也就是說關(guān)系型數(shù)據(jù)庫中創(chuàng)建的表一定滿足第一范式。
2NF(第二范式):2NF 要求數(shù)據(jù)庫表中的每個實例或行必須可以被惟一地區(qū)分,2NF 在 1NF 的基礎(chǔ)上增加了一個列,這個列稱為主鍵,非主屬性都依賴于主鍵。
3NF(第三范式):3NF 在 2NF 的基礎(chǔ)之上,要求每列都和主鍵列直接相關(guān),而不是間接相關(guān),即不存在其他表的非主鍵信息。
在開發(fā)過程中,并不一定要滿足三大范式,有時候為了提高查詢效率,可以在表中冗余其他表的字段。
一般來說:drop > truncate > delete
下圖是 MySQL 的一個簡要架構(gòu)圖,從下圖你可以很清晰的看到客戶端的一條 SQL 語句在 MySQL 內(nèi)部是如何執(zhí)行的。
MySQL 5.5 之前,MyISAM 引擎是 MySQL 的默認(rèn)存儲引擎,MySQL 5.5 版本之后,InnoDB 是 MySQL 的默認(rèn)存儲引擎。
MyISAM 只有表級鎖,而 InnoDB 支持行級鎖和表級鎖,默認(rèn)為行級鎖。
具體的鎖詳情請參考阿Q的此篇文章:面試必備常見存儲引擎與鎖的分類,請查收
MyISAM 不提供事務(wù)支持,InnoDB 提供事務(wù)支持,實現(xiàn)了 SQL 標(biāo)準(zhǔn)定義的四個隔離級別,具有提交和回滾事務(wù)的能力。
InnoDB 默認(rèn)使用的 REPEATABLE-READ(可重讀)隔離級別是可以解決幻讀問題發(fā)生的(基于 MVCC 和 Next-Key Lock)。
關(guān)于 MySQL 事務(wù)以及解決幻讀的詳細(xì)介紹,可以看看阿Q寫的這篇文章:InnoDB 解決幻讀的方案--LBCC&MVCC
MyISAM 不支持,而 InnoDB 支持。
MyISAM 不支持,而 InnoDB 支持。使用 InnoDB 的數(shù)據(jù)庫在異常崩潰后,數(shù)據(jù)庫重新啟動的時候會保證數(shù)據(jù)庫恢復(fù)到崩潰前的狀態(tài)。這個恢復(fù)的過程依賴于 redo log 。
MyISAM 不支持,而 InnoDB 支持。
雖然 MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作為索引結(jié)構(gòu),但是兩者的實現(xiàn)方式不太一樣。
InnoDB 的性能比 MyISAM 更強大,不管是在讀寫混合模式下還是只讀模式下,隨著 CPU 核數(shù)的增加,InnoDB 的讀寫能力呈線性增長。MyISAM 因為讀寫不能并發(fā),它的處理能力跟核數(shù)沒關(guān)系。
InnoDB 和 MyISAM 性能對比
redo log(重做日志)是InnoDB存儲引擎獨有的,它讓MySQL擁有了崩潰恢復(fù)能力。
比如 MySQL 實例掛了或宕機了,重啟時,InnoDB存儲引擎會使用redo log恢復(fù)數(shù)據(jù),保證數(shù)據(jù)的持久性與完整性。
更新表數(shù)據(jù)的時候,如果發(fā)現(xiàn) Buffer Pool 里存在要更新的數(shù)據(jù),就直接在 Buffer Pool 里更新。然后會把“在某個數(shù)據(jù)頁上做了什么修改”記錄到重做日志緩存(redo log buffer)里,接著刷盤到 redo log 文件里。
InnoDB 存儲引擎為 redo log 的刷盤策略提供了 innodb_flush_log_at_trx_commit 參數(shù),它支持三種策略
innodb_flush_log_at_trx_commit 參數(shù)默認(rèn)為 1 ,也就是說當(dāng)事務(wù)提交時會調(diào)用 fsync(同步操作) 對 redo log 進行刷盤。
另外 InnoDB 存儲引擎有一個后臺線程,每隔1秒,就會把 redo log buffer 中的內(nèi)容寫到文件系統(tǒng)緩存(page cache),然后調(diào)用 fsync 刷盤。
redo log buffer占用的空間即將達到 innodb_log_buffer_size 一半的時候,后臺線程會主動刷盤。
硬盤上存儲的 redo log 日志文件不只一個,而是以一個日志文件組的形式出現(xiàn)的,每個的redo日志文件大小都是一樣的。
比如可以配置為一組4個文件,每個文件的大小是 1GB,整個 redo log 日志文件組可以記錄4G的內(nèi)容。
它采用的是環(huán)形數(shù)組形式,從頭開始寫,寫到末尾又回到頭循環(huán)寫,如下圖所示。
所以,如果數(shù)據(jù)寫滿了但是還沒有來得及將數(shù)據(jù)真正的刷入磁盤當(dāng)中,那么就會發(fā)生「內(nèi)存抖動」現(xiàn)象,從肉眼的角度來觀察會發(fā)現(xiàn) mysql 會宕機一會兒,此時就是正在刷盤了。
binlog 是歸檔日志,屬于 Server 層的日志,是一個二進制格式的文件,記錄內(nèi)容是語句的原始邏輯,類似于“給 ID=2 這一行的 c 字段加 1”。
不管用什么存儲引擎,只要發(fā)生了表數(shù)據(jù)更新,都會產(chǎn)生 binlog 日志。它的主要作用就是數(shù)據(jù)備份、主從復(fù)制。
binlog會記錄所有涉及更新數(shù)據(jù)的邏輯操作,屬于邏輯日志,并且是順序?qū)憽?/p>
binlog 日志有三種格式,可以通過binlog_format參數(shù)指定。
事務(wù)執(zhí)行過程中,先把日志寫到binlog cache,事務(wù)提交的時候,再把binlog cache寫到binlog文件中。
因為一個事務(wù)的binlog不能被拆開,無論這個事務(wù)多大,也要確保一次性寫入,所以系統(tǒng)會給每個線程分配一個塊內(nèi)存作為binlog cache。
我們可以通過binlog_cache_size參數(shù)控制單個線程 binlog cache 大小,如果存儲內(nèi)容超過了這個參數(shù),就要暫存到磁盤(Swap)。
binlog 也提供了 sync_binlog 參數(shù)來控制寫入 page cache 和磁盤的時機:
假設(shè)執(zhí)行 sql 過程中寫完 redo log 日志后,binlog 日志寫期間發(fā)生了異常,會出現(xiàn)什么情況呢?
由于 binlog 沒寫完就異常,這時候 binlog 里面沒有對應(yīng)的修改記錄。因此,之后用 binlog 日志恢復(fù)數(shù)據(jù)時,就會少這一次更新,最終數(shù)據(jù)不一致。
為了解決兩份日志之間的邏輯一致問題,InnoDB 存儲引擎使用兩階段提交方案。
將 redo log 的寫入拆成了兩個步驟 prepare 和 commit,這就是兩階段提交。使用兩階段提交后,寫入 binlog 時發(fā)生異常也不會有影響,因為 MySQL 根據(jù) redo log日志恢復(fù)數(shù)據(jù)時,發(fā)現(xiàn) redo log 還處于 prepare 階段,并且沒有對應(yīng) binlog 日志,就會回滾該事務(wù)。
再看一個場景,redo log 設(shè)置 commit 階段發(fā)生異常,那會不會回滾事務(wù)呢?
并不會回滾事務(wù),雖然 redo log 是處于 prepare 階段,但是能通過事務(wù)id找到對應(yīng)的 binlog 日志,所以 MySQL 認(rèn)為是完整的,就會提交事務(wù)恢復(fù)數(shù)據(jù)。
我們知道如果想要保證事務(wù)的原子性,就需要在異常發(fā)生時,對已經(jīng)執(zhí)行的操作(INSERT、DELETE、UPDATE)進行回滾,在 MySQL 中,恢復(fù)機制是通過回滾日志(undo log) 實現(xiàn)的,所有事務(wù)進行的修改都會先記錄到這個回滾日志中,然后再執(zhí)行相關(guān)的操作。
每次對記錄進行改動都會記錄一條 undo log,每條 undo log 也都有一個DB_ROLL_PTR屬性,可以將這些 undo log 都連起來,串成一個鏈表,形成版本鏈。
版本鏈的頭節(jié)點就是當(dāng)前記錄最新的值。
relaylog 是中繼日志,在主從同步的時候使用到,它是一個中介臨時的日志文件,用于存儲從 master 節(jié)點同步過來的 binlog 日志內(nèi)容。
master 主節(jié)點的 binlog 傳到 slave 從節(jié)點后,被寫入 relay log 里,從節(jié)點的 slave sql 線程從 relaylog 里讀取日志然后應(yīng)用到 slave 從節(jié)點本地。
從服務(wù)器 I/O 線程將主服務(wù)器的二進制日志讀取過來記錄到從服務(wù)器本地文件,然后 SQL 線程會讀取 relay-log 日志的內(nèi)容并應(yīng)用到從服務(wù)器,從而使從服務(wù)器和主服務(wù)器的數(shù)據(jù)保持一致。
索引其實是一種數(shù)據(jù)結(jié)構(gòu),能夠幫助我們快速的檢索數(shù)據(jù)庫中的數(shù)據(jù)。
索引的作用就相當(dāng)于書的目錄。打個比方: 我們在查字典的時候,如果沒有目錄,那我們就只能一頁一頁的去找我們需要查的那個字,速度很慢。如果有目錄了,我們只需要先去目錄里查找字的位置,然后直接翻到那一頁就行了。
哈希表是鍵值對的集合,通過鍵(key)即可快速取出對應(yīng)的值(value),因此哈希表可以快速檢索數(shù)據(jù)(接近 O(1))。
但是!哈希算法有個 Hash 沖突問題,也就是說多個不同的 key 最后得到的 index 相同。通常情況下,我們常用的解決辦法是 鏈地址法。
鏈地址法就是將哈希沖突數(shù)據(jù)存放在鏈表中。就比如 JDK1.8 之前 HashMap 就是通過鏈地址法來解決哈希沖突的。不過,JDK1.8 以后 HashMap 為了減少鏈表過長的時候搜索時間過長引入了紅黑樹。
為了減少 Hash 沖突的發(fā)生,一個好的哈希函數(shù)應(yīng)該“均勻地”將數(shù)據(jù)分布在整個可能的哈希值集合中。
既然哈希表這么快,為什么 MySQL 沒有使用其作為索引的數(shù)據(jù)結(jié)構(gòu)呢? 主要是因為 Hash 索引不支持順序和范圍查詢。假如我們要對表中的數(shù)據(jù)進行排序或者進行范圍查詢,那 Hash 索引可就不行了,并且每次 IO 只能取一個。
數(shù)據(jù)表的主鍵列使用的就是主鍵索引,一種特殊的唯一索引。
在 MySQL 的 InnoDB 的表中,當(dāng)沒有顯示的指定表的主鍵時,InnoDB 會自動先檢查表中是否有唯一索引且不允許存在 null 值的字段,如果有,則選擇該字段為默認(rèn)的主鍵,否則 InnoDB 將會自動創(chuàng)建一個 6Byte 的自增主鍵。
二級索引又稱為輔助索引,是因為二級索引的葉子節(jié)點存儲的數(shù)據(jù)是主鍵。也就是說,通過二級索引,可以定位主鍵的位置。
唯一索引,普通索引,前綴索引等索引屬于二級索引。
MySQL 中的全文索引,有兩個變量,最小搜索長度和最大搜索長度,對于長度小于最小搜索長度和大于最大搜索長度的詞語,都不會被索引。
聚簇索引即索引結(jié)構(gòu)和數(shù)據(jù)一起存放的索引,并不是一種單獨的索引類型。InnoDB 的主鍵索引的葉子節(jié)點中存放的就是數(shù)據(jù)行,所以它屬于聚簇索引。
在 MySQL 中,InnoDB 引擎的表的 .ibd 文件就包含了該表的索引和數(shù)據(jù),對于 InnoDB 引擎表來說,該表的索引(B+樹)的每個非葉子節(jié)點存儲索引,葉子節(jié)點存儲索引和索引對應(yīng)的數(shù)據(jù)。
非聚簇索引即索引結(jié)構(gòu)和數(shù)據(jù)分開存放的索引,并不是一種單獨的索引類型。二級索引(輔助索引)就屬于非聚簇索引。MySQL 的 MyISAM 引擎,不管主鍵還是非主鍵,使用的都是非聚簇索引。
輔助索引是我們?nèi)藶閯?chuàng)建的索引,它的葉子節(jié)點中存放的是主鍵,當(dāng)我們通過輔助索引查找到主鍵之后,再通過查找的主鍵去回表查找主鍵索引。
回表就是先通過數(shù)據(jù)庫索引掃描出該索引樹中數(shù)據(jù)所在的行,取到主鍵 id,再通過主鍵 id 取出主鍵索引數(shù)中的數(shù)據(jù),即基于非主鍵索引的查詢需要多掃描一棵索引樹。
如果一個索引包含(或者說覆蓋)所有需要查詢的字段的值,我們就稱之為“覆蓋索引”。指的是通過索引就能查詢到我們所需要的數(shù)據(jù),而不需要根據(jù)索引再去查詢數(shù)據(jù)表中的數(shù)據(jù)( 回表),這樣就減少了數(shù)據(jù)庫的 io 操作,提高查詢效率。
使用表中的多個字段創(chuàng)建索引,就是聯(lián)合索引,也叫組合索引或復(fù)合索引。
最左前綴匹配原則指的是在使用聯(lián)合索引時,MySQL 會根據(jù)聯(lián)合索引中的字段順序,從左到右依次到查詢條件中去匹配,如果查詢條件中存在與聯(lián)合索引中最左側(cè)字段相匹配的字段,則就會使用該字段過濾一批數(shù)據(jù),直至聯(lián)合索引中全部字段匹配完成,或者在執(zhí)行過程中遇到范圍查詢,如 >、<、between 和 以%開頭的like查詢 等條件,才會停止匹配。
所以,我們在使用聯(lián)合索引時,可以將區(qū)分度高的字段放在最左邊,這也可以過濾更多數(shù)據(jù)。
索引下推(Index Condition Pushdown) 是 MySQL 5.6 版本中提供的一項索引優(yōu)化功能,可以在非聚簇索引遍歷過程中,對索引中包含的字段先做判斷,過濾掉不符合條件的記錄,減少回表次數(shù)。
當(dāng)操作符與不同類型的操作數(shù)一起使用時,會發(fā)生類型轉(zhuǎn)換以使操作數(shù)兼容。某些轉(zhuǎn)換是隱式發(fā)生的。例如,MySQL 會根據(jù)需要自動將字符串轉(zhuǎn)換為數(shù)字,反之亦然。以下規(guī)則描述了比較操作的轉(zhuǎn)換方式:
當(dāng)普通索引為條件時查詢到數(shù)據(jù)會一直掃描,直到掃完整張表;
當(dāng)唯一索引為查詢條件時,查到該數(shù)據(jù)會直接返回,不會繼續(xù)掃表;
普通索引會直接將操作更新到 change buffer 中,然后結(jié)束
唯一索引需要判斷數(shù)據(jù)是否沖突
所以唯一索引更加適合查詢的場景,普通索引更適合插入的場景。
索引失效也是慢查詢的主要原因之一,常見的導(dǎo)致索引失效的情況有下面這些:
一個事情由n個單元組成,這n個單元在執(zhí)行過程中,要么同時成功,要么同時失敗,這就把n個單元放在了一個事務(wù)之中。舉個簡單的例子:在不考慮試題正確與否的前提下,一張試卷由多個題目構(gòu)成,當(dāng)你答完題交給老師的時候是將一整張試卷交給老師,而不是將每道題單獨交給老師,在這里試卷就可以理解成一個事務(wù)。
事務(wù)的特性:
為了解決以上隔離性引發(fā)的并發(fā)問題,數(shù)據(jù)庫提供了事務(wù)的隔離機制。
雖然 serializable 級別可以解決所有的數(shù)據(jù)庫并發(fā)問題,但是它會在讀取的每一行數(shù)據(jù)上都加鎖,這就可能導(dǎo)致大量的超時和鎖競爭問題,從而導(dǎo)致效率下降。所以我們在實際應(yīng)用中也很少使用 serializable,只有在非常需要確保數(shù)據(jù)的一致性而且可以接受沒有并發(fā)的情況下,才考慮采用該級別。
鎖的粒度過大會導(dǎo)致性能的下降, MySQL 的 InnoDB 引擎下存在一種性能更優(yōu)越的 MVCC 方法。
MVCC 是 Multi-Version Concurremt Control 的簡稱,意思是基于多版本的并發(fā)控制協(xié)議,通過版本號避免同一數(shù)據(jù)在不同事務(wù)間的競爭。它主要是為了提高數(shù)據(jù)庫的并發(fā)讀寫性能,不用加鎖就能讓多個事務(wù)并發(fā)讀寫。
MVCC 的實現(xiàn)依賴于隱藏列、Undo log、 Read View 。
從上面對 SQL 標(biāo)準(zhǔn)定義了四個隔離級別的介紹可以看出,標(biāo)準(zhǔn)的 SQL 隔離級別定義里,REPEATABLE-READ(可重復(fù)讀)是不可以防止幻讀的。
但是 InnoDB 實現(xiàn)的 REPEATABLE-READ 隔離級別其實是可以解決幻讀問題發(fā)生的,主要有下面兩種情況:
InnoDB 存儲引擎在分布式事務(wù)的情況下一般會用到 SERIALIZABLE 隔離級別。
關(guān)于 MySQL 事務(wù)以及解決幻讀的詳細(xì)介紹,可以看看阿Q寫的這篇文章:InnoDB 解決幻讀的方案--LBCC&MVCC
具體的鎖詳情請參考阿Q的此篇文章:面試必備常見存儲引擎與鎖的分類,請查收
select * from tb_student s where s.age='18' and s.name=' 張三 ';查詢語句的執(zhí)行流程如下:權(quán)限校驗(如果命中緩存)--->查詢緩存--->分析器--->優(yōu)化器--->權(quán)限校驗--->執(zhí)行器--->引擎
update tb_student A set A.age='19' where A.name=' 張三 ';這條語句基本上也會沿著上一個查詢的流程走,只不過執(zhí)行更新的時候要記錄日志,這就會引入日志模塊了,MySQL 自帶的日志模塊是 binlog(歸檔日志) ,所有的存儲引擎都可以使用,我們常用的 InnoDB 引擎還自帶了一個日志模塊 redo log(重做日志),我們就以 InnoDB 模式下來探討這個語句的執(zhí)行流程。
更新語句執(zhí)行流程如下:分析器---->權(quán)限校驗---->執(zhí)行器--->引擎---redo log(prepare 狀態(tài))---> binlog --->redo log(commit狀態(tài))
對字段進行 null 值判斷,
使用!=或<>
or 來連接條件(使用union all代替)
in 和 not in 也要慎用
不要使用模糊查詢(可用全文索引)
減少表達式操作
函數(shù)操作
同步策略:
好看的皮囊千篇一律,有趣的靈魂萬里挑一,讓我們在冷漠的城市里相互溫暖,我是阿Q,我們下期再見!

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流