1、關(guān)系型和非關(guān)系型數(shù)據(jù)庫的區(qū)別?
關(guān)系型數(shù)據(jù)庫的優(yōu)點
容易理解,因為它采用了關(guān)系模型來組織數(shù)據(jù)。
可以保持?jǐn)?shù)據(jù)的一致性。
數(shù)據(jù)更新的開銷比較小。
支持復(fù)雜查詢(帶 where 子句的查詢)
非關(guān)系型數(shù)據(jù)庫(NOSQL)的優(yōu)點
無需經(jīng)過 SQL 層的解析,讀寫效率高。
基于鍵值對,讀寫性能很高,易于擴(kuò)展
可以支持多種類型數(shù)據(jù)的存儲,如圖片,文檔等等。
擴(kuò)展(可分為內(nèi)存性數(shù)據(jù)庫以及文檔型數(shù)據(jù)庫,比如 Redis,MongoDB,HBase 等,適合場景:數(shù)據(jù)量大高可用的日志系統(tǒng)/地理位置存儲系統(tǒng))。
2、詳細(xì)說一下一條 MySQL 語句執(zhí)行的步驟
Server 層按順序執(zhí)行 SQL 的步驟為:
客戶端請求 -> 連接器(驗證用戶身份,給予權(quán)限)
查詢緩存(存在緩存則直接返回,不存在則執(zhí)行后續(xù)操作)
分析器(對 SQL 進(jìn)行詞法分析和語法分析操作)
優(yōu)化器(主要對執(zhí)行的 SQL 優(yōu)化選擇最優(yōu)的執(zhí)行方案方法)
執(zhí)行器(執(zhí)行時會先看用戶是否有執(zhí)行權(quán)限,有才去使用這個引擎提供的接口)-> 去引擎層獲取數(shù)據(jù)返回(如果開啟查詢緩存則會緩存查詢結(jié)果)
索引相關(guān)
3、MySQL 使用索引的原因?
根本原因
索引的出現(xiàn),就是為了提高數(shù)據(jù)查詢的效率,就像書的目錄一樣。
對于數(shù)據(jù)庫的表而言,索引其實就是它的“目錄”。
擴(kuò)展
創(chuàng)建唯一性索引,可以保證數(shù)據(jù)庫表中每一行數(shù)據(jù)的唯一性。
幫助引擎層避免排序和臨時表
將隨機(jī) IO 變?yōu)轫樞?IO,加速表和表之間的連接。
4、索引的三種常見底層數(shù)據(jù)結(jié)構(gòu)以及優(yōu)缺點
三種常見的索引底層數(shù)據(jù)結(jié)構(gòu):分別是哈希表、有序數(shù)組和搜索樹。
哈希表這種適用于等值查詢的場景,比如 memcached 以及其它一些 NoSQL 引擎,不適合范圍查詢。
有序數(shù)組索引只適用于靜態(tài)存儲引擎,等值和范圍查詢性能好,但更新數(shù)據(jù)成本高。
N 叉樹由于讀寫上的性能優(yōu)點以及適配磁盤訪問模式以及廣泛應(yīng)用在數(shù)據(jù)庫引擎中。
擴(kuò)展(以 InnoDB 的一個整數(shù)字段索引為例,這個 N 差不多是 1200??脴涓呤?4 的時候,就可以存 1200 的 3 次方個值,這已經(jīng) 17 億了。考慮到樹根的數(shù)據(jù)塊總是在內(nèi)存中的,一個 10 億行的表上一個整數(shù)字段的索引,查找一個值最多只需要訪問 3 次磁盤。其實,樹的第二層也有很大概率在內(nèi)存中,那么訪問磁盤的平均次數(shù)就更少了。)
5、索引的常見類型以及它是如何發(fā)揮作用的?
根據(jù)葉子節(jié)點的內(nèi)容,索引類型分為主鍵索引和非主鍵索引。
主鍵索引的葉子節(jié)點存的整行數(shù)據(jù),在InnoDB里也被稱為聚簇索引。
非主鍵索引葉子節(jié)點存的主鍵的值,在InnoDB里也被稱為二級索引。
6、MyISAM 和 InnoDB 實現(xiàn) B 樹索引方式的區(qū)別是什么?
InnoDB 存儲引擎:B+ 樹索引的葉子節(jié)點保存數(shù)據(jù)本身,其數(shù)據(jù)文件本身就是索引文件。
MyISAM 存儲引擎:B+ 樹索引的葉子節(jié)點保存數(shù)據(jù)的物理地址,葉節(jié)點的 data 域存放的是數(shù)據(jù)記錄的地址,索引文件和數(shù)據(jù)文件是分離的。
7、InnoDB 為什么設(shè)計 B+ 樹索引?
兩個考慮因素:
InnoDB 需要執(zhí)行的場景和功能需要在特定查詢上擁有較強(qiáng)的性能。
CPU 將磁盤上的數(shù)據(jù)加載到內(nèi)存中需要花費大量時間。
為什么選擇 B+ 樹:
哈希索引雖然能提供O(1)復(fù)雜度查詢,但對范圍查詢和排序卻無法很好的支持,最終會導(dǎo)致全表掃描。
B 樹能夠在非葉子節(jié)點存儲數(shù)據(jù),但會導(dǎo)致在查詢連續(xù)數(shù)據(jù)可能帶來更多的隨機(jī) IO。
而 B+ 樹的所有葉節(jié)點可以通過指針來相互連接,減少順序遍歷帶來的隨機(jī) IO。
普通索引還是唯一索引?
由于唯一索引用不上 change buffer 的優(yōu)化機(jī)制,因此如果業(yè)務(wù)可以接受,從性能角度出發(fā)建議你優(yōu)先考慮非唯一索引。
8、什么是覆蓋索引和索引下推?
覆蓋索引:
在某個查詢里面,索引 k 已經(jīng)“覆蓋了”我們的查詢需求,稱為覆蓋索引。
覆蓋索引可以減少樹的搜索次數(shù),顯著提升查詢性能,所以使用覆蓋索引是一個常用的性能優(yōu)化手段。
索引下推:
MySQL 5.6 引入的索引下推優(yōu)化(index condition pushdown), 可以在索引遍歷過程中,對索引中包含的字段先做判斷,直接過濾掉不滿足條件的記錄,減少回表次數(shù)。
9、哪些操作會導(dǎo)致索引失效?
對索引使用左或者左右模糊匹配,也就是 like %xx 或者 like %xx% 這兩種方式都會造成索引失效。原因在于查詢的結(jié)果可能是多個,不知道從哪個索引值開始比較,于是就只能通過全表掃描的方式來查詢。
對索引進(jìn)行函數(shù)/對索引進(jìn)行表達(dá)式計算,因為索引保持的是索引字段的原始值,而不是經(jīng)過函數(shù)計算的值,自然就沒辦法走索引。
對索引進(jìn)行隱式轉(zhuǎn)換相當(dāng)于使用了新函數(shù)。
WHERE 子句中的 OR語句,只要有條件列不是索引列,就會進(jìn)行全表掃描。
10、字符串加索引
直接創(chuàng)建完整索引,這樣可能會比較占用空間。
創(chuàng)建前綴索引,節(jié)省空間,但會增加查詢掃描次數(shù),并且不能使用覆蓋索引。
倒序存儲,再創(chuàng)建前綴索引,用于繞過字符串本身前綴的區(qū)分度不夠的問題。
創(chuàng)建 hash 字段索引,查詢性能穩(wěn)定,有額外的存儲和計算消耗,跟第三種方式一樣,都不支持范圍掃描。
11、MySQL 的 change buffer 是什么?
當(dāng)需要更新一個數(shù)據(jù)頁時,如果數(shù)據(jù)頁在內(nèi)存中就直接更新;而如果這個數(shù)據(jù)頁還沒有在內(nèi)存中的話,在不影響數(shù)據(jù)一致性的前提下,InnoDB 會將這些更新操作緩存在 change buffer 中。
這樣就不需要從磁盤中讀入這個數(shù)據(jù)頁了,在下次查詢需要訪問這個數(shù)據(jù)頁的時候,將數(shù)據(jù)頁讀入內(nèi)存,然后執(zhí)行 change buffer 中與這個頁有關(guān)的操作。通過這種方式就能保證這個數(shù)據(jù)邏輯的正確性。
注意唯一索引的更新就不能使用 change buffer,實際上也只有普通索引可以使用。
適用場景:
- 對于寫多讀少的業(yè)務(wù)來說,頁面在寫完以后馬上被訪問到的概率比較小,此時 change buffer 的使用效果最好。這種業(yè)務(wù)模型常見的就是賬單類、日志類的系統(tǒng)。
- 反過來,假設(shè)一個業(yè)務(wù)的更新模式是寫入之后馬上會做查詢,那么即使?jié)M足了條件,將更新先記錄在 change buffer,但之后由于馬上要訪問這個數(shù)據(jù)頁,會立即觸發(fā) merge 過程。這樣隨機(jī)訪問 IO 的次數(shù)不會減少,反而增加了 change buffer 的維護(hù)代價。
12、MySQL 是如何判斷一行掃描數(shù)的?
MySQL 在真正開始執(zhí)行語句之前,并不能精確地知道滿足這個條件的記錄有多少條。
而只能根據(jù)統(tǒng)計信息來估算記錄數(shù)。這個統(tǒng)計信息就是索引的“區(qū)分度。
13、MySQL 的 redo log 和 binlog 區(qū)別?

14、為什么需要 redo log?
redo log 主要用于 MySQL 異常重啟后的一種數(shù)據(jù)恢復(fù)手段,確保了數(shù)據(jù)的一致性。
其實是為了配合 MySQL 的 WAL 機(jī)制。因為 MySQL 進(jìn)行更新操作,為了能夠快速響應(yīng),所以采用了異步寫回磁盤的技術(shù),寫入內(nèi)存后就返回。但是這樣,會存在 crash后 內(nèi)存數(shù)據(jù)丟失的隱患,而 redo log 具備 crash safe 的能力。
15、為什么 redo log 具有 crash-safe 的能力,是 binlog 無法替代的?
第一點:redo log 可確保 innoDB 判斷哪些數(shù)據(jù)已經(jīng)刷盤,哪些數(shù)據(jù)還沒有
redo log 和 binlog 有一個很大的區(qū)別就是,一個是循環(huán)寫,一個是追加寫。也就是說 redo log 只會記錄未刷盤的日志,已經(jīng)刷入磁盤的數(shù)據(jù)都會從 redo log 這個有限大小的日志文件里刪除。binlog 是追加日志,保存的是全量的日志。
當(dāng)數(shù)據(jù)庫 crash 后,想要恢復(fù)未刷盤但已經(jīng)寫入 redo log 和 binlog 的數(shù)據(jù)到內(nèi)存時,binlog 是無法恢復(fù)的。雖然 binlog 擁有全量的日志,但沒有一個標(biāo)志讓 innoDB 判斷哪些數(shù)據(jù)已經(jīng)刷盤,哪些數(shù)據(jù)還沒有。
但 redo log 不一樣,只要刷入磁盤的數(shù)據(jù),都會從 redo log 中抹掉,因為是循環(huán)寫!數(shù)據(jù)庫重啟后,直接把 redo log 中的數(shù)據(jù)都恢復(fù)至內(nèi)存就可以了。
第二點:如果 redo log 寫入失敗,說明此次操作失敗,事務(wù)也不可能提交
redo log 每次更新操作完成后,就一定會寫入日志,如果寫入失敗,說明此次操作失敗,事務(wù)也不可能提交。
redo log 內(nèi)部結(jié)構(gòu)是基于頁的,記錄了這個頁的字段值變化,只要crash后讀取redo log進(jìn)行重放,就可以恢復(fù)數(shù)據(jù)。
這就是為什么 redo log 具有 crash-safe 的能力,而 binlog 不具備。
16、當(dāng)數(shù)據(jù)庫 crash 后,如何恢復(fù)未刷盤的數(shù)據(jù)到內(nèi)存中?
根據(jù) redo log 和 binlog 的兩階段提交,未持久化的數(shù)據(jù)分為幾種情況:
change buffer 寫入,redo log 雖然做了 fsync 但未 commit,binlog 未 fsync 到磁盤,這部分?jǐn)?shù)據(jù)丟失。
change buffer 寫入,redo log fsync 未 commit,binlog 已經(jīng) fsync 到磁盤,先從 binlog 恢復(fù) redo log,再從 redo log 恢復(fù) change buffer。
change buffer 寫入,redo log 和 binlog 都已經(jīng) fsync,直接從 redo log 里恢復(fù)。
17、redo log 寫入方式?
redo log包括兩部分內(nèi)容,分別是內(nèi)存中的日志緩沖(redo log buffer)和磁盤上的日志文件(redo log file)。
MySQL 每執(zhí)行一條 DML 語句,會先把記錄寫入 redo log buffer(用戶空間) ,再保存到內(nèi)核空間的緩沖區(qū) OS-buffer 中,后續(xù)某個時間點再一次性將多個操作記錄寫到 redo log file(刷盤) 。這種先寫日志,再寫磁盤的技術(shù),就是WAL。

可以發(fā)現(xiàn),redo log buffer寫入到redo log file,是經(jīng)過OS buffer中轉(zhuǎn)的。其實可以通過參數(shù)
innodb_flush_log_at_trx_commit進(jìn)行配置,參數(shù)值含義如下:
0:稱為延遲寫,事務(wù)提交時不會將redo log buffer中日志寫入到OS buffer,而是每秒寫入OS buffer并調(diào)用寫入到redo log file中。
1:稱為實時寫,實時刷”,事務(wù)每次提交都會將redo log buffer中的日志寫入OS buffer并保存到redo log file中。
2:稱為實時寫,延遲刷。每次事務(wù)提交寫入到OS buffer,然后是每秒將日志寫入到redo log file。
18、redo log 的執(zhí)行流程?
我們來看下Redo log的執(zhí)行流程,假設(shè)執(zhí)行的 SQL 如下:
update T set a =1 where id =666

MySQL 客戶端將請求語句 update T set a =1 where id =666,發(fā)往 MySQL Server 層。
MySQL Server 層接收到 SQL 請求后,對其進(jìn)行分析、優(yōu)化、執(zhí)行等處理工作,將生成的 SQL 執(zhí)行計劃發(fā)到 InnoDB 存儲引擎層執(zhí)行。
InnoDB 存儲引擎層將a修改為1的這個操作記錄到內(nèi)存中。
記錄到內(nèi)存以后會修改 redo log 的記錄,會在添加一行記錄,其內(nèi)容是需要在哪個數(shù)據(jù)頁上做什么修改。
此后,將事務(wù)的狀態(tài)設(shè)置為 prepare ,說明已經(jīng)準(zhǔn)備好提交事務(wù)了。
等到 MySQL Server 層處理完事務(wù)以后,會將事務(wù)的狀態(tài)設(shè)置為 commit,也就是提交該事務(wù)。
在收到事務(wù)提交的請求以后,redo log 會把剛才寫入內(nèi)存中的操作記錄寫入到磁盤中,從而完成整個日志的記錄過程。
19、binlog 的概念是什么,起到什么作用, 可以保證 crash-safe 嗎?
binlog 是歸檔日志,屬于 MySQL Server 層的日志??梢詫崿F(xiàn)主從復(fù)制和數(shù)據(jù)恢復(fù)兩個作用。
當(dāng)需要恢復(fù)數(shù)據(jù)時,可以取出某個時間范圍內(nèi)的 binlog 進(jìn)行重放恢復(fù)。
但是 binlog 不可以做 crash safe,因為 crash 之前,binlog 可能沒有寫入完全 MySQL 就掛了。所以需要配合 redo log 才可以進(jìn)行 crash safe。
20、什么是兩階段提交?
MySQL 將 redo log 的寫入拆成了兩個步驟:prepare 和 commit,中間再穿插寫入binlog,這就是"兩階段提交"。
而兩階段提交就是讓這兩個狀態(tài)保持邏輯上的一致。redolog 用于恢復(fù)主機(jī)故障時的未更新的物理數(shù)據(jù),binlog 用于備份操作。兩者本身就是兩個獨立的個體,要想保持一致,就必須使用分布式事務(wù)的解決方案來處理。
為什么需要兩階段提交呢?

如果不用兩階段提交的話,可能會出現(xiàn)這樣情況
先寫 redo log,crash 后 bin log 備份恢復(fù)時少了一次更新,與當(dāng)前數(shù)據(jù)不一致。
先寫 bin log,crash 后,由于 redo log 沒寫入,事務(wù)無效,所以后續(xù) bin log 備份恢復(fù)時,數(shù)據(jù)不一致。
兩階段提交就是為了保證 redo log 和 binlog 數(shù)據(jù)的安全一致性。只有在這兩個日志文件邏輯上高度一致了才能放心的使用。
在恢復(fù)數(shù)據(jù)時,redolog 狀態(tài)為 commit 則說明 binlog 也成功,直接恢復(fù)數(shù)據(jù);如果 redolog 是 prepare,則需要查詢對應(yīng)的 binlog事務(wù)是否成功,決定是回滾還是執(zhí)行。
21、MySQL 怎么知道 binlog 是完整的?
一個事務(wù)的 binlog 是有完整格式的:
statement 格式的 binlog,最后會有 COMMIT;
row 格式的 binlog,最后會有一個 XID event。
22、什么是 WAL 技術(shù),有什么優(yōu)點?
WAL,中文全稱是 Write-Ahead Logging,它的關(guān)鍵點就是日志先寫內(nèi)存,再寫磁盤。MySQL 執(zhí)行更新操作后,在真正把數(shù)據(jù)寫入到磁盤前,先記錄日志。
好處是不用每一次操作都實時把數(shù)據(jù)寫盤,就算 crash 后也可以通過redo log 恢復(fù),所以能夠?qū)崿F(xiàn)快速響應(yīng) SQL 語句。
23、binlog 日志的三種格式
binlog 日志有三種格式
Statement:基于SQL語句的復(fù)制((statement-based replication,SBR))
Row:基于行的復(fù)制。(row-based replication,RBR)
Mixed:混合模式復(fù)制。(mixed-based replication,MBR)
Statement格式
每一條會修改數(shù)據(jù)的 SQL 都會記錄在 binlog 中
優(yōu)點:不需要記錄每一行的變化,減少了binlog日志量,節(jié)約了IO,提高性能。
缺點:由于記錄的只是執(zhí)行語句,為了這些語句能在備庫上正確運行,還必須記錄每條語句在執(zhí)行的時候的一些相關(guān)信息,以保證所有語句能在備庫得到和在主庫端執(zhí)行時候相同的結(jié)果。
Row格式
不記錄 SQL 語句上下文相關(guān)信息,僅保存哪條記錄被修改。
優(yōu)點:binlog 中可以不記錄執(zhí)行的 SQL 語句的上下文相關(guān)的信息,僅需要記錄那一條記錄被修改成什么了。所以rowlevel的日志內(nèi)容會非常清楚的記錄下每一行數(shù)據(jù)修改的細(xì)節(jié)。不會出現(xiàn)某些特定情況下的存儲過程、或 function、或trigger的調(diào)用和觸發(fā)無法被正確復(fù)制的問題。
缺點:可能會產(chǎn)生大量的日志內(nèi)容。
Mixed格式
實際上就是 Statement 與 Row 的結(jié)合。一般的語句修改使用 statment 格式保存 binlog,如一些函數(shù),statement 無法完成主從復(fù)制的操作,則采用 row 格式保存 binlog,MySQL 會根據(jù)執(zhí)行的每一條具體的 SQL 語句來區(qū)分對待記錄的日志形式。
24、redo log日志格式

redo log buffer (內(nèi)存中)是由首尾相連的四個文件組成的,它們分別是:ib_logfile_1、ib_logfile_2、ib_logfile_3、ib_logfile_4。
write pos 是當(dāng)前記錄的位置,一邊寫一邊后移,寫到第 3 號文件末尾后就回到 0 號文件開頭。
checkpoint 是當(dāng)前要擦除的位置,也是往后推移并且循環(huán)的,擦除記錄前要把記錄更新到數(shù)據(jù)文件。
write pos 和 checkpoint 之間的是“粉板”上還空著的部分,可以用來記錄新的操作。
如果 write pos 追上 checkpoint,表示“粉板”滿了,這時候不能再執(zhí)行新的更新,得停下來先擦掉一些記錄,把 checkpoint 推進(jìn)一下。
有了 redo log,當(dāng)數(shù)據(jù)庫發(fā)生宕機(jī)重啟后,可通過 redo log將未落盤的數(shù)據(jù)(check point之后的數(shù)據(jù))恢復(fù),保證已經(jīng)提交的事務(wù)記錄不會丟失,這種能力稱為crash-safe。
25、原本可以執(zhí)行得很快的 SQL 語句,執(zhí)行速度卻比預(yù)期的慢很多,原因是什么?如何解決?
原因:從大到小可分為四種情況
MySQL 數(shù)據(jù)庫本身被堵住了,比如:系統(tǒng)或網(wǎng)絡(luò)資源不夠。
SQL 語句被堵住了,比如:表鎖,行鎖等,導(dǎo)致存儲引擎不執(zhí)行對應(yīng)的 SQL 語句。
確實是索引使用不當(dāng),沒有走索引。
表中數(shù)據(jù)的特點導(dǎo)致的,走了索引,但回表次數(shù)龐大。
解決:
考慮采用 force index 強(qiáng)行選擇一個索引
考慮修改語句,引導(dǎo) MySQL 使用我們期望的索引。比如把“order by b limit 1” 改成 “order by b,a limit 1” ,語義的邏輯是相同的。
第三種方法是,在有些場景下,可以新建一個更合適的索引,來提供給優(yōu)化器做選擇,或刪掉誤用的索引。
如果確定是索引根本沒必要,可以考慮刪除索引。
26、InnoDB 數(shù)據(jù)頁結(jié)構(gòu)
一個數(shù)據(jù)頁大致劃分七個部分
File Header:表示頁的一些通用信息,占固定的38字節(jié)。
page Header:表示數(shù)據(jù)頁專有信息,占固定的56字節(jié)。
inimum+Supermum:兩個虛擬的偽記錄,分別表示頁中的最小記錄和最大記錄,占固定的26字節(jié)。
User Records:真正存儲我們插入的數(shù)據(jù),大小不固定。
Free Space:頁中尚未使用的部分,大小不固定。
Page Directory:頁中某些記錄的相對位置,也就是各個槽對應(yīng)的記錄在頁面中的地址偏移量。
File Trailer:用于檢驗頁是否完整,占固定大小 8 字節(jié)。
數(shù)據(jù)相關(guān)
27、MySQL 是如何保證數(shù)據(jù)不丟失的?
只要redolog 和 binlog 保證持久化磁盤就能確保MySQL異常重啟后回復(fù)數(shù)據(jù)
在恢復(fù)數(shù)據(jù)時,redolog 狀態(tài)為 commit 則說明 binlog 也成功,直接恢復(fù)數(shù)據(jù);如果 redolog 是 prepare,則需要查詢對應(yīng)的 binlog事務(wù)是否成功,決定是回滾還是執(zhí)行。
28、誤刪數(shù)據(jù)怎么辦?
DBA 的最核心的工作就是保證數(shù)據(jù)的完整性,先要做好預(yù)防,預(yù)防的話大概是通過這幾個點:
權(quán)限控制與分配(數(shù)據(jù)庫和服務(wù)器權(quán)限)
制作操作規(guī)范
定期給開發(fā)進(jìn)行培訓(xùn)
搭建延遲備庫
做好 SQL 審計,只要是對線上數(shù)據(jù)有更改操作的語句(DML和DDL)都需要進(jìn)行審核
做好備份。備份的話又分為兩個點 (1)如果數(shù)據(jù)量比較大,用物理備份 xtrabackup。定期對數(shù)據(jù)庫進(jìn)行全量備份,也可以做增量備份。(2)如果數(shù)據(jù)量較少,用 mysqldump 或者 mysqldumper。再利用 binlog 來恢復(fù)或者搭建主從的方式來恢復(fù)數(shù)據(jù)。定期備份binlog 文件也是很有必要的
如果發(fā)生了數(shù)據(jù)刪除的操作,又可以從以下幾個點來恢復(fù):
DML 誤操作語句造成數(shù)據(jù)不完整或者丟失??梢酝ㄟ^ flashback,美團(tuán)的 myflash,也是一個不錯的工具,本質(zhì)都差不多
都是先解析 binlog event,然后在進(jìn)行反轉(zhuǎn)。把 delete 反轉(zhuǎn)為insert,insert 反轉(zhuǎn)為 delete,update前后 image 對調(diào)。
所以必須設(shè)置binlog_format=row 和 binlog_row_image=full,切記恢復(fù)數(shù)據(jù)的時候,應(yīng)該先恢復(fù)到臨時的實例,然后在恢復(fù)回主庫上。
DDL語句誤操作(truncate和drop),由于DDL語句不管 binlog_format 是 row 還是 statement ,在 binlog 里都只記錄語句,不記錄 image 所以恢復(fù)起來相對要麻煩得多。
只能通過全量備份+應(yīng)用 binlog 的方式來恢復(fù)數(shù)據(jù)。一旦數(shù)據(jù)量比較大,那么恢復(fù)時間就特別長
rm 刪除:使用備份跨機(jī)房,或者最好是跨城市保存。
29、drop、truncate 和 delete 的區(qū)別
DELETE 語句執(zhí)行刪除的過程是每次從表中刪除一行,并且同時將該行的刪除操作作為事務(wù)記錄在日志中保存以便進(jìn)行進(jìn)行回滾操作。
TRUNCATE TABLE 則一次性地從表中刪除所有的數(shù)據(jù)并不把單獨的刪除操作記錄記入日志保存,刪除行是不能恢復(fù)的。并且在刪除的過程中不會激活與表有關(guān)的刪除觸發(fā)器。執(zhí)行速度快。
drop語句將表所占用的空間全釋放掉。
在速度上,一般來說,drop> truncate > delete。
如果想刪除部分?jǐn)?shù)據(jù)用 delete,注意帶上 where 子句,回滾段要足夠大;
如果想刪除表,當(dāng)然用 drop;如果想保留表而將所有數(shù)據(jù)刪除,如果和事務(wù)無關(guān),用 truncate 即可;
如果和事務(wù)有關(guān),或者想觸發(fā) trigger,還是用 delete;如果是整理表內(nèi)部的碎片,可以用 truncate 跟上 reuse stroage,再重新導(dǎo)入/插入數(shù)據(jù)。
30、在 MySQL 中有兩個 kill 命令
一個是 kill query + 線程 id,表示終止這個線程中正在執(zhí)行的語句
一個是 kill connection + 線程 id,這里 connection 可缺省,表示斷開這個線程的連接
kill 不掉的原因
kill命令被堵了,還沒到位
kill命令到位了,但是沒被立刻觸發(fā)
kill命令被觸發(fā)了,但執(zhí)行完也需要時間
31、如何理解 MySQL 的邊讀邊發(fā)
如果客戶端接受慢,會導(dǎo)致 MySQL 服務(wù)端由于結(jié)果發(fā)不出去,這個事務(wù)的執(zhí)行時間會很長。
服務(wù)端并不需要保存一個完整的結(jié)果集,取數(shù)據(jù)和發(fā)數(shù)據(jù)的流程都是通過一個 next_buffer 來操作的。
內(nèi)存的數(shù)據(jù)頁都是在 Buffer_Pool中操作的。
InnoDB 管理 Buffer_Pool 使用的是改進(jìn)的 LRU 算法,使用鏈表實現(xiàn),實現(xiàn)上,按照 5:3 的比例把整個 LRU 鏈表分成了 young 區(qū)域和 old 區(qū)域。
32、MySQL 的大表查詢?yōu)槭裁床粫瑑?nèi)存?
由于 MySQL 是邊讀變發(fā),因此對于數(shù)據(jù)量很大的查詢結(jié)果來說,不會再 server 端保存完整的結(jié)果集,所以,如果客戶端讀結(jié)果不及時,會堵住 MySQL 的查詢過程,但是不會把內(nèi)存打爆。
InnoDB 引擎內(nèi)部,由于有淘汰策略,InnoDB 管理 Buffer_Pool 使用的是改進(jìn)的 LRU 算法,使用鏈表實現(xiàn),實現(xiàn)上,按照 5:3 的比例把整個 LRU 鏈表分成了 young 區(qū)域和 old 區(qū)域。對冷數(shù)據(jù)的全掃描,影響也能做到可控制。
33、MySQL 臨時表的用法和特性
只對當(dāng)前session可見。
可以與普通表重名。
增刪改查用的是臨時表。
show tables 不顯示普通表。
在實際應(yīng)用中,臨時表一般用于處理比較復(fù)雜的計算邏輯。
由于臨時表是每個線程自己可見的,所以不需要考慮多個線程執(zhí)行同一個處理時臨時表的重名問題,在線程退出的時候,臨時表會自動刪除。
34、MySQL 存儲引擎介紹(InnoDB、MyISAM、MEMORY)
InnoDB 是事務(wù)型數(shù)據(jù)庫的首選引擎,支持事務(wù)安全表 (ACID),支持行鎖定和外鍵。MySQL5.5.5 之后,InnoDB 作為默認(rèn)存儲引擎
MyISAM 基于 ISAM 的存儲引擎,并對其進(jìn)行擴(kuò)展。它是在 Web、數(shù)據(jù)存儲和其他應(yīng)用環(huán)境下最常用的存儲引擎之一。MyISAM 擁有較高的插入、查詢速度,但不支持事務(wù)。在 MySQL5.5.5 之前的版本中,MyISAM 是默認(rèn)存儲引擎
MEMORY 存儲引擎將表中的數(shù)據(jù)存儲到內(nèi)存中,為查詢和引用其他表數(shù)據(jù)提供快速訪問。
35、都說 InnoDB 好,那還要不要使用 MEMORY 引擎?
內(nèi)存表就是使用 memory 引擎創(chuàng)建的表
為什么我不建議你在生產(chǎn)環(huán)境上使用內(nèi)存表。這里的原因主要包括兩個方面:鎖粒度問題;數(shù)據(jù)持久化問題。
由于重啟會丟數(shù)據(jù),如果一個備庫重啟,會導(dǎo)致主備同步線程停止;如果主庫跟這個備庫是雙 M 架構(gòu),還可能導(dǎo)致主庫的內(nèi)存表數(shù)據(jù)被刪掉。
36、如果數(shù)據(jù)庫誤操作, 如何執(zhí)行數(shù)據(jù)恢復(fù)?
數(shù)據(jù)庫在某個時候誤操作,就可以找到距離誤操作最近的時間節(jié)點的bin log,重放到臨時數(shù)據(jù)庫里,然后選擇誤刪的數(shù)據(jù)節(jié)點,恢復(fù)到線上數(shù)據(jù)庫。
主從備份相關(guān)
37、MySQL 是如何保證主備同步?
主備關(guān)系的建立:
一開始創(chuàng)建主備關(guān)系的時候,是由備庫指定的,比如基于位點的主備關(guān)系,備庫說“我要從binlog文件A的位置P”開始同步,主庫就從這個指定的位置開始往后發(fā)。
而主備關(guān)系搭建之后,是主庫決定要發(fā)給數(shù)據(jù)給備庫的,所以主庫有新的日志也會發(fā)給備庫。
MySQL 主備切換流程:
客戶端讀寫都是直接訪問A,而節(jié)點B是備庫,只要將A的更新都同步過來,到本地執(zhí)行就可以保證數(shù)據(jù)是相同的。
當(dāng)需要切換的時候就把節(jié)點換一下,A的節(jié)點B的備庫
一個事務(wù)完整的同步過程:
備庫B和主庫A建立來了長鏈接,主庫A內(nèi)部專門線程用于維護(hù)了這個長鏈接。
在備庫B上通過changemaster命令設(shè)置主庫A的IP端口用戶名密碼以及從哪個位置開始請求binlog包括文件名和日志偏移量
在備庫B上執(zhí)行start-slave命令備庫會啟動兩個線程:io_thread和sql_thread分別負(fù)責(zé)建立連接和讀取中轉(zhuǎn)日志進(jìn)行解析執(zhí)行
備庫讀取主庫傳過來的binlog文件備庫收到文件寫到本地成為中轉(zhuǎn)日志
后來由于多線程復(fù)制方案的引入,sql_thread演化成了多個線程。
38、什么是主備延遲
主庫和備庫在執(zhí)行同一個事務(wù)的時候出現(xiàn)時間差的問題,主要原因有:
有些部署條件下,備庫所在機(jī)器的性能要比主庫性能差。
備庫的壓力較大。
大事務(wù),一個主庫上語句執(zhí)行10分鐘,那么這個事務(wù)可能會導(dǎo)致從庫延遲10分鐘。
39、為什么要有多線程復(fù)制策略?
因為單線程復(fù)制的能力全面低于多線程復(fù)制,對于更新壓力較大的主庫,備庫可能是一直追不上主庫的,帶來的現(xiàn)象就是備庫上seconds_behind_master值越來越大。
在實際應(yīng)用中,建議使用可靠性優(yōu)先策略,減少主備延遲,提升系統(tǒng)可用性,盡量減少大事務(wù)操作,把大事務(wù)拆分小事務(wù)。
40、MySQL 的并行策略有哪些?
按表分發(fā)策略:如果兩個事務(wù)更新不同的表,它們就可以并行。因為數(shù)據(jù)是存儲在表里的,所以按表分發(fā),可以保證兩個 worker 不會更新同一行。缺點:如果碰到熱點表,比如所有的更新事務(wù)都會涉及到某一個表的時候,所有事務(wù)都會被分配到同一個 worker 中,就變成單線程復(fù)制了。
按行分發(fā)策略:如果兩個事務(wù)沒有更新相同的行,它們在備庫上可以并行。如果兩個事務(wù)沒有更新相同的行,它們在備庫上可以并行執(zhí)行。顯然,這個模式要求 binlog 格式必須是 row。缺點:相比于按表并行分發(fā)策略,按行并行策略在決定線程分發(fā)的時候,需要消耗更多的計算資源。
41、MySQL的一主一備和一主多從有什么區(qū)別?
在一主一備的雙 M 架構(gòu)里,主備切換只需要把客戶端流量切到備庫;而在一主多從架構(gòu)里,主備切換除了要把客戶端流量切到備庫外,還需要把從庫接到新主庫上。
42、主庫出問題如何解決?
基于位點的主備切換:存在找同步位點這個問題
MySQL 5.6 版本引入了 GTID,徹底解決了這個困難。那么,GTID 到底是什么意思,又是如何解決找同步位點這個問題呢?
GTID:全局事務(wù) ID,是一個事務(wù)在提交的時候生成的,是這個事務(wù)的唯一標(biāo)識;它由兩部分組成,格式是:GTID=server_uuid:gno
每個 MySQL 實例都維護(hù)了一個 GTID 集合,用來對應(yīng)“這個實例執(zhí)行過的所有事務(wù)”。
在基于 GTID 的主備關(guān)系里,系統(tǒng)認(rèn)為只要建立主備關(guān)系,就必須保證主庫發(fā)給備庫的日志是完整的。因此,如果實例 B 需要的日志已經(jīng)不存在,A’就拒絕把日志發(fā)給 B。
43、MySQL 讀寫分離涉及到過期讀問題的幾種解決方案?
強(qiáng)制走主庫方案
sleep 方案
判斷主備無延遲方案
配合 semi-sync 方案
等主庫位點方案
GTID 方案。
實際生產(chǎn)中,先客戶端對請求做分類,區(qū)分哪些請求可以接受過期讀,而哪些請求完全不能接受過期讀;然后,對于不能接受過期讀的語句,再使用等 GTID 或等位點的方案。
44、MySQL的并發(fā)鏈接和并發(fā)查詢有什么區(qū)別?
在執(zhí)行show processlist的結(jié)果里,看到了幾千個連接,指的是并發(fā)連接。而"當(dāng)前正在執(zhí)行"的語句,才是并發(fā)查詢。
并發(fā)連接數(shù)多影響的是內(nèi)存,并發(fā)查詢太高對CPU不利。一個機(jī)器的CPU核數(shù)有限,線程全沖進(jìn)來,上下文切換的成本就會太高。
所以需要設(shè)置參數(shù):innodb_thread_concurrency 用來限制線程數(shù),當(dāng)線程數(shù)達(dá)到該參數(shù),InnoDB就會認(rèn)為線程數(shù)用完了,會阻止其他語句進(jìn)入引擎執(zhí)行。
性能相關(guān)
45、短時間提高 MySQL 性能的方法
第一種方法:先處理掉那些占著連接但是不工作的線程?;蛘咴倏紤]斷開事務(wù)內(nèi)空閑太久的連接。kill connection + id
第二種方法:減少連接過程的消耗:慢查詢性能問題在 MySQL 中,會引發(fā)性能問題的慢查詢,大體有以下三種可能:索引沒有設(shè)計好;SQL 語句沒寫好;MySQL 選錯了索引(force index)。
46、為什么 MySQL 自增主鍵 ID 不連續(xù)?
唯一鍵沖突
事務(wù)回滾
自增主鍵的批量申請
深層次原因是:MySQL 不判斷自增主鍵是否存在,從而減少加鎖的時間范圍和粒度,這樣能保持更高的性能,確保自增主鍵不能回退,所以才有自增主鍵不連續(xù)。
自增主鍵怎么做到唯一性?自增值加1來通過自增鎖控制并發(fā)。
47、InnoDB 為什么要用自增 ID 作為主鍵?
自增主鍵的插入模式,符合遞增插入,每次都是追加操作,不涉及挪動記錄,也不會觸發(fā)葉子節(jié)點的分裂。
每次插入新的記錄就會順序添加到當(dāng)前索引節(jié)點的后續(xù)位置,當(dāng)一頁寫滿,就會自動開辟一個新的頁。
而有業(yè)務(wù)邏輯的字段做主鍵,不容易保證有序插入,由于每次插入主鍵的值近似于隨機(jī)
因此每次新紀(jì)錄都要被插到現(xiàn)有索引頁得中間某個位置, 頻繁的移動、分頁操作造成了大量的碎片,得到了不夠緊湊的索引結(jié)構(gòu),寫數(shù)據(jù)成本較高。
48、如何最快的復(fù)制一張表?
為了避免對源表加讀鎖,更穩(wěn)妥的方案是先將數(shù)據(jù)寫到外部文本文件,然后再寫回目標(biāo)表
一種方法是,使用 mysqldump 命令將數(shù)據(jù)導(dǎo)出成一組 INSERT 語句
另一種方法是直接將結(jié)果導(dǎo)出成.csv 文件。MySQL 提供語法,用來將查詢結(jié)果導(dǎo)出到服務(wù)端本地目錄:select * from db1.t where a>900 into outfile '/server_tmp/t.csv';得到.csv 導(dǎo)出文件后,你就可以用下面的 load data 命令將數(shù)據(jù)導(dǎo)入到目標(biāo)表 db2.t 中:load data infile '/server_tmp/t.csv' into table db2.t;
物理拷貝:在 MySQL 5.6 版本引入了可傳輸表空間(transportable tablespace) 的方法,可以通過導(dǎo)出 + 導(dǎo)入表空間的方式,實現(xiàn)物理拷貝表的功能。
49、grant 和 flush privileges語句
grant語句會同時修改數(shù)據(jù)表和內(nèi)存,判斷權(quán)限的時候使用的內(nèi)存數(shù)據(jù),因此,規(guī)范使用是不需要加上 flush privileges 語句。
flush privileges 語句本身會用數(shù)據(jù)表的數(shù)據(jù)重建一份內(nèi)存權(quán)限數(shù)據(jù),所以在權(quán)限數(shù)據(jù)可能存在不一致的情況下再使用。
50、要不要使用分區(qū)表?
分區(qū)并不是越細(xì)越好。實際上,單表或者單分區(qū)的數(shù)據(jù)一千萬行,只要沒有特別大的索引,對于現(xiàn)在的硬件能力來說都已經(jīng)是小表了。
分區(qū)也不要提前預(yù)留太多,在使用之前預(yù)先創(chuàng)建即可。比如,如果是按月分區(qū),每年年底時再把下一年度的 12 個新分區(qū)創(chuàng)建上即可。對于沒有數(shù)據(jù)的歷史分區(qū),要及時的 drop 掉。
51、join 用法
使用 left join 左邊的表不一定是驅(qū)動表
如果需要 left join 的語義,就不能把被驅(qū)動表的字段放在 where 條件里面做等值判斷或不等值判斷,必須都寫在 on 里面
標(biāo)準(zhǔn)的 group by 語句,是需要在 select 部分加一個聚合函數(shù),比如select a,count(*) from t group by a order by null;
52、MySQL 有哪些自增ID?各自場景是什么?
表的自增 ID 達(dá)到上限之后,在申請值不會變化,進(jìn)而導(dǎo)致聯(lián)系插入數(shù)據(jù)的時候報主鍵沖突錯誤。
row_id 達(dá)到上限之后,歸 0 在重新遞增,如果出現(xiàn)相同的 row_id 后寫的數(shù)據(jù)會覆蓋之前的數(shù)據(jù)。
Xid 只需要不在同一個 binlog 文件出現(xiàn)重復(fù)值即可,理論上會出現(xiàn)重復(fù)值,但概率極小可忽略不計。
InnoDB 的 max_trx_id 遞增值每次 MySQL 重啟會保存起來。
Xid 是由 server 層維護(hù)的。InnoDB 內(nèi)部使用 Xid,就是為了能夠在 InnoDB 事務(wù)和 server 之間做關(guān)聯(lián)。但是,InnoDB 自己的 trx_id,是另外維護(hù)的。
thread_id 是我們使用中最常見的,而且也是處理得最好的一個自增 id 邏輯了。使用了insert_unique算法
53、Xid 在 MySQL 內(nèi)部是怎么生成的呢?
MySQL 內(nèi)部維護(hù)了一個全局變量 global_query_id,每次執(zhí)行語句(包括select語句)的時候?qū)⑺x值給 Query_id,然后給這個變量加 1。如果當(dāng)前語句是這個事務(wù)執(zhí)行的第一條語句,那么 MySQL 還會同時把 Query_id 賦值給這個事務(wù)的 Xid。
而 global_query_id 是一個純內(nèi)存變量,重啟之后就清零了。所以你就知道了,在同一個數(shù)據(jù)庫實例中,不同事務(wù)的 Xid 也是有可能相同的。但是 MySQL 重啟之后會重新生成新的 binlog 文件,這就保證了,同一個 binlog 文件里,Xid 一定是惟一的。
鎖相關(guān)
54、說一下 MySQL 的鎖
MySQL 在 server 層 和 存儲引擎層 都運用了大量的鎖
MySQL server 層需要講兩種鎖,第一種是MDL(metadata lock) 元數(shù)據(jù)鎖,第二種則 Table Lock 表鎖。
MDL 又名元數(shù)據(jù)鎖,那么什么是元數(shù)據(jù)呢,任何描述數(shù)據(jù)庫的內(nèi)容就是元數(shù)據(jù),比如我們的表結(jié)構(gòu)、庫結(jié)構(gòu)等都是元數(shù)據(jù)。那為什么需要 MDL 呢?
主要解決兩個問題:事務(wù)隔離問題;數(shù)據(jù)復(fù)制問題
InnoDB 有五種表級鎖:IS(意向讀鎖);IX(意向?qū)戞i);S(讀);X(寫);AUTO-INC
在對表進(jìn)行select/insert/delete/update語句時候不會加表級鎖
IS和IX的作用是為了判斷表中是否有已經(jīng)被加鎖的記錄
自增主鍵的保障就是有 AUTO-INC 鎖,是語句級別的:為表的某個列添加 AUTO_INCREMENT 屬性,之后在插?記錄時,可以不指定該列的值,系統(tǒng)會?動為它賦上單調(diào)遞增的值。
InnoDB 4 種行級鎖
RecordLock:記錄鎖
GapLock:間隙鎖解決幻讀;前一次查詢不存在的東西在下一次查詢出現(xiàn)了,其實就是事務(wù)A中的兩次查詢之間事務(wù)B執(zhí)行插入操作被事務(wù)A感知了
Next-KeyLock:鎖住某條記錄又想阻止其它事務(wù)在改記錄前面的間隙插入新紀(jì)錄
InsertIntentionLock:插入意向鎖;如果插入到同一行間隙中的多個事務(wù)未插入到間隙內(nèi)的同一位置則無須等待
行鎖和表鎖的抉擇
全表掃描用行級鎖
55、什么是幻讀?
值在同一個事務(wù)中,存在前后兩次查詢同一個范圍的數(shù)據(jù),第二次看到了第一次沒有查詢到的數(shù)據(jù)。
幻讀出現(xiàn)的場景:
事務(wù)的隔離級別是可重復(fù)讀,且是當(dāng)前讀。
幻讀指新插入的行。
幻讀帶來的問題:
對行鎖語義的破壞
破壞了數(shù)據(jù)一致性
解決:
加間隙鎖,鎖住行與行之間的間隙,阻塞新插入的操作。
帶來的問題:降低并發(fā)度,可能導(dǎo)致死鎖。
其它為什么系列
56、為什么 MySQL 會抖一下?
臟頁會被后臺線程自動 flush,也會由于數(shù)據(jù)頁淘汰而觸發(fā) flush,而刷臟頁的過程由于會占用資源,可能會讓你的更新和查詢語句的響應(yīng)時間長一些。
57、為什么刪除了表,表文件的大小還是沒變?
數(shù)據(jù)項刪除之后 InnoDB 某個頁 page A 會被標(biāo)記為可復(fù)用。
delete 命令把整個表的數(shù)據(jù)刪除,結(jié)果就是,所有的數(shù)據(jù)頁都會被標(biāo)記為可復(fù)用。但是磁盤上,文件不會變小。
經(jīng)過大量增刪改的表,都是可能是存在空洞的。這些空洞也占空間所以,如果能夠把這些空洞去掉,就能達(dá)到收縮表空間的目的。
重建表,就可以達(dá)到這樣的目的??梢允褂?alter table A engine=InnoDB 命令來重建表。
58、count(*)實現(xiàn)方式以及各種 count 對比
對于 count(主鍵 id) 來說,InnoDB 引擎會遍歷整張表,把每一行的 id 值都取出來,返回給 server 層。server 層拿到 id 后,判斷是不可能為空的,就按行累加。
對于 count(1) 來說,InnoDB 引擎遍歷整張表,但不取值。server 層對于返回的每一行,放一個數(shù)字“1”進(jìn)去,判斷是不可能為空的,按行累加。單看這兩個用法的差別的話,你能對比出來,count(1) 執(zhí)行得要比 count(主鍵 id) 快。因為從引擎返回 id 會涉及到解析數(shù)據(jù)行,以及拷貝字段值的操作。
對于 count(字段) 來說:如果這個“字段”是定義為 not null 的話,一行行地從記錄里面讀出這個字段,判斷不能為 null,按行累加;如果這個“字段”定義允許為 null,那么執(zhí)行的時候,判斷到有可能是 null,還要把值取出來再判斷一下,不是 null 才累加。也就是前面的第一條原則,server 層要什么字段,InnoDB 就返回什么字段。
但是 count * 是例外,并不會把全部字段取出來,而是專門做了優(yōu)化,不取值。count(*) 肯定不是 null,按行累加。
所以結(jié)論是:按照效率排序的話,count(字段)<count(主鍵 id)<count(1)≈count(※),所以建議盡量使用 count(*)。
59、orderby 排序內(nèi)部原理
MySQL 會為每個線程分配一個內(nèi)存(sort-buffer)用于排序該內(nèi)存大小為 sort_buffer_size;
如果排序的數(shù)據(jù)量小于 sort_buffer_size,排序就會在內(nèi)存中完成;
內(nèi)部排序分為兩種
全字段排序:到索引樹上找到滿足條件的主鍵ID根據(jù)主鍵ID去取出數(shù)據(jù)放到sort_buffer然后進(jìn)行快速排序
rowid排序:通過控制排序的行數(shù)據(jù)的長度來讓sort_buffer中盡可能多的存放數(shù)據(jù)
如果數(shù)據(jù)量很大,內(nèi)存中無法存下這么多,就會使用磁盤臨時文件來輔助排序,稱為外部排序;
外部排序,MySQL會分為好幾份單獨的臨時文件來存放排序后的數(shù)據(jù),一般是磁盤文件中進(jìn)行歸并,然后將這些文件合并成一個大文件;
60、如何高效的使用 MySQL 顯式隨機(jī)消息
隨機(jī)取出 Y1,Y2,Y3之后,算出Ymax,Ymin
得到id集后算出Y1、Y2、Y3對應(yīng)的三個id 最后 select * from t where id in (id1, id2, id3) 這樣掃描的行數(shù)應(yīng)該是C+Ymax+3
mysql> select count(*) into @C from t;
set @Y1 = floor(@C * rand());
set @Y2 = floor(@C * rand());
set @Y3 = floor(@C * rand());
Ymax = max(Y1,Y2,Y3)
Ymin = min(Y1,Y2,Y3)
select id from t limit Ymin,(Ymax - Ymin)