線上有支查詢慢了。你打開程式碼看了三分鐘,開始猜:是不是資料量變大了?是不是該加索引?是不是要加快取?
這一整套流程從第一步就錯了。你在猜「它為什麼慢」,但你根本還不知道它做了什麼。同一句 SQL,可以走索引只讀三頁,也可以全表掃描讀六十五萬頁——這兩件事在程式碼裡長得一模一樣,在 EXPLAIN 裡差了五個數量級。
執行計畫就是資料庫在動手之前寫下的作戰計畫:打算用哪個索引、打算讀幾列、打算怎麼排序。它是整條診斷鏈的第一站,也是這一整軌反覆會用到的工具。這一章要把它讀懂:
type:存取方式的等級。從 const 到 ALL,這一欄決定了數量級。key 與 key_len:不只是「有沒有用索引」,而是複合索引用到了第幾個欄位。rows 與 filtered:這兩個數字是估算,不是實測。知道它從哪來,才知道它什麼時候會騙你。Extra:新手最容易略過、但資訊密度最高的一欄。Using filesort、Using temporary 都在這裡。EXPLAIN ANALYZE:MySQL 8.0.18 之後,你終於可以看到實際跑了多久、實際讀了幾列。這一章以 MySQL 8.0 / InnoDB 為準。索引的結構(B+Tree、聚簇索引、回表、最左前綴)在 Java 後端底層 ch01 已經講過,這裡假設你知道那些,專心處理「怎麼看出它到底有沒有照你想的方式走」。
這一軌有三條會反覆出現的主線,第一條從這章就開始:優化器看的是估算,你該看的是實測。
線上查詢變慢的時候,最常見的處理方式是這樣的:看程式碼 → 覺得某個條件沒索引 → 加索引 → 上線 → 有時候好了,有時候沒好。
「有時候沒好」才是重點。因為這個流程裡沒有任何一步是在確認事實,全部都是推測。
你寫的是「要什麼」,資料庫決定的是「怎麼拿」。一句 SELECT ... WHERE a = ? AND b = ?,優化器至少有這些選擇:
idx_a,找到候選列之後回表逐列檢查 bidx_b,反過來做這幾種跑法的成本可以差上萬倍。而你在程式碼裡看不出它選了哪一種。
後面每一章都會遇到同一個誘惑——聽到某個症狀,直接跳到某個解法(慢就加索引、鎖就降隔離等級、CPU 高就調參數)。這一軌會反覆把你拉回同一個順序:
症狀 → 執行計畫(它打算做什麼)
→ 實測(它實際做了什麼)
→ 根因(為什麼會這樣做)
→ 才輪到改法執行計畫是這條鏈的第一站。這章之後的每一章——優化器選錯索引、JOIN 變慢、深分頁、鎖等待、ORM 發出的 SQL——都會回到 EXPLAIN 這張表上找證據。
EXPLAIN SELECT ...; -- 看計畫,不執行
EXPLAIN ANALYZE SELECT ...; -- 真的跑一次,計畫 + 實測(8.0.18+)
EXPLAIN FOR CONNECTION 12345; -- 看「正在跑」的那句在做什麼最後一個在線上救火時特別有用:SHOW PROCESSLIST 看到某個連線卡了 40 秒,直接用它的連線 id 看那句 SQL 當下的計畫,不必自己重現。
不要把 EXPLAIN 當成一張要背的表。每個欄位其實都在回答一個很具體的問題,照著問題記就不會忘。
mysql> EXPLAIN SELECT o.id, o.amount, u.name
-> FROM orders o JOIN users u ON u.id = o.user_id
-> WHERE o.user_id = 42 AND o.status = 1
-> ORDER BY o.created_at DESC LIMIT 20;
+--+-----------+-----+------+-----------------+---------+-------+-------------+----+--------+-----------+
|id|select_type|table|type |possible_keys |key |key_len|ref |rows|filtered|Extra |
+--+-----------+-----+------+-----------------+---------+-------+-------------+----+--------+-----------+
| 1|SIMPLE |o |ref |idx_uid_status,..|idx_uid_s| 9|const,const | 214| 100.00|Using where|
| 1|SIMPLE |u |eq_ref|PRIMARY |PRIMARY | 8|shop.o.user_i| 1| 100.00|NULL |
+--+-----------+-----+------+-----------------+---------+-------+-------------+----+--------+-----------+id:誰先誰後?數字大的先執行;數字相同的由上而下。看巢狀子查詢時最有用。select_type:這一列是哪種查詢?SIMPLE(無子查詢)、PRIMARY(最外層)、SUBQUERY、DERIVED(衍生表)、UNION。看到 DEPENDENT SUBQUERY 要警覺——那代表子查詢會被外層每一列驅動一次。table:這一步在讀哪張表?可能是 <derived2> 這種衍生表代號。partitions:命中哪些分區(沒分區就是 NULL)。type:用什麼方式存取這張表?——數量級由它決定,下一節專講。possible_keys:優化器「考慮過」哪些索引?注意是考慮過,不是用了。key:最後真的用了哪個?possible_keys 有東西但 key 是 NULL——代表優化器評估後認為走索引更貴,這是 ch02 的主題。key_len:索引用到了第幾個欄位?複合索引的關鍵指標,第四節專講。ref:索引是拿什麼去比對的?const(常數)、另一張表的欄位、或 func(比對前經過運算——常常是壞消息)。rows:估計要掃幾列?估計,不是實際。filtered:掃出來之後,估計有幾成會留下?單位是百分比。Extra:還做了哪些額外的事?資訊密度最高的一欄,第六節專講。possible_keys 有值就以為「有走索引」——要看 key;②看到 key 有值就放心——複合索引可能只用到第一個欄位,要看 key_len。EXPLAIN FORMAT=JSON SELECT ...; -- 多出 cost 數字,看得到優化器算的成本
EXPLAIN FORMAT=TREE SELECT ...; -- 樹狀,執行順序一目了然(8.0.16+)表格式看單表最快;JOIN 一多、巢狀一深,FORMAT=TREE 比表格好讀非常多,因為表格式把樹狀結構壓成了平面。
如果只能看一個欄位,看 type。它描述「用什麼方式存取這張表」,而不同方式之間的差距是數量級的。
system / const:最多一列。用主鍵或唯一索引配常數,例如 WHERE id = 42。優化器甚至會在準備階段就把它讀出來當常數用。eq_ref:JOIN 時,被驅動表用主鍵/唯一索引比對,每次比對最多一列。JOIN 能拿到的最好結果。ref:用非唯一索引比對,每次可能回傳多列。日常查詢最常見的健康狀態。range:索引上的範圍掃描——BETWEEN、>、IN (...)。健康,但要注意範圍多寬。index:掃「整棵索引樹」。名字看起來像好事,其實是全掃,只是掃的是索引不是資料表。搭配 Using index(覆蓋索引)時還可以接受,因為索引比資料表小很多;沒有覆蓋還要回表的話,往往比 ALL 更慘。ALL:全表掃描。小表無所謂,大表就是災難。range,JOIN 的被驅動表至少要 ref。看到 ALL 出現在大表上,先不要問「要不要加索引」,先問「為什麼它不用現有的索引」。這是最反直覺的一點,值得單獨推導一次。假設一張 500 萬列的表,走 type=index 掃 idx_status:
掃索引樹葉節點:讀約 8,000 頁(索引小,很快)
每命中一列 → 拿主鍵回表 → 一次隨機 I/O
如果命中 200 萬列 → 200 萬次隨機 I/O而全表掃描是順序讀 65,000 頁。順序 I/O 對隨機 I/O 的差距,加上 InnoDB 的預讀(read ahead),結果就是:當命中比例夠高時,全表掃描真的比較快。
優化器知道這件事,所以它有時候會「明明有索引卻不用」。那通常不是它壞掉,是它在算帳。它算錯的情形留到 ch02。
index_merge:同時用了兩個以上的索引再合併結果。看到它通常代表該建一個複合索引了。ref_or_null:條件是 col = ? OR col IS NULL,比 ref 多一段掃描。NULL:連表都不用讀。例如 SELECT MIN(id) FROM t 直接從索引取得,或 WHERE 1 = 0 被判定為不可能。key 有值只代表「有用到這個索引」,不代表「用好了」。複合索引 (user_id, status, created_at) 只用到第一個欄位,和三個都用到,效果差很多——而兩者的 key 欄位長得一模一樣。
唯一能分辨的是 key_len:它是這次查詢實際用到的索引前綴,總共佔幾個 byte。
TINYINT 1、INT 4、BIGINT 8、DATETIME 5、DATE 3NULL 的欄位:再加 1 個 byte(記錄是否為 NULL)VARCHAR(n) 在 utf8mb4 下:n × 4(每字元最多 4 bytes)CREATE TABLE orders (
id BIGINT NOT NULL AUTO_INCREMENT,
user_id BIGINT NOT NULL, -- 8
status TINYINT NOT NULL, -- 1
created_at DATETIME NOT NULL, -- 5
memo VARCHAR(50) NULL, -- 50*4 + 2 + 1 = 203
PRIMARY KEY (id),
KEY idx_u_s_c (user_id, status, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;| 查詢條件 | key_len | 意思 |
|---|---|---|
| user_id = 42 | 8 | 只用到第 1 個欄位 |
| user_id = 42 AND status = 1 | 9 | 用到前 2 個 |
| user_id = 42 AND status = 1 AND created_at > ? | 14 | 三個都用到 |
| user_id = 42 AND created_at > ? | 8 | 只用到第 1 個——中間跳過 status,後面就接不上了 |
key 照樣顯示 idx_u_s_c,看起來一切正常——只有 key_len 誠實地告訴你,created_at 那個條件根本沒進到索引裡,是回表之後逐列比對出來的。ref 欄位告訴你「拿什麼去比對索引」:
const:拿常數比對——最單純的情況shop.o.user_id:拿另一張表的欄位比對——JOIN 的正常狀態func:比對前先做了運算——例如型別轉換或函數呼叫。這是一個要停下來看的訊號,下一節與第八節都會再遇到它。還有一個規則值得記:索引在遇到第一個範圍條件之後就停止繼續縮小範圍了。
-- idx_u_s_c (user_id, status, created_at)
WHERE user_id = 42 AND status > 1 AND created_at = '2026-08-13'
-- key_len = 9:user_id 等值 + status 範圍,到此為止
-- created_at 只能在回表後逐列過濾(Extra 會出現 Using where 或 index condition)所以複合索引的欄位順序有一條實務規則:等值條件放前面,範圍條件放最後。這也是為什麼同樣三個欄位,順序換一下效能可以差幾十倍。
rows 是這一軌第一條主線的核心:它是估算,不是事實。知道這個數字怎麼來的,才知道它什麼時候會騙你。
InnoDB 不會為了回答「這個條件有幾列」去掃一次表——那樣 EXPLAIN 就跟真的執行一樣貴了。它看的是統計資訊:
innodb_stats_persistent_sample_pages 決定,預設只有 20 頁SHOW TABLE STATUS 的 Rows 和 COUNT(*) 常常對不上,這是正常的)filtered 是百分比:從儲存引擎撈上來的列,估計有幾成能通過 server 層剩下的條件。真正重要的是這個乘積:
實際往上一層傳的列數 ≈ rows × filtered / 100在單表查詢裡它只是參考值;在 JOIN 裡它決定一切——因為這個乘積就是「被驅動表要被查幾次」。rows=10000, filtered=1.00 代表優化器認為只會有 100 列進到下一步;如果實際是 10000 列,被驅動表就會被多查 99 倍次數。這正是 ch03 那個「測試環境 20ms、正式環境 40 秒」的來源。
最快的方法是拿它跟真實數字對照:
-- 優化器怎麼估
EXPLAIN SELECT * FROM orders WHERE status = 1 AND created_at > '2026-08-01';
-- rows = 214, filtered = 11.11
-- 實際是多少
SELECT COUNT(*) FROM orders WHERE status = 1 AND created_at > '2026-08-01';
-- 1,204,338差一個數量級以上,就別再看它的計畫是否合理了——先去修統計資訊(ANALYZE TABLE,見 ch02)。計畫是根據錯的數字算出來的,再怎麼讀都是讀一份錯誤推論。
rows 不是「這句查詢會回傳幾列」,是「這一步預計要檢查幾列」。一句 LIMIT 10 的查詢,rows 顯示 200 萬是完全可能的——它要掃 200 萬列才能找出那 10 列。會慢的正是這種:回傳的資料很少,但代價很大。只看回傳結果的大小永遠發現不了。
Extra 是最容易被略過的一欄,因為它排在最右邊、內容是英文短句、而且常常是 NULL。但一句查詢「慢在哪裡」的答案,多半就寫在這裡。
Using index:覆蓋索引——要的欄位索引裡都有,不必回表。看到它就是賺到。Using index condition:索引條件下推(ICP)——把原本要回表後才能判斷的條件,下推到索引掃描階段先過濾,減少回表次數。是好事。Using index for group-by:GROUP BY 直接用索引完成,不用排序也不用暫存表。Using where:儲存引擎撈上來之後,server 層還要再過濾一次。單獨看不代表壞——但如果同時 type=ALL,意思就是「全表撈上來再逐列篩」,那就是最糟的組合。Using filesort:結果需要額外排序,因為 ORDER BY 的順序沒辦法直接由索引提供。名字裡有 file,但不一定寫檔案——資料量小的時候在 sort_buffer_size 裡排完就好;超過就會落到磁碟,那才是災難。(兩種排序演算法、以及怎麼用索引避開,是 ch04 的主題。)Using temporary:建了暫存表,常見於 GROUP BY 的欄位跟排序欄位不同、DISTINCT 加 ORDER BY、UNION。暫存表可能在記憶體,也可能落到磁碟。Using join buffer (hash join):被驅動表沒有可用的索引,只能把驅動表的資料放進 buffer 做 hash join。8.0.18 之後的 hash join 比舊的 Block Nested Loop 快很多,但它出現本身就是一個訊號:那張表少了一個索引。(ch03 主講。)Using temporary; Using filesort 同時出現,而且 rows 很大。那代表資料庫要先把一大堆列撈出來、堆進暫存表、再整批排序——三件貴事一次做完。後台的統計報表頁面十之八九慢在這裡。優化器會重寫你的 SQL(子查詢改寫成 JOIN、消除恆真條件、加上隱式轉換)。EXPLAIN 之後緊接著下這一句,可以看到它眼中的版本:
EXPLAIN SELECT * FROM members WHERE phone = 912345678;
SHOW WARNINGS;
/* select#1 ... where (cast(`shop`.`members`.`phone` as double) = 912345678) */那個 cast(...) 就是索引失效的兇手——而你寫的 SQL 裡完全看不到它。這一招是第八節那個真實案例的破案關鍵,記起來。
EXPLAIN 給的是計畫與估算。MySQL 8.0.18 之後,EXPLAIN ANALYZE 會真的執行這句 SQL,然後把「估算」和「實際」並排寫給你看。這是這一軌第一條主線最直接的工具。
EXPLAIN ANALYZE
SELECT o.id, u.name FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 1 AND o.created_at > '2026-08-01';
-> Nested loop inner join
(cost=1250 rows=214) (actual time=0.09..8412 rows=1204338 loops=1)
-> Filter: (o.status = 1)
(cost=980 rows=214) (actual time=0.06..2140 rows=1204338 loops=1)
-> Table scan on o (cost=980 rows=2000000) (actual time=0.05..1802 rows=2000000 loops=1)
-> Single-row index lookup on u using PRIMARY (id=o.user_id)
(cost=1.2 rows=1) (actual time=0.004..0.004 rows=1 loops=1204338)cost= / rows=:優化器的估算。actual time=A..B:實測。A 是拿到第一列的時間,B 是拿完全部的時間,單位毫秒。loops=N:這個節點被執行了幾次。actual time 和 rows 都是「每一次 loop 的平均值」,不是總計。上面最後一行看起來只花 0.004 毫秒,但 loops=1204338——真正的成本是 0.004 × 1204338 ≈ 4.8 秒。這種「單次很快、次數爆炸」的節點,是 JOIN 與 ORM N+1 慢查詢最典型的長相。把上面那份輸出的兩組數字對起來看:
估算 rows=214 實際 rows=1,204,338 → 差了 5,600 倍優化器是因為以為只有 214 列,才選擇了 Nested Loop(外層少少幾列,內層查幾次無所謂)。統計資訊一錯,計畫就跟著錯,而計畫錯的代價是 8.4 秒。這就是「優化器看估算、你看實測」的具體長相。
EXPLAIN ANALYZE,就是真的再壓一次資料庫。SELECT;UPDATE/DELETE 只能用一般的 EXPLAIN(或先把它改寫成等價的 SELECT 來看)。SET SESSION max_execution_time = 5000;(單位毫秒)當保險,免得手一滑把庫壓垮。ORM 發出去的 SQL 跟你想的往往不一樣(ch08 專講)。要先看到它:
spring:
jpa:
properties:
hibernate.format_sql: true
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.orm.jdbc.bind: TRACE # 看得到綁定的參數值參數值很重要——不同的參數值可以走出完全不同的計畫(範圍寬窄不同、型別不同)。拿沒有參數的 SQL 去 EXPLAIN,等於分析了一句線上根本沒跑過的查詢。
會員查詢 API 上線兩年都很正常。某次改版之後,監控出現一件怪事:同一支 API,大部分請求 2 毫秒,但每天有幾百次跑到 8 秒以上。慢的那些請求沒有規律——不是尖峰時段、不是特定會員、重試一次又正常了。DBA 撈慢查詢日誌,看到的是這句:
SELECT * FROM members WHERE phone = 912345678;而 members.phone 上明明有索引 idx_phone,表有 280 萬列。
把兩種呼叫方式各 EXPLAIN 一次,差異立刻現形:
EXPLAIN SELECT * FROM members WHERE phone = '0912345678';
-- type=ref key=idx_phone key_len=82 ref=const rows=1 Extra=NULL
EXPLAIN SELECT * FROM members WHERE phone = 912345678;
-- type=ALL key=NULL key_len=NULL ref=NULL rows=2871043 Extra=Using where再用上一節那招問它為什麼:
SHOW WARNINGS;
/* ... where (cast(`shop`.`members`.`phone` as double) = 912345678) */兇手是隱式型別轉換。推導一次為什麼會這樣:
phone 是 VARCHAR(20),但傳進來的參數是數字。'0912345678' 的順序,不是 cast(...) 之後的順序,B+Tree 無法用來定位。'0912345678' 轉成數字之後是 912345678——前導的 0 不見了。所以這句查詢會同時撈到 '0912345678' 和 '912345678' 兩筆不同的資料。它不只是慢,它會回傳錯的人。這種 bug 不會拋例外、不會進錯誤日誌,只會在某天變成一張客訴單。回到 Java 這邊,來源是這種寫法:
@Query(value = "SELECT * FROM members WHERE phone = ?1", nativeQuery = true)
Optional<Member> findByPhoneRaw(long phone); // ← 型別寫成了 long新的內部管理後台呼叫了這個方法(參數從 JSON 的數字欄位進來,被綁成 Long),而原本的 App 端走的是另一個型別正確的方法。兩條路徑打同一張表、同一個條件、看起來同一句 SQL——只有型別不同。
String,與欄位型別對齊。查詢立刻回到 type=ref、2 毫秒。phone 改成 BIGINT——電話號碼有前導 0、有分機、有 +886,那是把一個型別錯誤換成一個資料模型錯誤。nativeQuery 的參數型別做 code review 檢查點;在整合測試裡對關鍵查詢斷言 EXPLAIN 的 type 不等於 ALL——執行計畫是可以被自動化測試的。只要索引欄位被包住,症狀都一樣:
-- ❌ 函數包住欄位
WHERE DATE(created_at) = '2026-08-13'
-- ✅ 改成範圍,索引可用
WHERE created_at >= '2026-08-13 00:00:00' AND created_at < '2026-08-14 00:00:00'
-- ❌ 欄位上做運算
WHERE amount * 100 > 5000
-- ✅ 把運算移到常數那邊
WHERE amount > 50還有一種特別難找的:JOIN 兩張表的字元集或 collation 不同(例如舊表是 utf8mb3、新表是 utf8mb4)。MySQL 會對其中一邊做隱式轉換,被驅動表的索引就此失效,Extra 出現 Using join buffer。表面上兩邊都有索引,怎麼看都沒問題。
EXPLAIN 看到 ref=func,或 SHOW WARNINGS 看到 cast(,就是有東西把你的欄位包起來了。執行計畫是診斷的第一站,不是最後一站。知道它不能回答什麼,跟知道它能回答什麼一樣重要——不然你會盯著一張很漂亮的計畫,找不到那 8 秒去了哪裡。
type=const、只讀一列的 UPDATE,可以卡 40 秒——它在等另一個交易放開行鎖。計畫上完美無缺。(ch05 主講。)rows 一模一樣。(ch07 主講。)type=const、rows=1,漂亮得不得了——只是它跑了 200 次。單句計畫再完美,也照不出批次層的災難。(ch08 主講。)SELECT * 撈了 200 個欄位、其中三個是 TEXT,資料庫端可能只花 5 毫秒,網路傳輸與 ORM 映射花掉 2 秒。EXPLAIN 不執行查詢,但不是完全免費:它要做語法解析、要向 InnoDB 要統計資訊,含衍生表的複雜查詢在某些情況下還會物化子查詢。日常使用可以忽略,但別寫進迴圈裡當監控。EXPLAIN ANALYZE 是真的執行——正式環境使用前先設 max_execution_time。WHERE status = 1 測出來的結論,套不到 status = 9 上(前者可能佔 60% 資料,後者只有 0.01%)。要用線上真正慢的那組參數去測。有兩種情況,一上來就 EXPLAIN 是浪費時間:
type/key_len/Extra 描述的是結構,基本可信;rows/filtered 是估算,要拿實測對照;而鎖、快取、呼叫次數則完全不在它的視野裡。下一章要處理的正是中間那一層:當優化器的估算錯了,它會選錯索引——而且是在某個你毫無察覺的時間點突然選錯的。
| type | 存取方式 | 大概讀多少 | 看到它該做什麼 |
|---|---|---|---|
| const / system | 主鍵或唯一索引 = 常數 | 1 列 | 最理想,不必動 |
| eq_ref | JOIN 時被驅動表走主鍵/唯一索引 | 每次比對 1 列 | JOIN 能拿到的最好結果 |
| ref | 非唯一索引等值比對 | 每次比對數列 | 日常查詢的健康狀態 |
| range | 索引上的範圍掃描(BETWEEN/>/IN) | 看範圍多寬 | 可接受,但要確認範圍沒有寬到失去意義 |
| index_merge | 同時走兩個以上索引再合併 | 數個索引的命中總和 | 訊號:該建一個複合索引了 |
| index | 掃描整棵索引樹 | 整個索引 | 有 Using index(覆蓋)還行;要回表的話常比 ALL 更慘 |
| ALL | 全表掃描 | 整張表 | 大表上出現=先問「為什麼不用現有索引」,不是急著加新索引 |
| Extra 內容 | 意思 | 好壞 | 怎麼處理 |
|---|---|---|---|
| Using index | 覆蓋索引,不必回表 | ✅ 好 | 不用動;反過來說,這是優化的目標狀態 |
| Using index condition | 索引條件下推(ICP),提早過濾減少回表 | ✅ 好 | 不用動 |
| Using index for group-by | GROUP BY 直接用索引完成 | ✅ 好 | 不用動 |
| Using where | 引擎撈上來後 server 層再過濾 | ⚠ 看情況 | 配 ref/range 沒事;配 ALL=全表撈上來逐列篩,要處理 |
| Using filesort | 需要額外排序,索引提供不了順序 | ⚠ 通常壞 | 調索引欄位順序讓 ORDER BY 走索引(ch04) |
| Using temporary | 建立暫存表(GROUP BY/DISTINCT/UNION) | ❌ 壞 | 先確認 GROUP BY 與 ORDER BY 是否可以對齊(ch04) |
| Using join buffer (hash join) | 被驅動表沒有可用索引 | ❌ 壞 | 在被驅動表的 JOIN 欄位上補索引(ch03) |
| Using temporary; Using filesort | 先堆暫存表再整批排序 | ❌ 最該警覺 | 配上大 rows=後台報表頁最典型的慢法 |
| 工具 | 回答的問題 | 會不會執行 | 什麼時候用 |
|---|---|---|---|
| EXPLAIN | 它打算怎麼做(索引、順序、額外動作) | 不執行 | 第一站;任何單句慢查詢都從這裡開始 |
| EXPLAIN ANALYZE | 實際跑了多久、實際幾列、估算差多少 | 真的執行 | 計畫看起來合理但還是慢的時候;正式環境記得配 max_execution_time |
| EXPLAIN FOR CONNECTION | 現在卡住的那句在做什麼 | 不執行 | 線上救火,配 SHOW PROCESSLIST 用 |
| 慢查詢日誌 / sys schema | 哪些句子慢、慢幾次、總共吃掉多少時間 | 被動記錄 | 還不知道要查哪一句的時候(ch07 主講) |
點擊卡片翻面查看答案,共 13 張。