MySQL 效能調校完整指南:從慢查詢、EXPLAIN、索引到伺服器最佳化

MySQL 資料庫剛建立時,資料量可能只有幾百筆、幾千筆,即使 SQL 寫得不是很好,使用者通常也感覺不到明顯的效能問題。

但當資料成長到幾十萬、幾百萬甚至更多筆之後,相同的 SQL 查詢可能從原本不到 0.1 秒,逐漸變成數秒甚至數十秒。

這時候就需要進行 MySQL 效能調校(MySQL Performance Tuning)

所謂效能調校,並不是看到資料庫慢了就增加記憶體,也不是看到 SQL 慢了就到處建立索引。

比較合理的流程是:發現問題 → 找出慢查詢 → 分析執行計畫 → 判斷瓶頸 → 修改 SQL 或索引 → 再次測試。


MySQL 為什麼會變慢?

造成 MySQL 效能下降的原因很多,大致可以分成幾個層次:

  • SQL 查詢設計不佳
  • 缺少適當索引
  • 索引設計錯誤
  • JOIN 或子查詢處理大量資料
  • 資料表結構設計不佳
  • 一次讀取過多不必要的資料
  • Transaction 或 Lock 造成等待
  • 記憶體不足
  • Disk I/O 成為瓶頸
  • MySQL Server 參數配置不適合目前工作負載

因此,MySQL 很慢,不代表問題一定出在 MySQL Server。很多時候真正的問題其實只是某一條 SQL。


第一步:先找到哪一條 SQL 很慢

資料庫效能最佳化最忌諱的做法,就是還沒有找到問題,就開始修改設定。

第一步應該先確認:到底是哪一條 SQL 慢?

MySQL 可以利用 慢查詢紀錄(Slow Query Log) 找出執行時間較長的 SQL。

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

實際正式環境不應只看「超過幾秒」,還要一起考慮 SQL 的執行頻率。

SQL A:
一次執行 3 秒
每天執行 2 次

SQL B:
一次執行 0.2 秒
每天執行 100,000 次

從整體系統負擔來看,SQL B 反而可能更值得優先最佳化。

因此效能分析至少要同時觀察:

  • 執行時間
  • 執行次數
  • 掃描資料列數
  • 回傳資料列數
  • CPU 使用量
  • I/O
  • Lock Wait


第二步:使用 EXPLAIN 分析 SQL

找到可疑 SQL 之後,下一步通常不是立刻修改 SQL,而是使用 EXPLAIN。

EXPLAIN
SELECT *
FROM orders
WHERE customer_id = 1001;

EXPLAIN 可以讓我們看到 MySQL Optimizer(查詢最佳化器)準備如何執行這條 SQL。

分析時可以特別注意:

  • type
  • possible_keys
  • key
  • rows
  • filtered
  • Extra

例如:

type = ALL
key = NULL
rows = 500000

通常表示 MySQL 可能需要掃描大量資料。

但看到 ALL 並不能直接下結論說「一定要建立索引」,還要一起考慮資料量、資料分布、WHERE 條件、選擇性(Selectivity)以及查詢目的。


第三步:理解 Index 索引

索引(Index)是 MySQL 效能調校中最重要的工具之一。

假設有一張 customers :

CREATE TABLE customers (
    customer_id INT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(255),
    city VARCHAR(50)
);

現在執行:

SELECT *
FROM customers
WHERE email = '[email protected]';

如果 email 沒有 Index,資料量很大時,MySQL 可能必須掃描大量資料。

建立:

CREATE INDEX idx_email
ON customers(email);

就有機會讓 MySQL 更快速定位符合條件的紀錄。

但是,Index 並不是越多越好。因為 Index 本身也需要儲存空間,並且在 INSERT、UPDATE、DELETE 時持續維護。


哪些欄位比較需要 Index?

WHERE 條件

SELECT *
FROM orders
WHERE customer_id = 100;

customer_id 就值得評估是否建立 Index。

JOIN 條件

SELECT *
FROM orders o
JOIN customers c
ON o.customer_id = c.customer_id;

JOIN 使用的欄位通常是重要的索引候選。

ORDER BY

SELECT *
FROM orders
ORDER BY order_date;

某些情況下,適當 Index 可以降低排序成本。

GROUP BY

SELECT customer_id, COUNT(*)
FROM orders
GROUP BY customer_id;

也有機會利用 Index 改善執行效率。

Composite Index 複合索引

實務上經常不是只有單一條件,例如:

SELECT *
FROM orders
WHERE customer_id = 100
AND status = 'PAID'
ORDER BY order_date;

這時可以評估複合索引(Composite Index):

CREATE INDEX idx_customer_status_date
ON orders(customer_id, status, order_date);

但複合索引不是簡單把 WHERE 裡面的欄位全部放進去。欄位順序會影響索引能否有效利用,因此還必須理解 最左前綴原則(Leftmost Prefix Rule)


為什麼建立 Index,MySQL 還是不使用?

常見原因包括:

  • 資料量太少
  • 條件會取得大量資料
  • Index 選擇性太低
  • SQL 對欄位進行函數運算
  • 資料型別不一致
  • LIKE 條件前方使用 %
  • 複合索引欄位順序不適合
  • Optimizer 判斷 Full Table Scan 成本較低

例如:

WHERE YEAR(order_date) = 2026

與:

WHERE order_date >= '2026-01-01'
AND order_date < '2027-01-01'

可能產生完全不同的索引使用方式。


SELECT * 也可能造成效能問題

很多初學者習慣:

SELECT *
FROM orders;

如果實際只需要 order_id、customer_id 與 total,比較合理的寫法是:

SELECT order_id, customer_id, total
FROM orders;

這樣可以降低資料庫讀取量、記憶體使用量、網路傳輸量與 Application 處理量。某些情況甚至可以形成 覆蓋索引(Covering Index)


JOIN 效能最佳化

JOIN 並不是效能不好,真正需要注意的是:JOIN 的資料表有多大,以及 JOIN 條件是否可以有效定位資料。

SELECT o.order_id, c.name
FROM orders o
JOIN customers c
ON o.customer_id = c.customer_id;

如果資料量很大,就必須觀察:

  • JOIN 欄位是否有 Index
  • MySQL 從哪張 Table 開始讀
  • 每個步驟預估讀取多少 Rows
  • WHERE 是否可以提早縮小結果
  • 是否讀取不必要欄位


Transaction 與 Lock 也會讓資料庫看起來很慢

有時候 SQL 本身執行很快,但 Application 卻卡住。原因可能不是查詢效能,而是正在等待 Lock。

START TRANSACTION;

UPDATE accounts
SET balance = balance - 1000
WHERE id = 1;

如果這個 Transaction 遲遲沒有 COMMIT,另一個 Transaction 又要修改相同資料時,就可能必須等待。

因此資料庫效能問題不能只看 SQL Query,也必須了解 Transaction、Row Lock、Gap Lock、Lock Wait 與 Deadlock。

InnoDB Buffer Pool

如果 SQL 與 Index 都沒有明顯問題,才應該進一步觀察 MySQL Server。

InnoDB 最重要的記憶體區域之一,就是 InnoDB Buffer Pool

它可以快取經常使用的資料頁與 Index Page,降低重複從磁碟讀取資料的需求。

innodb_buffer_pool_size

但是 Buffer Pool 並不是越大越好,還需要考慮 Server 總記憶體、是否為 Dedicated Database Server、作業系統需要的 RAM、其他服務需要的 RAM,以及實際 Working Set。


MySQL 效能調校正確流程

使用者覺得系統很慢
        ↓
確認到底慢在哪裡
        ↓
找到高成本 SQL
        ↓
Slow Query Log
        ↓
EXPLAIN
        ↓
分析 Rows / Index / Join
        ↓
修改 SQL
        ↓
修改 Index
        ↓
重新測試
        ↓
仍有問題?
        ↓
觀察 Lock / CPU / Memory / Disk I/O
        ↓
最後才考慮 Server Configuration

資料庫效能最佳化不是找到一個神奇參數,MySQL 就突然快十倍;它是一個持續觀察、測量、修改與驗證的過程。

延伸學習

  • MySQL EXPLAIN 怎麼看?
  • MySQL Index 索引完整指南
  • Composite Index 複合索引
  • Covering Index 覆蓋索引
  • MySQL 為什麼沒有使用 Index?
  • Slow Query Log 教學
  • JOIN 如何最佳化?
  • InnoDB Buffer Pool 是什麼?
  • MySQL Transaction、Lock 與 Deadlock

張貼留言

0 留言