MySQL性能調(diào)優(yōu)面試復(fù)習(xí)小結(jié)之Explain、索引、慢查詢、緩存和架構(gòu)優(yōu)化
前言
這篇文章系統(tǒng)梳理 MySQL 查詢慢的排查和優(yōu)化方法,讀完你可以用一套清晰思路回答性能調(diào)優(yōu)類面試題。
面試?yán)飭?MySQL 性能優(yōu)化,最怕答成散點(diǎn)。
比如只說“加索引”。
這不夠。
真正靠譜的回答,應(yīng)該從 定位問題 開始,再到 索引優(yōu)化、SQL 優(yōu)化、分頁優(yōu)化、表結(jié)構(gòu)優(yōu)化、緩存和架構(gòu)優(yōu)化。
核心目標(biāo)就一句話:
讓 MySQL 少掃描、少回表、少排序、少建臨時(shí)表、少做無效 IO。
一、先用 EXPLAIN 定位問題
查詢慢,不要上來就加索引。
第一步應(yīng)該是看執(zhí)行計(jì)劃。
常用命令:
EXPLAIN SELECT * FROM user WHERE age = 18;
EXPLAIN 可以告訴你 MySQL 準(zhǔn)備怎么執(zhí)行這條 SQL。
重點(diǎn)看這些字段:
| 字段 | 含義 | 重點(diǎn) |
|---|---|---|
possible_keys | 可能使用的索引 | 優(yōu)化器發(fā)現(xiàn)的候選索引 |
key | 實(shí)際使用的索引 | 為 NULL 說明沒用索引 |
key_len | 實(shí)際使用的索引長(zhǎng)度 | 常用于判斷聯(lián)合索引用到幾列 |
rows | 預(yù)估掃描行數(shù) | 越大越危險(xiǎn) |
type | 訪問類型 | 判斷查詢效率的關(guān)鍵 |
Extra | 額外信息 | 看排序、臨時(shí)表、覆蓋索引 |
1. possible_keys 和 key 的區(qū)別
possible_keys 表示 MySQL 認(rèn)為可能用得上的索引。
key 表示最終真正用到的索引。
比如:
possible_keys: idx_age key: NULL type: ALL
這說明:
idx_age理論上可用;- 但優(yōu)化器最終沒用;
- 查詢還是全表掃描。
原因可能是數(shù)據(jù)量小、索引區(qū)分度低、回表成本高,或者統(tǒng)計(jì)信息不準(zhǔn)確。
2. key_len 看聯(lián)合索引用得深不深
假設(shè)有聯(lián)合索引:
CREATE INDEX idx_name_age_city ON user(name, age, city);
如果 SQL 是:
SELECT * FROM user WHERE name = 'Tom' AND age = 18;
key_len 可以幫你判斷索引用到了 name,還是用到了 name + age。
它不是越長(zhǎng)越好。
它的價(jià)值是判斷 聯(lián)合索引是否被充分使用。
3. rows 看掃描量大不大
rows 是優(yōu)化器預(yù)估要掃描的行數(shù)。
比如:
type: ALL rows: 1000000
這基本就是危險(xiǎn)信號(hào)。
它說明 MySQL 可能要掃描 100 萬行。
但 rows 是估算值。
如果統(tǒng)計(jì)信息不準(zhǔn),可以執(zhí)行:
ANALYZE TABLE user;
讓 MySQL 重新統(tǒng)計(jì)。
二、重點(diǎn)看 type:掃描方式?jīng)Q定效率
type 表示 MySQL 找數(shù)據(jù)的方式。
常見類型從差到好大致是:
ALL < index < range < ref < eq_ref < const
1. ALL:全表掃描
ALL 是最差的情況。
它表示 MySQL 要掃描整張表。
type: ALL key: NULL
這種情況通常要重點(diǎn)優(yōu)化。
2. index:全索引掃描
index 是全索引掃描。
它比全表掃描稍好一點(diǎn),因?yàn)閽叩氖撬饕龢洹?/p>
但本質(zhì)還是從頭掃到尾。
數(shù)據(jù)量大時(shí)也很慢。
3. range:索引范圍掃描
常見于:
WHERE age > 18 WHERE price BETWEEN 10 AND 80 WHERE id IN (1, 2, 3)
從 range 開始,索引作用就比較明顯了。
4. ref:非唯一索引等值查詢
比如:
SELECT * FROM user WHERE age = 18;
如果 age 是普通索引,可能出現(xiàn):
type: ref
因?yàn)?age = 18 可能匹配多行。
5. eq_ref:唯一索引關(guān)聯(lián)查詢
常見于多表 JOIN。
比如訂單表關(guān)聯(lián)用戶表:
SELECT * FROM orders o JOIN user u ON o.user_id = u.id;
如果 u.id 是主鍵或唯一索引,可能出現(xiàn) eq_ref。
6. const:主鍵或唯一索引查一行
比如:
SELECT * FROM user WHERE id = 1;
id 是主鍵,結(jié)果最多一條。
這種查詢非???。
三、Extra 里三個(gè)高頻信號(hào)
Extra 是執(zhí)行計(jì)劃里的補(bǔ)充信息。
面試?yán)镒畛栠@三個(gè):
Using filesort Using temporary Using index
1. Using filesort:額外排序
Using filesort 表示 MySQL 不能直接利用索引順序完成排序,只能自己額外排序。
注意,filesort 不一定真的寫磁盤文件。
它可能在內(nèi)存里排。
數(shù)據(jù)太大時(shí)才會(huì)落盤。
比如:
SELECT * FROM user ORDER BY age;
如果 age 沒有索引,MySQL 可能會(huì):
掃描數(shù)據(jù) 取出結(jié)果 額外排序 返回結(jié)果
這就可能出現(xiàn) Using filesort。
優(yōu)化方式是讓排序字段走索引:
CREATE INDEX idx_age ON user(age);
2. Using temporary:使用臨時(shí)表
Using temporary 表示 MySQL 創(chuàng)建了內(nèi)部臨時(shí)表保存中間結(jié)果。
常見于:
GROUP BYORDER BYDISTINCTUNION
比如:
SELECT age, COUNT(*) FROM user GROUP BY age;
如果不能利用索引完成分組,MySQL 可能會(huì)把中間結(jié)果放進(jìn)臨時(shí)表。
臨時(shí)表為什么慢?
因?yàn)?MySQL 要額外寫入中間結(jié)果,再讀取、排序或分組。數(shù)據(jù)量大時(shí)還可能落盤。
內(nèi)部臨時(shí)表可以有索引。
但通常由 MySQL 自動(dòng)決定。
你優(yōu)化的方向不是手動(dòng)控制臨時(shí)表,而是讓原 SQL 能直接利用索引完成分組或排序。
3. Using index:覆蓋索引
Using index 是好信號(hào)。
它表示查詢需要的數(shù)據(jù)只從索引里就能拿到,不需要回表。
比如有索引:
CREATE INDEX idx_name_age ON user(name, age);
執(zhí)行:
SELECT age FROM user WHERE name = 'Tom';
name 用于過濾。
age 用于返回。
兩個(gè)字段都在索引里。
這就是覆蓋索引。
四、索引優(yōu)化:不要只會(huì)說“加索引”
索引的本質(zhì)是減少掃描范圍。
但索引不是越多越好。
它會(huì)占空間,也會(huì)降低寫入速度。
1. 哪些字段適合建索引
常見適合建索引的字段:
- 經(jīng)常出現(xiàn)在
WHERE里的字段; - 經(jīng)常用于
ORDER BY的字段; - 經(jīng)常用于
GROUP BY的字段; - 多表 JOIN 的關(guān)聯(lián)字段;
- 區(qū)分度高的字段。
比如:
SELECT * FROM orders WHERE user_id = 1001;
可以考慮:
CREATE INDEX idx_user_id ON orders(user_id);
2. 哪些字段不適合建索引
這些字段不一定適合:
- 區(qū)分度很低的字段,比如性別、狀態(tài);
- 很少用于查詢條件的字段;
- 經(jīng)常更新的字段;
- 小表上的普通字段;
- 大文本字段的完整索引。
比如 gender 只有男、女。
建索引后篩選度很低。
優(yōu)化器可能仍然選擇全表掃描。
3. 聯(lián)合索引要遵循最左匹配原則
假設(shè)有聯(lián)合索引:
CREATE INDEX idx_a_b_c ON t(a, b, c);
可以命中索引的情況:
WHERE a = 1 WHERE a = 1 AND b = 2 WHERE a = 1 AND b = 2 AND c = 3
不太能充分命中的情況:
WHERE b = 2 WHERE c = 3 WHERE b = 2 AND c = 3
因?yàn)槿鄙僮钭筮叺?a。
4. 聯(lián)合索引字段順序怎么排
常見原則:
- 等值查詢字段放前面;
- 區(qū)分度高的字段優(yōu)先;
- 高頻查詢字段優(yōu)先;
- 排序、分組字段盡量接在后面。
比如高頻 SQL 是:
SELECT * FROM orders WHERE user_id = 1001 AND status = 1 ORDER BY create_time DESC;
可以考慮:
CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time);
這樣既能過濾,也能幫助排序。
五、常見索引失效場(chǎng)景
很多慢 SQL 不是沒索引。
而是索引用不上。
1. 左模糊查詢
WHERE name LIKE '%Tom' WHERE name LIKE '%Tom%'
這種通常無法走普通 B+Tree 索引。
因?yàn)樗饕菑淖蟮接矣行虻摹?/p>
如果你不知道開頭是什么,MySQL 很難按索引定位。
可以優(yōu)化成右模糊:
WHERE name LIKE 'Tom%'
2. 對(duì)索引列使用函數(shù)
WHERE DATE(create_time) = '2026-06-08'
這樣會(huì)讓索引列參與函數(shù)計(jì)算。
可以改成范圍查詢:
WHERE create_time >= '2026-06-08 00:00:00' AND create_time < '2026-06-09 00:00:00'
3. 對(duì)索引列做表達(dá)式計(jì)算
WHERE age + 1 = 19
可以改成:
WHERE age = 18
4. 隱式類型轉(zhuǎn)換
如果 phone 是字符串類型,卻這樣寫:
WHERE phone = 13800138000
可能發(fā)生隱式類型轉(zhuǎn)換。
應(yīng)該寫成:
WHERE phone = '13800138000'
5. OR 使用不當(dāng)
WHERE name = 'Tom' OR age = 18
如果 name 有索引,但 age 沒索引,可能導(dǎo)致索引效果變差。
可以考慮給兩個(gè)字段都建合適索引,或者拆成 UNION。
6. 范圍查詢后的字段
聯(lián)合索引中,范圍查詢后面的字段通常不能繼續(xù)用于精確定位。
比如索引:
CREATE INDEX idx_a_b_c ON t(a, b, c);
SQL:
WHERE a = 1 AND b > 10 AND c = 3
這里 a 可以用。
b 可以做范圍掃描。
c 通常不能繼續(xù)用于縮小索引掃描范圍。
六、如果 MySQL 選錯(cuò)索引怎么辦
有時(shí) EXPLAIN 會(huì)發(fā)現(xiàn)優(yōu)化器選了不合適的索引。
不要第一反應(yīng)就強(qiáng)制索引。
先看三個(gè)問題:
- 統(tǒng)計(jì)信息是否不準(zhǔn);
- 索引區(qū)分度是否太低;
- SQL 是否讓索引失效。
可以先更新統(tǒng)計(jì)信息:
ANALYZE TABLE products;
如果確認(rèn)優(yōu)化器確實(shí)選錯(cuò)了,可以使用索引提示。
1. USE INDEX
建議優(yōu)化器考慮某個(gè)索引:
SELECT * FROM products USE INDEX (idx_buyprice) WHERE buyPrice BETWEEN 10 AND 80;
2. FORCE INDEX
強(qiáng)制 MySQL 盡量使用某個(gè)索引:
EXPLAIN SELECT productName, buyPrice FROM products FORCE INDEX (idx_buyprice) WHERE buyPrice BETWEEN 10 AND 80 ORDER BY buyPrice;
如果生效,可能看到:
key: idx_buyprice type: range
3. IGNORE INDEX
讓優(yōu)化器忽略某個(gè)索引:
SELECT * FROM products IGNORE INDEX (idx_name) WHERE buyPrice BETWEEN 10 AND 80;
但這些都屬于干預(yù)手段。
更推薦先從索引設(shè)計(jì)和 SQL 寫法上解決。
七、查詢優(yōu)化:少查、少回表、少 JOIN
1. 避免 SELECT *
SELECT * 的問題很直接。
它會(huì)查出整行數(shù)據(jù)。
帶來四類成本:
- 磁盤 IO 更多;
- 網(wǎng)絡(luò)傳輸更多;
- 應(yīng)用反序列化更多;
- 更容易破壞覆蓋索引。
比如你只需要:
SELECT id, name FROM user WHERE id = 1;
就不要寫:
SELECT * FROM user WHERE id = 1;
2. 盡量使用覆蓋索引
普通二級(jí)索引查詢流程:
查二級(jí)索引 拿到主鍵 id 回表查完整行 返回結(jié)果
覆蓋索引查詢流程:
查二級(jí)索引 直接拿到需要字段 返回結(jié)果
少了回表,自然更快。
3. JOIN 要小結(jié)果集驅(qū)動(dòng)大結(jié)果集
JOIN 可以理解成嵌套循環(huán)。
如果小表有 100 行,大表有 100 萬行。
理想方式是:
先查小結(jié)果集 100 行 每行去大表按索引查
如果反過來:
先掃大表 100 萬行 每行去小表匹配
成本就高很多。
更準(zhǔn)確地說,不是物理小表驅(qū)動(dòng)大表。
而是 過濾后的小結(jié)果集驅(qū)動(dòng)大結(jié)果集。
4. 被驅(qū)動(dòng)表關(guān)聯(lián)字段要有索引
比如:
SELECT * FROM orders o JOIN user u ON o.user_id = u.id;
如果 u.id 是主鍵,匹配很快。
如果被驅(qū)動(dòng)表關(guān)聯(lián)字段沒索引,可能每次匹配都掃描一遍表。
這會(huì)非常慢。
5. 高頻 JOIN 可以考慮冗余字段
比如訂單列表每次都要展示用戶名。
原設(shè)計(jì):
orders(id, user_id, amount) user(id, name)
每次都 JOIN:
SELECT o.id, o.amount, u.name FROM orders o JOIN user u ON o.user_id = u.id;
可以在訂單表冗余:
orders(id, user_id, user_name, amount)
查詢就變成:
SELECT id, amount, user_name FROM orders;
代價(jià)是數(shù)據(jù)冗余和一致性維護(hù)。
適合讀多寫少、字段變化少的場(chǎng)景。
訂單商品名、下單價(jià)格這類歷史快照字段,也很適合冗余。
八、分頁優(yōu)化:重點(diǎn)解決深分頁
普通分頁:
SELECT * FROM tb_sku LIMIT 200000, 10;
它不是直接跳到第 200000 行。
MySQL 通常要先掃描前 200010 行,再丟掉前 200000 行。
這就是深分頁慢的原因。
1. 基于位置分頁
如果主鍵自增,可以改成:
SELECT * FROM tb_sku WHERE id > 200000 ORDER BY id LIMIT 10;
這樣 MySQL 可以從索引位置繼續(xù)往后查。
效率高很多。
但它適合連續(xù)翻頁,不適合隨機(jī)跳頁。
2. 先查 id 再回表
如果必須深分頁,可以先用覆蓋索引查 id:
SELECT id FROM tb_sku ORDER BY create_time LIMIT 200000, 10;
再根據(jù) id 回表查詳情。
也可以寫成 JOIN:
SELECT s.* FROM tb_sku s JOIN ( SELECT id FROM tb_sku ORDER BY create_time LIMIT 200000, 10 ) t ON s.id = t.id;
這樣至少避免一開始就搬運(yùn)完整行數(shù)據(jù)。
九、表結(jié)構(gòu)優(yōu)化:大表和寬表要拆
如果單表數(shù)據(jù)達(dá)到千萬級(jí),查詢壓力可能明顯增加。
但不要一上來就分庫分表。
先看索引和 SQL 是否還能優(yōu)化。
1. 垂直拆分寬表
如果一張表字段很多,可以按訪問頻率拆。
比如:
user(id, name, phone, avatar, description, remark, ext_json)
可以拆成:
user(id, name, phone) user_profile(user_id, avatar, description, remark, ext_json)
高頻字段放主表。
低頻大字段放擴(kuò)展表。
這樣可以減少單行大小,提高緩存命中率。
2. 冷熱數(shù)據(jù)分離
訂單表常見做法是按時(shí)間冷熱分離。
比如:
- 最近 3 個(gè)月訂單放熱表;
- 歷史訂單放冷表;
- 普通查詢只查熱表。
這樣可以降低高頻查詢的數(shù)據(jù)規(guī)模。
3. 分庫分表
當(dāng)單庫或單表成為瓶頸時(shí),再考慮分庫分表。
常見方式:
| 方式 | 解決問題 |
|---|---|
| 垂直分庫 | 按業(yè)務(wù)拆庫,降低單庫壓力 |
| 垂直分表 | 拆寬表,減少單行體積 |
| 水平分庫 | 數(shù)據(jù)分散到多個(gè)庫 |
| 水平分表 | 大表拆成多個(gè)小表 |
分庫分表會(huì)引入復(fù)雜度。
比如:
- 分布式事務(wù);
- 跨庫 JOIN;
- 全局唯一 ID;
- 分頁排序;
- 擴(kuò)容遷移。
所以它是后手,不是起手式。
十、緩存優(yōu)化:讀多寫少優(yōu)先考慮
熱點(diǎn)數(shù)據(jù)可以放 Redis。
緩存的目標(biāo)是減少數(shù)據(jù)庫壓力。
常見讀流程是旁路緩存:
應(yīng)用先查緩存 緩存命中,直接返回 緩存未命中,查詢數(shù)據(jù)庫 數(shù)據(jù)庫返回后,寫入緩存
寫流程常見做法:
先更新數(shù)據(jù)庫 再刪除緩存
為什么不是先刪緩存再更新數(shù)據(jù)庫?
因?yàn)椴l(fā)下可能出現(xiàn)舊數(shù)據(jù)回寫緩存。
更推薦:
先保證數(shù)據(jù)庫成功,再刪除緩存,讓下一次讀取重新加載新數(shù)據(jù)。
緩存也不是銀彈。
要考慮幾個(gè)問題:
1. 緩存穿透
查詢的數(shù)據(jù)根本不存在。
請(qǐng)求每次都打到數(shù)據(jù)庫。
解決方式:
- 緩存空值;
- 使用布隆過濾器。
2. 緩存擊穿
某個(gè)熱點(diǎn) key 過期。
大量請(qǐng)求同時(shí)打到數(shù)據(jù)庫。
解決方式:
- 熱點(diǎn) key 不設(shè)置短過期;
- 加互斥鎖;
- 提前異步刷新。
3. 緩存雪崩
大量 key 同時(shí)過期。
數(shù)據(jù)庫瞬間被打爆。
解決方式:
- 過期時(shí)間加隨機(jī)值;
- 多級(jí)緩存;
- 限流降級(jí)。
十一、架構(gòu)優(yōu)化:讀寫分離和分庫分表
當(dāng) SQL 和索引已經(jīng)優(yōu)化過,但壓力仍然大,就要看架構(gòu)。
1. 主從復(fù)制
MySQL 主從復(fù)制大致流程:
主庫寫入數(shù)據(jù) 主庫記錄 binlog 從庫拉取 binlog 從庫回放日志 從庫得到相同數(shù)據(jù)
2. 讀寫分離
讀寫分離的思路:
寫請(qǐng)求 -> 主庫 讀請(qǐng)求 -> 從庫
這樣可以提高讀并發(fā)。
但要注意主從延遲。
比如用戶剛下單,馬上查訂單。
如果讀從庫,可能查不到。
這類強(qiáng)一致場(chǎng)景可以強(qiáng)制讀主庫。
3. 分庫分表解決什么
分庫主要解決單庫資源瓶頸。
分表主要解決單表數(shù)據(jù)量過大。
簡(jiǎn)單理解:
單庫扛不住 -> 分庫 單表太大 -> 分表 字段太多 -> 垂直拆表 讀壓力大 -> 讀寫分離 + 緩存
十二、慢查詢和鎖等待怎么定位
慢查詢不一定都是索引問題。
有時(shí) SQL 本身不慢,是被鎖卡住了。
面試?yán)锟梢园阉鸪蓛深悾?/p>
執(zhí)行慢:SQL 掃描多、排序多、回表多 等待慢:SQL 在等鎖、等 IO、等連接
1. 開啟慢查詢?nèi)罩?/h3>
慢查詢?nèi)罩居脕碛涗泩?zhí)行時(shí)間超過閾值的 SQL。
常見參數(shù):
SHOW VARIABLES LIKE 'slow_query_log'; SHOW VARIABLES LIKE 'long_query_time';
臨時(shí)開啟可以這樣:
SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1;
long_query_time = 1 表示超過 1 秒的 SQL 會(huì)被記錄。
生產(chǎn)環(huán)境一般要結(jié)合業(yè)務(wù)情況設(shè)置。
不是越小越好。
太小會(huì)產(chǎn)生大量日志。
2. 慢查詢?nèi)罩究词裁?/h3>
慢查詢?nèi)罩纠锍?矗?/p>
Query_time:SQL 總耗時(shí);Lock_time:等待鎖的時(shí)間;Rows_sent:返回行數(shù);Rows_examined:掃描行數(shù);- SQL 原文。
如果 Rows_examined 很大,說明掃描多。
重點(diǎn)查索引和 SQL 寫法。
如果 Lock_time 很大,說明可能被鎖阻塞。
重點(diǎn)查事務(wù)和鎖等待。
3. 用工具分析慢日志
慢查詢?nèi)罩竞芏鄷r(shí),不適合手動(dòng)看。
可以用:
mysqldumpslow -s t -t 10 slow.log
含義是按總耗時(shí)排序,取前 10 條。
也可以用 pt-query-digest。
它能聚合相似 SQL,看總耗時(shí)、平均耗時(shí)、執(zhí)行次數(shù)和掃描行數(shù)。
面試?yán)镎f出這個(gè)工具,會(huì)顯得更接近真實(shí)排查。
4. 當(dāng)前正在執(zhí)行什么 SQL
如果線上突然變慢,可以看當(dāng)前連接:
SHOW FULL PROCESSLIST;
重點(diǎn)看:
Command是否是Query;Time是否很長(zhǎng);State是否在等鎖;Info里具體 SQL 是什么。
常見狀態(tài)包括:
Sending data Waiting for table metadata lock Locked Creating sort index Copying to tmp table
其中 Waiting for table metadata lock 很典型。
它表示在等元數(shù)據(jù)鎖,也就是 MDL 鎖。
5. 什么是 MDL 鎖
MDL 是 metadata lock,元數(shù)據(jù)鎖。
它保護(hù)表結(jié)構(gòu)不被并發(fā)破壞。
比如一個(gè)事務(wù)正在查詢表。
另一個(gè)線程執(zhí)行:
ALTER TABLE user ADD COLUMN age INT;
這個(gè) DDL 可能會(huì)等待 MDL 鎖。
更麻煩的是,后續(xù)新的查詢也可能排隊(duì)。
于是一個(gè) DDL 就拖慢一堆 SQL。
所以線上執(zhí)行 DDL 要特別小心。
6. 查 InnoDB 事務(wù)和鎖等待
可以看 InnoDB 當(dāng)前狀態(tài):
SHOW ENGINE INNODB STATUS\G
重點(diǎn)看:
LATEST DETECTED DEADLOCK:最近死鎖信息;TRANSACTIONS:當(dāng)前事務(wù);- 是否有 lock wait;
- 哪個(gè)事務(wù)持有鎖;
- 哪個(gè)事務(wù)在等待鎖。
MySQL 8.0 也可以查系統(tǒng)表:
SELECT * FROM performance_schema.data_locks; SELECT * FROM performance_schema.data_lock_waits;
這些表可以看到誰持有鎖、誰在等鎖。
7. 常見鎖等待原因
慢查詢里的鎖等待,常見原因有:
- 大事務(wù)長(zhǎng)時(shí)間不提交;
- 更新沒有命中索引,導(dǎo)致鎖范圍變大;
- 間隙鎖導(dǎo)致插入被阻塞;
- DDL 等 MDL 鎖;
- 事務(wù)里夾雜慢接口調(diào)用;
- 批量更新一次改太多數(shù)據(jù)。
比如:
UPDATE user SET status = 1 WHERE phone = '13800138000';
如果 phone 沒索引,MySQL 可能掃描大量記錄。
在事務(wù)隔離級(jí)別和執(zhí)行條件影響下,鎖范圍可能明顯擴(kuò)大。
所以更新、刪除語句的條件字段也要有索引。
8. 死鎖怎么排查
死鎖不是“數(shù)據(jù)庫壞了”。
它是兩個(gè)事務(wù)互相等對(duì)方釋放鎖。
例如:
事務(wù) A:鎖住 id = 1,再等 id = 2 事務(wù) B:鎖住 id = 2,再等 id = 1
就會(huì)形成死鎖。
InnoDB 會(huì)自動(dòng)檢測(cè)死鎖,并回滾其中一個(gè)事務(wù)。
排查方式:
SHOW ENGINE INNODB STATUS\G
看 LATEST DETECTED DEADLOCK。
優(yōu)化方式:
- 固定訪問順序;
- 縮短事務(wù)時(shí)間;
- 減少一次事務(wù)影響的數(shù)據(jù)量;
- 更新條件走索引;
- 避免用戶交互或遠(yuǎn)程調(diào)用放在事務(wù)里。
9. 面試怎么回答慢查詢定位
可以這樣說:
我會(huì)先看慢查詢?nèi)罩荆? 通過 Query_time、Lock_time、Rows_examined 判斷是執(zhí)行慢還是等鎖慢。 如果 Rows_examined 很大, 就用 EXPLAIN 看執(zhí)行計(jì)劃, 重點(diǎn)看 type、key、rows、Extra, 再優(yōu)化索引和 SQL。 如果 Lock_time 很大, 就看 SHOW FULL PROCESSLIST, 確認(rèn)當(dāng)前 SQL 是否在 Waiting for lock 或 metadata lock。 然后用 SHOW ENGINE INNODB STATUS, 或者 performance_schema.data_locks、data_lock_waits, 定位哪個(gè)事務(wù)持有鎖,哪個(gè)事務(wù)在等待。 如果是死鎖, 看 LATEST DETECTED DEADLOCK, 再?gòu)氖聞?wù)訪問順序、索引、事務(wù)范圍幾個(gè)方向優(yōu)化。
慢查詢排查不是只看 SQL,也要看它是不是在等鎖。執(zhí)行慢看 EXPLAIN,等待慢看鎖和事務(wù)。
十三、面試回答模板
如果面試官問:
一張表查詢很慢,你有哪些解決方案?
可以這樣回答:
我會(huì)先用慢查詢?nèi)罩径ㄎ宦?SQL, 再用 EXPLAIN 分析執(zhí)行計(jì)劃。 重點(diǎn)看 type、key、rows、Extra。 如果是全表掃描,就看是否缺索引或索引失效。 如果 rows 很大,就考慮減少掃描范圍。 如果有 Using filesort 或 Using temporary, 就看 ORDER BY、GROUP BY 是否能通過索引優(yōu)化。 然后從幾個(gè)層面處理: 第一,優(yōu)化索引,比如 WHERE、JOIN、ORDER BY、GROUP BY 字段建合適索引, 多個(gè)字段高頻組合查詢就建聯(lián)合索引,并遵循最左匹配原則。 第二,避免索引失效, 比如不要左模糊、不要對(duì)索引列使用函數(shù)或表達(dá)式, 避免隱式類型轉(zhuǎn)換,注意 OR 條件。 第三,優(yōu)化 SQL, 避免 SELECT *,只查必要字段,盡量使用覆蓋索引減少回表。 JOIN 時(shí)讓小結(jié)果集驅(qū)動(dòng)大結(jié)果集, 并保證被驅(qū)動(dòng)表關(guān)聯(lián)字段有索引。 高頻 JOIN 可以通過冗余字段減少關(guān)聯(lián)。 第四,優(yōu)化分頁, 深分頁不要直接 LIMIT 大 offset, 可以改成基于 id 的位置分頁, 或者先查 id 再回表。 第五,優(yōu)化表結(jié)構(gòu), 大表可以考慮冷熱分離、水平拆表, 寬表可以考慮垂直拆分。 但拆表不是第一選擇,要先看 SQL 和索引。 第六,引入緩存, 熱點(diǎn)數(shù)據(jù)可以放 Redis, 讀請(qǐng)求使用旁路緩存, 寫請(qǐng)求可以先更新數(shù)據(jù)庫再刪除緩存, 同時(shí)處理緩存穿透、擊穿和雪崩。 如果單機(jī)數(shù)據(jù)庫壓力仍然大, 再考慮主從復(fù)制、讀寫分離、分庫分表等架構(gòu)方案。
最后可以補(bǔ)一句:
MySQL 調(diào)優(yōu)不是只會(huì)加索引,而是先定位瓶頸,再分層優(yōu)化。核心是減少掃描行數(shù)、減少回表、減少排序和臨時(shí)表,并降低數(shù)據(jù)庫整體壓力。
十四、復(fù)習(xí)速記版
最后給你一版背誦用口訣:
先定位:慢日志 + EXPLAIN 看計(jì)劃:type、key、rows、Extra 查索引:缺不缺、準(zhǔn)不準(zhǔn)、失沒失效 改 SQL:少查列、少回表、少 JOIN 治分頁:避開深 offset 拆結(jié)構(gòu):大表拆、寬表拆、冷熱分 加緩存:熱點(diǎn)進(jìn) Redis 上架構(gòu):主從、讀寫分離、分庫分表
再記住這幾個(gè)判斷:
key 為 NULL -> 沒用索引 rows 很大 -> 掃描多 type = ALL -> 全表掃描 Using filesort -> 額外排序 Using temporary -> 用了臨時(shí)表 Using index -> 覆蓋索引,好事
總結(jié)
到此這篇關(guān)于MySQL性能調(diào)優(yōu)面試復(fù)習(xí)小結(jié)之Explain、索引、慢查詢、緩存和架構(gòu)優(yōu)化的文章就介紹到這了,更多相關(guān)MySQL Explain、索引、慢查詢、緩存和架構(gòu)優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL 5.7 版本的安裝及簡(jiǎn)單使用(圖文教程)
這篇文章主要介紹了MySQL 5.7 版本的安裝及簡(jiǎn)單使用(圖文教程)的相關(guān)資料,這里對(duì)mysql 5.7的安裝及使用和注意事項(xiàng),需要的朋友可以參考下2016-12-12
MySQL數(shù)據(jù)庫查詢性能優(yōu)化策略
這篇文章主要介紹了MySQL數(shù)據(jù)庫查詢性能優(yōu)化的策略,幫助大家的工作學(xué)習(xí)提高M(jìn)ySQL數(shù)據(jù)庫的性能,感興趣的朋友可以了解下2020-08-08
Mysql刪除重復(fù)數(shù)據(jù)并且只保留一條(附實(shí)例!)
最近有朋友打電話尋求一個(gè)SQL相關(guān)的問題,大致是表中存在重復(fù)數(shù)據(jù),需要?jiǎng)h除掉重復(fù)數(shù)據(jù)保留一條的場(chǎng)景,下面這篇文章主要給大家介紹了關(guān)于Mysql刪除重復(fù)數(shù)據(jù)并且只保留一條的相關(guān)資料,需要的朋友可以參考下2023-02-02
PhpMyAdmin 配置文件現(xiàn)在需要一個(gè)短語密碼的解決方法
本文主要介紹PhpMyAdmin 配置文件現(xiàn)在需要一個(gè)短語密碼的解決方法,比較實(shí)用,希望能給大家做一個(gè)參考。2016-06-06
mysql的中文數(shù)據(jù)按拼音排序的2個(gè)方法
這篇文章主要介紹了mysql的中文數(shù)據(jù)按拼音排序的2個(gè)方法,用于一些特殊環(huán)境,需要的朋友可以參考下2014-06-06
Windows 10 與 MySQL 5.5 安裝使用及免安裝使用詳細(xì)教程(圖文)
本文介紹Windows 10環(huán)境下,MySQL 5.5的安裝使用及免安裝使用教程,本文提供了資源下載及相關(guān)問題解決方案,非常不錯(cuò),需要的朋友參考下2017-07-07

