MySQL 效能調校實戰 2026:EXPLAIN、索引優化與查詢提速

数据库

為什麼 2026 年還要死磕 MySQL 調校

儘管 NewSQL 與向量資料庫興起,MySQL 仍承載著全球絕大多數 OLTP 流量。多數「慢」問題並非硬體不足,而是索引缺失、執行計畫走偏、或連線管理混亂。本文聚焦可落地的調校動作。

症狀 常見根因
偶發卡頓 缺少索引,全表掃描
越用越慢 緩衝池過小,磁碟 IO 飆升
高峰雪崩 連線數失控,執行緒爭用
單條 SQL 逾時 深分頁 / 不合理 JOIN

第一步:用 EXPLAIN 看清執行計畫

任何調校都從 EXPLAIN 開始。重點關注 typekeyrowsExtra

EXPLAIN SELECT id, user_id, amount
FROM orders
WHERE user_id = 10086 AND status = 'PAID'
ORDER BY created_at DESC
LIMIT 20;

關鍵欄位判讀:

欄位 健康值 危險信號
type ref / range / const ALL(全表掃描)
key 命中預期索引 NULL
rows 接近實際命中行 遠大於預期
Extra Using index Using filesort / Using temporary

拿到複雜 SQL 先到 SQL 格式化 工具裡美化,便於定位巢狀與 JOIN 順序。


索引設計的三個核心法則

法則一:聯合索引遵循最左前綴

索引 (user_id, status, created_at) 能命中 user_id=,也能命中 user_id+status,但無法單獨命中 status

-- 命中索引
SELECT * FROM orders WHERE user_id = 1 AND status = 'PAID';

-- 用不上該聯合索引的 status 部分(索引跳躍掃描除外)
SELECT * FROM orders WHERE status = 'PAID';

法則二:優先覆蓋索引(Covering Index)

如果查詢欄位全部在索引裡,MySQL 無需回表,效能飛躍。

-- 建立覆蓋索引
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, created_at);

-- Using index:直接從索引返回,不回表
EXPLAIN SELECT user_id, status, created_at
FROM orders WHERE user_id = 10086;

法則三:區分度高的列放前面

user_id(區分度高)放聯合索引前列,把低區分度(如 status 僅有幾種值)靠後。


慢查詢日誌:讓問題自己現形

-- 開啟慢查詢日誌(超過 200ms 記錄)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.2;
SET GLOBAL log_queries_not_using_indexes = 'ON';

配合 pt-query-digest 或內建 sys.statement_analysis 視圖定位 Top SQL。


InnoDB 緩衝池調校

緩衝池(buffer pool)是 InnoDB 的效能心臟,目標是讓熱點資料常駐記憶體。

-- 查看緩衝池命中率(應 > 99%)
SHOW ENGINE INNODB STATUS\G
-- Buffer pool hit rate 幾乎接近 1000/1000 為佳

-- 生產建議:buffer pool 設為可用記憶體的 60%~75%
-- 在 my.cnf 中:
-- innodb_buffer_pool_size = 12G
-- innodb_buffer_pool_instances = 8   -- 減少多核爭用

查詢層優化

避免深分頁的「LIMIT 100000, 20」

深分頁會讓 MySQL 先掃描 10 萬行再丟棄。用游標/延遲關聯改寫:

-- 反模式
SELECT * FROM orders ORDER BY id LIMIT 100000, 20;

-- 優化:先取主鍵,再 JOIN 回表
SELECT o.* FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 20) t ON o.id = t.id;

用 EXISTS 替代 IN 大列表

-- 當子查詢結果集很大時
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.amount > 1000);

只查需要的列

SELECT * 不僅浪費 IO,還會讓覆蓋索引失效。明確列名是性價比最高的優化。


JOIN 優化要點

  • 驅動表選小表,被驅動表連接列必須有索引。
  • 關聯欄位型別必須一致(避免隱式轉換導致索引失效)。
  • 大表 JOIN 考慮冗餘維度欄位,減少跨表。

連線池:別讓連線成為瓶頸

應用側連線管理混亂是高峰雪崩的主因。以 Node.js 為例:

import mysql from "mysql2/promise";

const pool = mysql.createPool({
  host: "db.example.com",
  user: "app",
  database: "shop",
  connectionLimit: 20,      // 控制並發連線
  waitForConnections: true,
  queueLimit: 50,           // 超出排隊的請求上限
  enableKeepAlive: true
});

// 永遠用 try/finally 釋放連線,避免洩漏
const [rows] = await pool.query("SELECT * FROM orders WHERE user_id = ?", [uid]);

Hash 計算 工具可以產生快取鍵,將熱點查詢結果快取,減輕資料庫壓力。


常見問題 FAQ

Q1:加了索引為什麼還是慢?

可能:索引選擇性差、統計資訊過期(ANALYZE TABLE)、或查詢沒走最左前綴。用 EXPLAIN 驗證實際命中的 key

Q2:索引越多越好嗎?

不是。索引拖慢寫入、佔用空間。遵循「讀多寫少的热点查詢才建索引」原則。

Q3:buffer pool 越大越好嗎?

不是無腦大。超過實體記憶體會觸發 swap,反而更慢。一般設為可用記憶體 60%~75%。

Q4:VARCHAR 索引要注意什麼?

長字串建索引用前綴索引:INDEX(col(20)),但前綴索引無法用於覆蓋掃描的精確匹配場景,需權衡。

Q5:怎麼判斷該不該分表?

單表行數上億、或熱點行鎖競爭嚴重、或備份視窗過長時,再考慮分庫分表。優先索引與查詢優化。


工具推薦

在 MySQL 調校實務中,以下 工具庫 能幫上忙:


MySQL 調校沒有銀彈,但有方法論:先用 EXPLAIN 找到瓶頸,再用索引與緩衝池解決 IO,最後用連線池穩住高峰。把這三層做對,絕大多數慢查詢都會消失。

本站提供瀏覽器本地工具,免註冊即可試用 →

#MySQL#性能调优#索引#EXPLAIN#InnoDB#数据库