最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MySQL性能調(diào)優(yōu)面試復(fù)習(xí)小結(jié)之Explain、索引、慢查詢、緩存和架構(gòu)優(yōu)化

 更新時(shí)間:2026年07月01日 10:47:48   作者:學(xué)計(jì)算機(jī)的計(jì)算基  
這篇文章主要介紹了MySQL性能調(diào)優(yōu)面試復(fù)習(xí)之Explain、索引、慢查詢、緩存和架構(gòu)優(yōu)化的相關(guān)資料,文中通過代碼從問題執(zhí)行計(jì)劃定位問題開始,到索引優(yōu)化、SQL優(yōu)化、分頁優(yōu)化、表結(jié)構(gòu)優(yōu)化、緩存和架構(gòu)優(yōu)化等多個(gè)方面,需要的朋友可以參考下

前言

這篇文章系統(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 BY
  • ORDER BY
  • DISTINCT
  • UNION

比如:

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è)問題:

  1. 統(tǒng)計(jì)信息是否不準(zhǔn);
  2. 索引區(qū)分度是否太低;
  3. 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)文章

最新評(píng)論

东兴市| 横峰县| 龙里县| 乐山市| 怀宁县| 赤城县| 迁安市| 兴安盟| 乌海市| 肥西县| 滨海县| 重庆市| 平度市| 泽库县| 盐城市| 富宁县| 永济市| 汝城县| 台东市| 讷河市| 广饶县| 西盟| 禹城市| 屏边| 曲沃县| 页游| 淅川县| 喜德县| 江油市| 比如县| 双柏县| 河北省| 永德县| 南投县| 彰化市| 北流市| 太仆寺旗| 闸北区| 长丰县| 沙湾县| 兴仁县|