MySQL常考八股面試題:日志、SQL?優(yōu)化、鎖、事務(wù)、MVCC
這篇文章按圖片里的分類整理 MySQL 面試高頻問題。寫法盡量通俗,重點(diǎn)放在面試常問、開發(fā)常用、容易踩坑的地方,不做過度源碼展開。
一、日志
1. MySQL 三大日志:redo log、undo log、binlog 各自作用?
MySQL 面試?yán)镒畛柕娜惾罩臼?redo log、undo log、binlog。
redo log 是 InnoDB 的重做日志,主要保證事務(wù)的持久性。事務(wù)提交后,即使數(shù)據(jù)庫突然宕機(jī),也可以通過 redo log 把已經(jīng)提交的數(shù)據(jù)恢復(fù)回來。
undo log 是回滾日志,主要保證事務(wù)原子性,也用于 MVCC。事務(wù)執(zhí)行過程中會(huì)記錄修改前的數(shù)據(jù),如果事務(wù)回滾,就可以根據(jù) undo log 恢復(fù)舊值。
binlog 是 MySQL Server 層的二進(jìn)制日志,主要用于主從復(fù)制、數(shù)據(jù)恢復(fù)和審計(jì)。它記錄的是數(shù)據(jù)庫發(fā)生了哪些邏輯變更。
一句話記憶:
- redo log:保證崩潰恢復(fù),偏物理。
- undo log:保證事務(wù)回滾和 MVCC,記錄舊版本。
- binlog:保證復(fù)制和歸檔,偏邏輯。
2. redo log 為什么能保證事務(wù)崩潰恢復(fù)?WAL 機(jī)制是什么?
redo log 能保證崩潰恢復(fù),核心靠 WAL,也就是 Write Ahead Logging,先寫日志,再寫數(shù)據(jù)頁。
InnoDB 修改數(shù)據(jù)時(shí),不會(huì)每次都立刻把磁盤上的數(shù)據(jù)頁改掉,而是先修改內(nèi)存中的 Buffer Pool,同時(shí)記錄 redo log。事務(wù)提交時(shí),只要 redo log 持久化成功,就認(rèn)為事務(wù)提交成功。
如果數(shù)據(jù)庫宕機(jī),內(nèi)存里的臟頁可能還沒刷到磁盤,但 redo log 已經(jīng)在磁盤上。重啟后 InnoDB 會(huì)根據(jù) redo log 重放修改,把數(shù)據(jù)恢復(fù)到提交后的狀態(tài)。
通俗說:redo log 像快遞簽收記錄,貨物還沒完全入庫,但簽收記錄在,系統(tǒng)恢復(fù)后可以按記錄補(bǔ)齊。
3. binlog 是什么?statement、row、mixed 格式區(qū)別?
binlog 是 MySQL Server 層的二進(jìn)制日志,記錄數(shù)據(jù)庫變更,常用于主從復(fù)制和數(shù)據(jù)恢復(fù)。
它有三種常見格式:
statement:記錄 SQL 語句。優(yōu)點(diǎn)是日志量小,缺點(diǎn)是某些 SQL 在主從執(zhí)行結(jié)果可能不一致,比如 now()、uuid()、不確定順序的更新。
row:記錄每一行數(shù)據(jù)的變化。優(yōu)點(diǎn)是最準(zhǔn)確,主從一致性最好;缺點(diǎn)是日志量可能很大。
mixed:混合模式。MySQL 會(huì)根據(jù) SQL 是否安全,自動(dòng)選擇 statement 或 row。
生產(chǎn)環(huán)境更常用 row,因?yàn)閺?fù)制更可靠,也方便做數(shù)據(jù)訂正和恢復(fù)。
4. redo log 和 binlog 區(qū)別?
redo log 和 binlog 經(jīng)常一起問,因?yàn)樗鼈兌加涗浶薷模ㄎ煌耆煌?/p>
redo log 是 InnoDB 引擎層日志,binlog 是 MySQL Server 層日志。
redo log 主要用于崩潰恢復(fù),binlog 主要用于主從復(fù)制和數(shù)據(jù)恢復(fù)。
redo log 是循環(huán)寫,空間固定,會(huì)覆蓋舊日志;binlog 是追加寫,一個(gè)文件寫滿后切換到下一個(gè)。
redo log 記錄偏物理變化,比如某個(gè)頁做了什么修改;binlog 記錄偏邏輯變化,比如執(zhí)行了什么 SQL 或哪行變成什么樣。
一句話:redo log 管“宕機(jī)后自己怎么恢復(fù)”,binlog 管“別人怎么同步和回放”。
5. 事務(wù)提交時(shí) redo log、binlog 兩階段提交原理?
兩階段提交是為了保證 redo log 和 binlog 一致。
大致流程:
- InnoDB 寫 redo log,狀態(tài)為 prepare。
- MySQL Server 寫 binlog。
- InnoDB 把 redo log 改成 commit 狀態(tài)。
為什么要這樣?因?yàn)槭聞?wù)提交涉及兩個(gè)日志,如果只寫成功一個(gè)就宕機(jī),會(huì)出現(xiàn)主庫和從庫數(shù)據(jù)不一致。
恢復(fù)時(shí)會(huì)判斷:
- redo log 有 prepare,binlog 也完整:提交事務(wù)。
- redo log 有 prepare,binlog 不完整:回滾事務(wù)。
這樣可以保證主庫崩潰恢復(fù)結(jié)果和 binlog 復(fù)制結(jié)果一致。
二、SQL 優(yōu)化
1. 一條 SQL 執(zhí)行完整流程?
一條 SQL 大致會(huì)經(jīng)過這些步驟:
- 客戶端發(fā)送 SQL 到 MySQL Server。
- 連接器管理連接和權(quán)限。
- 解析器做詞法、語法分析。
- 預(yù)處理器檢查表、字段是否存在。
- 優(yōu)化器選擇執(zhí)行計(jì)劃,比如用哪個(gè)索引、表連接順序。
- 執(zhí)行器調(diào)用存儲(chǔ)引擎接口。
- 存儲(chǔ)引擎讀取或修改數(shù)據(jù)。
- 返回結(jié)果給客戶端。
如果是更新語句,還會(huì)涉及 undo log、redo log、binlog 等日志。
2. explain 執(zhí)行計(jì)劃每個(gè)字段含義?
explain 用來查看 SQL 執(zhí)行計(jì)劃,面試常問這些字段:
id:查詢執(zhí)行順序標(biāo)識(shí)。id 越大通常越先執(zhí)行;相同 id 從上往下執(zhí)行。
select_type:查詢類型,比如 SIMPLE、PRIMARY、SUBQUERY、DERIVED。
type:訪問類型,表示查表效率,優(yōu)化重點(diǎn)字段。
key:實(shí)際使用的索引。
rows:優(yōu)化器預(yù)估要掃描的行數(shù)。
Extra:額外信息,比如 Using index、Using where、Using filesort、Using temporary。
看 explain 時(shí),重點(diǎn)關(guān)注 type、key、rows、Extra。
3. type 執(zhí)行效率級(jí)別:all、index、range、ref、eq_ref、const、system
type 表示 MySQL 怎么訪問表,常見效率從差到好大概是:
ALL < index < range < ref < eq_ref < const < system
ALL:全表掃描,通常最差。index:掃描整個(gè)索引樹,比全表掃描稍好,但仍然掃很多。range:范圍掃描,比如between、>、in。ref:普通索引等值匹配,可能匹配多行。eq_ref:唯一索引或主鍵關(guān)聯(lián)查詢,每次最多匹配一行。const:主鍵或唯一索引等值查詢,結(jié)果最多一行。system:表只有一行,是 const 的特殊情況。
實(shí)際優(yōu)化目標(biāo)一般是避免 ALL,盡量達(dá)到 range、ref 或更好。
4. 怎么看慢查詢?nèi)罩荆?/h3>
慢查詢?nèi)罩居糜谟涗泩?zhí)行時(shí)間超過閾值的 SQL。
常用參數(shù):
slow_query_log:是否開啟慢查詢?nèi)罩尽?/li>long_query_time:超過多少秒算慢 SQL。slow_query_log_file:慢查詢?nèi)罩疚募窂健?/li>
排查時(shí)重點(diǎn)看:
- SQL 原文。
- 執(zhí)行耗時(shí)。
- 掃描行數(shù)。
- 返回行數(shù)。
- 是否走索引。
常用分析工具有 mysqldumpslow 和 pt-query-digest。
5. 慢查詢優(yōu)化整體思路?
慢 SQL 優(yōu)化可以按這個(gè)順序來:
- 用慢查詢?nèi)罩径ㄎ粏栴} SQL。
- 用
explain看執(zhí)行計(jì)劃。 - 判斷是否走了合適索引。
- 檢查是否有回表、Using filesort、Using temporary。
- 優(yōu)化 SQL 寫法,減少掃描行數(shù)。
- 必要時(shí)調(diào)整索引、拆表、緩存或改業(yè)務(wù)方案。
核心原則:少掃行、少回表、少排序、少臨時(shí)表。
6. limit 分頁深偏移量怎么優(yōu)化?例如limit 1000000, 10
limit 1000000, 10 慢,是因?yàn)?MySQL 需要先掃描并丟棄前 1000000 行,再返回 10 行。
常見優(yōu)化方式:
第一種,基于上一頁最大 id 做游標(biāo)分頁:
select * from user where id > 1000000 order by id limit 10;
第二種,先用覆蓋索引查出 id,再回表:
select u.*
from user u
join (
select id from user order by id limit 1000000, 10
) t on u.id = t.id;
第三種,產(chǎn)品層面避免跳到特別深的頁,比如搜索引擎通常只展示前幾十頁。
7. order by 排序原理,什么時(shí)候 Using filesort?怎么優(yōu)化?
order by 如果能直接利用索引順序,就不需要額外排序。
如果不能利用索引排序,MySQL 會(huì)使用 filesort。這里的 filesort 不一定真的落磁盤,它表示額外排序算法,數(shù)據(jù)大時(shí)可能用臨時(shí)文件。
常見觸發(fā)原因:
- 排序字段沒有合適索引。
- 聯(lián)合索引順序不符合最左前綴。
- 排序方向混亂,索引無法完全利用。
- where 條件和 order by 字段不匹配。
優(yōu)化方式:
- 給
where + order by建合適聯(lián)合索引。 - 盡量使用覆蓋索引。
- 控制返回?cái)?shù)據(jù)量。
- 避免對(duì)排序字段使用函數(shù)或表達(dá)式。
8. group by 原理與優(yōu)化思路?
group by 用于分組聚合。MySQL 執(zhí)行時(shí)通常需要按分組字段聚集數(shù)據(jù),可能用索引,也可能用臨時(shí)表和排序。
優(yōu)化思路:
- 給分組字段建立索引。
- where 先過濾,減少參與分組的數(shù)據(jù)量。
- 只查詢必要字段。
- 避免大結(jié)果集分組。
- 能在業(yè)務(wù)或離線任務(wù)預(yù)聚合的,不要每次實(shí)時(shí)算大表。
如果 explain 里出現(xiàn) Using temporary、Using filesort,說明可能存在額外臨時(shí)表和排序成本。
9. join 連接原理:內(nèi)連接、左連接、右連接
join 本質(zhì)是把多張表按條件關(guān)聯(lián)起來。
內(nèi)連接 inner join:只返回兩邊都匹配的數(shù)據(jù)。
左連接 left join:返回左表全部數(shù)據(jù),右表匹配不到時(shí)右表字段為 null。
右連接 right join:返回右表全部數(shù)據(jù),左表匹配不到時(shí)左表字段為 null。
開發(fā)中更常用 inner join 和 left join。右連接通??梢愿膶懗勺筮B接,保持閱讀習(xí)慣統(tǒng)一。
10. 大表 join 怎么優(yōu)化?
大表 join 優(yōu)化重點(diǎn)是減少驅(qū)動(dòng)表數(shù)據(jù)量,并讓被驅(qū)動(dòng)表能走索引。
常見做法:
- 小表驅(qū)動(dòng)大表。
- join 字段建立索引,類型保持一致。
- 先 where 過濾,再 join。
- 只查需要字段,避免
select *。 - 大分頁、大排序、大分組盡量拆分。
- 復(fù)雜場景可以用冗余字段、寬表、緩存、離線計(jì)算。
一句話:讓參與 join 的數(shù)據(jù)盡量少,讓匹配過程盡量走索引。
三、基礎(chǔ)概念
1. MySQL 存儲(chǔ)引擎有哪些?InnoDB、MyISAM 區(qū)別?
MySQL 常見存儲(chǔ)引擎有 InnoDB、MyISAM、Memory、Archive 等?,F(xiàn)在生產(chǎn)最常用的是 InnoDB。
InnoDB 支持事務(wù)、行級(jí)鎖、外鍵、崩潰恢復(fù),適合高并發(fā)和事務(wù)場景。
MyISAM 不支持事務(wù),不支持行鎖,主要是表級(jí)鎖,崩潰恢復(fù)能力弱,但結(jié)構(gòu)簡單,早期讀多寫少場景用得較多。
現(xiàn)在默認(rèn)優(yōu)先選擇 InnoDB。
2. InnoDB 相比 MyISAM 優(yōu)勢(shì)在哪?
InnoDB 的優(yōu)勢(shì)主要有:
- 支持事務(wù),滿足 ACID。
- 支持行級(jí)鎖,并發(fā)寫能力更好。
- 支持崩潰恢復(fù),可靠性更高。
- 支持 MVCC,提高讀寫并發(fā)。
- 支持外鍵。
面試?yán)锟梢灾苯诱f:InnoDB 更適合現(xiàn)代業(yè)務(wù)系統(tǒng),尤其是高并發(fā)、強(qiáng)一致、需要事務(wù)的場景。
3. 什么是事務(wù)?事務(wù)四大特性 ACID 分別是什么?
事務(wù)是一組操作的集合,要么全部成功,要么全部失敗。
ACID 分別是:
A Atomicity 原子性:事務(wù)內(nèi)操作要么全成功,要么全失敗。主要靠 undo log。
C Consistency 一致性:事務(wù)執(zhí)行前后,數(shù)據(jù)從一個(gè)一致狀態(tài)變成另一個(gè)一致狀態(tài)。
I Isolation 隔離性:多個(gè)事務(wù)并發(fā)執(zhí)行時(shí),彼此影響受隔離級(jí)別控制。
D Durability 持久性:事務(wù)提交后數(shù)據(jù)不會(huì)丟。主要靠 redo log。
4. 數(shù)據(jù)庫三范式是什么?日常開發(fā)一定要嚴(yán)格遵守嗎?
第一范式:字段不可再分,保證原子性。
第二范式:非主鍵字段必須完全依賴主鍵,避免部分依賴。
第三范式:非主鍵字段不能依賴其他非主鍵字段,避免傳遞依賴。
日常開發(fā)不一定死守三范式。范式能減少冗余、提高一致性,但有時(shí)為了查詢性能,會(huì)適當(dāng)反范式,比如冗余用戶名、訂單快照、統(tǒng)計(jì)字段。
原則是:核心數(shù)據(jù)保證一致,讀多性能瓶頸場景可以有控制地冗余。
5. 執(zhí)行一條 SQL 語句,期間都發(fā)生了哪些事情?
以更新語句為例:
- 客戶端發(fā)送 SQL。
- Server 層解析、優(yōu)化、生成執(zhí)行計(jì)劃。
- 執(zhí)行器調(diào)用 InnoDB。
- InnoDB 找到數(shù)據(jù)頁,加載到 Buffer Pool。
- 記錄 undo log,便于回滾。
- 修改內(nèi)存頁,產(chǎn)生臟頁。
- 寫 redo log prepare。
- Server 層寫 binlog。
- redo log commit。
- 后臺(tái)線程擇機(jī)把臟頁刷盤。
查詢語句則主要涉及解析、優(yōu)化、執(zhí)行、走索引、回表、返回結(jié)果。
四、鎖
1. InnoDB 有哪些鎖?行鎖、表鎖、意向鎖
InnoDB 常見鎖包括:
- 表鎖:鎖整張表。
- 行鎖:鎖某些記錄,粒度小,并發(fā)度高。
- 意向鎖:表級(jí)鎖,用來表示事務(wù)接下來想鎖某些行。
- 記錄鎖:鎖具體索引記錄。
- 間隙鎖:鎖兩個(gè)索引記錄之間的間隙。
- 臨鍵鎖:記錄鎖 + 間隙鎖。
InnoDB 的行鎖是加在索引上的,如果查詢條件沒有走索引,可能導(dǎo)致鎖范圍變大。
2. 行鎖什么時(shí)候變表鎖?
嚴(yán)格說,InnoDB 行鎖不會(huì)真的“升級(jí)”為表鎖;但如果 SQL 沒有走索引,InnoDB 可能掃描很多行并對(duì)大量記錄加鎖,看起來像鎖表。
常見原因:
- where 條件沒有索引。
- 索引失效。
- 字段類型不一致導(dǎo)致隱式轉(zhuǎn)換。
- 范圍條件過大。
所以更新和刪除時(shí)一定要確認(rèn)條件走索引,尤其是大表。
3. 記錄鎖、間隙鎖、臨鍵鎖分別是什么?
記錄鎖 Record Lock:鎖住某一條索引記錄。
間隙鎖 Gap Lock:鎖住索引記錄之間的間隙,不鎖具體記錄,主要防止幻讀。
臨鍵鎖 Next-Key Lock:記錄鎖 + 間隙鎖,既鎖記錄,也鎖記錄前面的間隙。
例如索引里有 10 和 20,間隙鎖可能鎖住 (10, 20) 這個(gè)范圍,防止其他事務(wù)插入 15。
4. 臨鍵鎖怎么解決幻讀?
幻讀是同一個(gè)事務(wù)內(nèi),兩次范圍查詢結(jié)果條數(shù)不一致,比如第一次查沒有某條記錄,第二次查突然出現(xiàn)了。
在可重復(fù)讀隔離級(jí)別下,InnoDB 對(duì)范圍查詢加臨鍵鎖,鎖住已有記錄和記錄之間的間隙。這樣其他事務(wù)就不能在這個(gè)范圍里插入新記錄。
所以臨鍵鎖通過“鎖記錄 + 鎖間隙”防止范圍內(nèi)新增數(shù)據(jù),從而解決當(dāng)前讀下的幻讀問題。
5. 什么是死鎖?產(chǎn)生條件、怎么排查和避免死鎖?
死鎖是多個(gè)事務(wù)互相等待對(duì)方持有的鎖,導(dǎo)致都無法繼續(xù)。
產(chǎn)生條件:
- 互斥。
- 持有并等待。
- 不可剝奪。
- 循環(huán)等待。
排查方式:
- 使用
show engine innodb status查看最近一次死鎖信息。 - 查看事務(wù)持有什么鎖、等待什么鎖。
- 結(jié)合慢 SQL、業(yè)務(wù)日志定位 SQL 順序。
避免方式:
- 統(tǒng)一加鎖順序。
- 事務(wù)盡量短。
- where 條件走索引。
- 避免大范圍更新。
- 必要時(shí)使用重試機(jī)制。
五、事務(wù)(超級(jí)高頻)
1. MySQL 四大事務(wù)隔離級(jí)別分別是什么?
MySQL 標(biāo)準(zhǔn)隔離級(jí)別有四種:
- 讀未提交 Read Uncommitted。
- 讀已提交 Read Committed。
- 可重復(fù)讀 Repeatable Read。
- 串行化 Serializable。
隔離級(jí)別越高,并發(fā)能力通常越低,一致性約束越強(qiáng)。
2. 臟讀、不可重復(fù)讀、幻讀分別是什么?
臟讀:讀到了其他事務(wù)還沒提交的數(shù)據(jù)。如果對(duì)方回滾,你讀到的就是臟數(shù)據(jù)。
不可重復(fù)讀:同一個(gè)事務(wù)內(nèi),兩次讀取同一行數(shù)據(jù),結(jié)果不一樣。通常是其他事務(wù)提交了修改。
幻讀:同一個(gè)事務(wù)內(nèi),兩次范圍查詢,結(jié)果集條數(shù)不一樣。通常是其他事務(wù)插入或刪除了符合條件的數(shù)據(jù)。
簡單記:臟讀讀到未提交,不可重復(fù)讀是同一行變了,幻讀是結(jié)果集行數(shù)變了。
3. 四大隔離級(jí)別分別能解決哪些問題?
讀未提交:什么都解決不了,可能臟讀、不可重復(fù)讀、幻讀。
讀已提交:解決臟讀,但可能不可重復(fù)讀和幻讀。
可重復(fù)讀:解決臟讀、不可重復(fù)讀。InnoDB 通過 MVCC 和臨鍵鎖在很多場景下也解決幻讀。
串行化:基本都能解決,但并發(fā)性能最低。
生產(chǎn)中 MySQL InnoDB 默認(rèn)是可重復(fù)讀。
4. InnoDB 默認(rèn)隔離級(jí)別是什么?
InnoDB 默認(rèn)隔離級(jí)別是 Repeatable Read,也就是可重復(fù)讀。
它通過 MVCC 保證普通快照讀的可重復(fù)讀,通過 next-key lock 處理當(dāng)前讀下的幻讀問題。
5. 什么是幻讀?怎么解決幻讀?
幻讀指一個(gè)事務(wù)內(nèi)兩次范圍查詢,第二次出現(xiàn)了第一次沒有的記錄,像“幻影”一樣。
解決方式:
- 串行化隔離級(jí)別,直接強(qiáng)約束并發(fā)。
- InnoDB 可重復(fù)讀下,普通快照讀通過 Read View 避免幻讀。
- 當(dāng)前讀通過 next-key lock 鎖住范圍,阻止其他事務(wù)插入。
注意:MVCC 主要解決快照讀的一致性,臨鍵鎖主要解決當(dāng)前讀的幻讀。
6. 事務(wù)的實(shí)現(xiàn)原理:MVCC 是什么?
事務(wù)能力不是一個(gè)單獨(dú)機(jī)制完成的,而是多個(gè)機(jī)制配合:
- 原子性靠 undo log。
- 持久性靠 redo log。
- 隔離性靠鎖和 MVCC。
- 一致性由業(yè)務(wù)約束、數(shù)據(jù)庫約束和上述機(jī)制共同保證。
MVCC 是多版本并發(fā)控制。它讓讀操作可以讀到某個(gè)時(shí)間點(diǎn)的數(shù)據(jù)版本,而不是總被寫操作阻塞。
通俗說:數(shù)據(jù)被修改后,舊版本不會(huì)立刻消失,讀事務(wù)可以根據(jù)規(guī)則找到自己應(yīng)該看到的版本。
7. 可重復(fù)讀隔離級(jí)別,完全解決幻讀了嗎?
要分情況說。
對(duì)于普通 select 快照讀,可重復(fù)讀通過 MVCC 的 Read View 能保證同一事務(wù)內(nèi)查詢結(jié)果一致,基本避免幻讀。
對(duì)于 select ... for update、update、delete 這類當(dāng)前讀,InnoDB 通過 next-key lock 鎖范圍,防止其他事務(wù)插入,從而避免幻讀。
但如果混用快照讀和當(dāng)前讀,可能看到不一樣的結(jié)果,這是面試?yán)锏募臃贮c(diǎn)。
六、MVCC
1. MVCC 底層原理是什么?
MVCC 全稱 Multi-Version Concurrency Control,多版本并發(fā)控制。
InnoDB 每行記錄都有隱藏字段,配合 undo log 保存歷史版本,再通過 Read View 判斷當(dāng)前事務(wù)能看到哪個(gè)版本。
它的核心目標(biāo)是提高讀寫并發(fā):讀不阻塞寫,寫不阻塞普通讀。
2. 什么是快照讀、當(dāng)前讀?舉例說明
快照讀讀取的是某個(gè)時(shí)間點(diǎn)的數(shù)據(jù)版本,不加鎖。
普通 select 通常是快照讀:
select * from user where id = 1;
當(dāng)前讀讀取的是最新數(shù)據(jù),并且通常要加鎖。
常見當(dāng)前讀:
select * from user where id = 1 for update; update user set name = 'Tom' where id = 1; delete from user where id = 1;
快照讀看歷史版本,當(dāng)前讀看最新版本。
3. 隱式字段 DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID 作用?
InnoDB 行記錄里有幾個(gè)隱藏字段:
DB_TRX_ID:最近一次修改這行記錄的事務(wù) id。
DB_ROLL_PTR:回滾指針,指向 undo log 中的舊版本。
DB_ROW_ID:如果表沒有主鍵和唯一非空索引,InnoDB 會(huì)生成隱藏行 id。
MVCC 主要依賴 DB_TRX_ID 和 DB_ROLL_PTR 找到可見版本。
4. undo log 日志作用、版本鏈?zhǔn)鞘裁矗?/h3>
undo log 記錄數(shù)據(jù)修改前的舊值,用于事務(wù)回滾和 MVCC。
當(dāng)一行數(shù)據(jù)被多次修改時(shí),每次修改都會(huì)產(chǎn)生 undo log,記錄之間通過回滾指針串起來,就形成版本鏈。
查詢時(shí),如果當(dāng)前版本對(duì)事務(wù)不可見,InnoDB 會(huì)沿著版本鏈往前找,直到找到一個(gè)可見版本,或者找不到。
5. Read View 視圖四個(gè)字段作用、可見性規(guī)則?
Read View 是 MVCC 判斷版本是否可見的核心。
常見字段:
m_ids:創(chuàng)建 Read View 時(shí),系統(tǒng)中活躍事務(wù) id 列表。min_trx_id:活躍事務(wù)中最小 id。max_trx_id:下一個(gè)將要分配的事務(wù) id。creator_trx_id:創(chuàng)建這個(gè) Read View 的事務(wù) id。
可見性規(guī)則簡化理解:
- 如果版本的事務(wù) id 小于
min_trx_id,說明早就提交了,可見。 - 如果版本的事務(wù) id 大于等于
max_trx_id,說明是之后才出現(xiàn)的,不可見。 - 如果事務(wù) id 在
m_ids里,說明當(dāng)時(shí)還沒提交,不可見。 - 如果事務(wù) id 不在
m_ids里,說明已經(jīng)提交,可見。
6. MVCC 怎么實(shí)現(xiàn)可重復(fù)讀、讀已提交?
讀已提交 RC:每次執(zhí)行普通 select 都會(huì)生成新的 Read View。所以同一個(gè)事務(wù)里,第二次查詢能看到其他事務(wù)已經(jīng)提交的數(shù)據(jù)。
可重復(fù)讀 RR:事務(wù)中第一次普通 select 時(shí)生成 Read View,后續(xù)普通 select 復(fù)用這個(gè) Read View。所以同一個(gè)事務(wù)里多次讀取結(jié)果一致。
區(qū)別就在于 Read View 的創(chuàng)建時(shí)機(jī)。
七、索引
1. InnoDB 索引結(jié)構(gòu)為什么選 B+ 樹,不選二叉樹、紅黑樹、B 樹?
數(shù)據(jù)庫索引存儲(chǔ)在磁盤上,核心目標(biāo)是減少磁盤 IO。
二叉樹和紅黑樹高度相對(duì)較高,數(shù)據(jù)量大時(shí)查找層數(shù)多,磁盤 IO 多。
B 樹每個(gè)節(jié)點(diǎn)既存 key 也存數(shù)據(jù),單頁能放的 key 數(shù)量相對(duì)少,樹可能更高。
B+ 樹非葉子節(jié)點(diǎn)只存 key 和指針,葉子節(jié)點(diǎn)存完整數(shù)據(jù)或主鍵,單頁能放更多 key,樹更矮,IO 更少。葉子節(jié)點(diǎn)還用鏈表連接,范圍查詢更方便。
所以 InnoDB 選擇 B+ 樹是為了降低 IO、提高范圍查詢效率。
2. B+ 樹和 B 樹的區(qū)別?
B 樹的每個(gè)節(jié)點(diǎn)都可能存數(shù)據(jù)。
B+ 樹只有葉子節(jié)點(diǎn)存數(shù)據(jù),非葉子節(jié)點(diǎn)只做索引導(dǎo)航。
B+ 樹葉子節(jié)點(diǎn)之間有鏈表,范圍查詢更快。
B+ 樹單個(gè)非葉子節(jié)點(diǎn)能存更多 key,樹更矮,磁盤 IO 更少。
3. 聚簇索引和非聚簇索引(二級(jí)索引)區(qū)別?
聚簇索引的葉子節(jié)點(diǎn)存整行數(shù)據(jù)。在 InnoDB 中,主鍵索引就是聚簇索引。
非聚簇索引,也叫二級(jí)索引,葉子節(jié)點(diǎn)存的是索引字段和主鍵值。
通過二級(jí)索引查到主鍵后,如果還需要其他字段,就要再根據(jù)主鍵回到聚簇索引查整行,這就是回表。
4. 主鍵索引、唯一索引、普通索引、聯(lián)合索引區(qū)別?
主鍵索引:唯一且不能為空,一張表只能有一個(gè)主鍵。
唯一索引:值不能重復(fù),但通常允許 null,具體行為和數(shù)據(jù)庫規(guī)則有關(guān)。
普通索引:沒有唯一性限制,只提升查詢效率。
聯(lián)合索引:多個(gè)字段組合成一個(gè)索引,比如 (a, b, c),使用時(shí)要遵守最左前綴原則。
5. 什么是回表查詢?怎么避免回表?
回表是指通過二級(jí)索引查到主鍵后,再根據(jù)主鍵去聚簇索引查整行數(shù)據(jù)。
例如有索引 (name):
select age from user where name = 'Tom';
如果 age 不在索引里,就需要回表。
避免回表的方式是使用覆蓋索引,把查詢需要的字段都放進(jìn)索引:
create index idx_name_age on user(name, age);
6. 覆蓋索引是什么?使用場景?
覆蓋索引是指查詢需要的字段都能從索引里拿到,不需要回表。
例如聯(lián)合索引 (name, age):
select name, age from user where name = 'Tom';
這種情況下只查索引就夠了。
覆蓋索引適合高頻查詢、列表頁、分頁查詢等場景,可以減少回表 IO。
7. 最左前綴原則原理,為什么要遵守?
聯(lián)合索引按字段順序排序,比如 (a, b, c),索引先按 a 排,再按 b 排,最后按 c 排。
所以查詢必須從最左邊字段開始連續(xù)使用,才能充分利用索引。
可以走索引的例子:
where a = 1 where a = 1 and b = 2 where a = 1 and b = 2 and c = 3
不符合的例子:
where b = 2 where c = 3
因?yàn)槿鄙僮钭笞侄?a,索引整體順序用不上。
8. 索引下推原理和作用?
索引下推 Index Condition Pushdown,簡稱 ICP,是 MySQL 的一種優(yōu)化。
沒有索引下推時(shí),存儲(chǔ)引擎根據(jù)索引找到記錄后,可能先回表,再由 Server 層判斷其他條件。
有索引下推時(shí),能在存儲(chǔ)引擎層先用索引里的字段過濾一部分?jǐn)?shù)據(jù),減少回表次數(shù)。
典型場景是聯(lián)合索引中部分字段可以用于過濾,但不能完全用于定位。
9. 什么是索引失效?哪些情況會(huì)導(dǎo)致索引失效?
索引失效是指 SQL 雖然有索引,但優(yōu)化器沒有使用,或者只能使用一部分。
常見原因:
- 對(duì)索引列使用函數(shù)或表達(dá)式。
- 字段類型不一致導(dǎo)致隱式轉(zhuǎn)換。
like '%xxx'前綴模糊。- 聯(lián)合索引不滿足最左前綴。
- 使用
or且部分條件沒有索引。 - 范圍查詢后面的聯(lián)合索引字段無法繼續(xù)有序利用。
- 數(shù)據(jù)量太小或優(yōu)化器判斷全表掃描更劃算。
優(yōu)化時(shí)不要只看“建沒建索引”,要看 explain 里實(shí)際有沒有用。
10. 模糊查詢like '%xxx'、like 'xxx%'哪個(gè)走索引?
like 'xxx%' 可以走索引,因?yàn)榍熬Y確定,B+ 樹可以按范圍查。
like '%xxx' 一般不能走普通 B+ 樹索引,因?yàn)殚_頭不確定,無法從索引樹定位范圍。
如果必須支持任意位置模糊搜索,可以考慮全文索引、搜索引擎,或者業(yè)務(wù)側(cè)倒排索引。
11. 字段類型隱式轉(zhuǎn)換為什么會(huì)導(dǎo)致索引失效?
如果字段類型和查詢條件類型不一致,MySQL 可能對(duì)字段做隱式轉(zhuǎn)換。
例如手機(jī)號(hào)字段是 varchar,卻這樣查:
where phone = 13800138000
MySQL 可能把 phone 轉(zhuǎn)成數(shù)字比較,相當(dāng)于對(duì)索引列做函數(shù)處理,索引就可能失效。
正確寫法:
where phone = '13800138000'
12. 為什么不建議用select *?
不建議 select * 的原因:
- 查出不需要的字段,增加網(wǎng)絡(luò)和內(nèi)存開銷。
- 更容易回表,無法利用覆蓋索引。
- 表結(jié)構(gòu)變更時(shí),結(jié)果字段不穩(wěn)定。
- 大字段如 text、blob 會(huì)拖慢查詢。
生產(chǎn)建議明確寫出需要的字段。
13. 聯(lián)合索引創(chuàng)建順序原則:區(qū)分度高、長度小、經(jīng)常查詢
聯(lián)合索引字段順序一般考慮:
- 經(jīng)常用于查詢條件的字段靠前。
- 區(qū)分度高的字段優(yōu)先。
- 字段長度小的優(yōu)先,索引更緊湊。
- 等值查詢字段通常放前面,范圍查詢字段放后面。
- 還要兼顧 order by、group by。
沒有絕對(duì)公式,要結(jié)合真實(shí) SQL 和 explain 判斷。
14. 什么時(shí)候不適合建索引?
這些場景不太適合建索引:
- 表數(shù)據(jù)量很小。
- 字段區(qū)分度很低,比如性別、狀態(tài)值很少。
- 字段很少用于查詢條件。
- 寫入非常頻繁,索引會(huì)增加維護(hù)成本。
- 大字段不適合直接建普通索引。
- 已有聯(lián)合索引可以覆蓋,不需要重復(fù)建單列索引。
索引不是越多越好。它能加快查詢,但會(huì)拖慢寫入,并占用磁盤空間。
八、面試回答小抄
- redo log 保證崩潰恢復(fù),undo log 支持回滾和 MVCC,binlog 用于復(fù)制和歸檔。
- WAL 是先寫日志再寫數(shù)據(jù)頁,保證宕機(jī)后能恢復(fù)。
- 兩階段提交解決 redo log 和 binlog 一致性問題。
- explain 重點(diǎn)看 type、key、rows、Extra。
- 慢 SQL 優(yōu)化核心是少掃行、少回表、少排序、少臨時(shí)表。
- InnoDB 默認(rèn)隔離級(jí)別是可重復(fù)讀。
- MVCC 依賴隱藏字段、undo log 版本鏈和 Read View。
- 快照讀讀歷史版本,當(dāng)前讀讀最新版本并加鎖。
- InnoDB 索引用 B+ 樹,是為了降低 IO 和優(yōu)化范圍查詢。
- 聯(lián)合索引要遵守最左前綴原則。
like 'xxx%'通??勺咚饕?code>like '%xxx' 通常不走普通索引。
總結(jié)
MySQL 面試題看起來分散,其實(shí)主線很清楚:
- 日志:redo、undo、binlog 分別解決恢復(fù)、回滾、復(fù)制。
- SQL 優(yōu)化:先定位慢 SQL,再看執(zhí)行計(jì)劃,最后減少掃描和回表。
- 鎖:理解行鎖、間隙鎖、臨鍵鎖,以及死鎖排查。
- 事務(wù):抓住 ACID、隔離級(jí)別、臟讀、不可重復(fù)讀、幻讀。
- MVCC:抓住隱藏字段、undo 版本鏈、Read View。
- 索引:抓住 B+ 樹、聚簇索引、回表、覆蓋索引、最左前綴。
面試回答時(shí)建議先說結(jié)論,再講原理,最后補(bǔ)一句實(shí)際開發(fā)中的坑點(diǎn),這樣比單純背概念更容易拿分。
到此這篇關(guān)于MySQL??及斯擅嬖囶}:日志、SQL 優(yōu)化、鎖、事務(wù)、MVCC的文章就介紹到這了,更多相關(guān)mysql??济嬖囶}內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
優(yōu)化mysql數(shù)據(jù)庫的經(jīng)驗(yàn)總結(jié)
本篇文章是對(duì)優(yōu)化mysql數(shù)據(jù)庫的經(jīng)驗(yàn)進(jìn)行了詳細(xì)的總結(jié)介紹,需要的朋友參考下2013-06-06
Mysql中基本語句優(yōu)化的十個(gè)原則小結(jié)
這篇文章主要給大家總結(jié)介紹了Mysql中基本語句優(yōu)化的十個(gè)原則,通過學(xué)習(xí)與記住它們,在構(gòu)造sql時(shí)可以養(yǎng)成良好的習(xí)慣,文中介紹的相對(duì)比較詳細(xì)與簡單明了,需要的朋友們可以參考借鑒,下面來一起看看吧。2017-06-06
MySQL存儲(chǔ)引擎 InnoDB與MyISAM的區(qū)別
InnoDB和MyISAM是許多人在使用MySQL時(shí)最常用的兩個(gè)表類型,這兩個(gè)表類型各有優(yōu)劣,視具體應(yīng)用而定。2014-03-03
mysql實(shí)現(xiàn)定時(shí)備份的詳細(xì)圖文教程
這篇文章主要給大家介紹了關(guān)于mysql實(shí)現(xiàn)定時(shí)備份的詳細(xì)圖文教程,我們都知道數(shù)據(jù)是無價(jià),如果不對(duì)數(shù)據(jù)進(jìn)行備份,相當(dāng)是讓數(shù)據(jù)在裸跑,一旦服務(wù)器出問題,只有哭的份了,需要的朋友可以參考下2023-07-07
深入理解MySQL主從復(fù)制線程狀態(tài)轉(zhuǎn)變
這篇文章主要給大家介紹了關(guān)于MySQL主從復(fù)制線程狀態(tài)轉(zhuǎn)變的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02
mysql tmp_table_size和max_heap_table_size大小配置
這篇文章主要介紹了mysql tmp_table_size和max_heap_table_size大小配置,需要的朋友可以參考下2016-05-05
CentOS 6.5 i386 安裝MySQL 5.7.18詳細(xì)教程
這篇文章主要介紹了CentOS 6.5 i386 安裝MySQL 5.7.18詳細(xì)教程,需要的朋友可以參考下2017-04-04
MySQL 修改數(shù)據(jù)庫名稱的一個(gè)新奇方法
這篇文章主要介紹了MySQL 修改數(shù)據(jù)庫名稱的一個(gè)新奇方法,MySQL 修改數(shù)據(jù)庫名的一個(gè)變通方法,需要的朋友可以參考下2014-07-07
mysql workbench 設(shè)置外鍵的方法實(shí)現(xiàn)
在MySQL Workbench中設(shè)置外鍵屬性是非常方便的,本文就來介紹一下mysql workbench 設(shè)置外鍵的方法實(shí)現(xiàn),具有一定能的參考價(jià)值,感興趣的可以了解一下2024-01-01

