技術 7 分鐘閱讀

MySQL 查詢優化:讓你的資料庫跑得更快

MySQL 查詢優化:讓你的資料庫跑得更快

前言

很多接案專案一開始跑得好好的,等資料量一大就開始變慢。問題通常出在資料庫查詢沒有優化。這篇文章分享一些我在實務中常用的 MySQL 優化技巧。

善用 EXPLAIN 分析查詢

優化的第一步是了解查詢到底怎麼執行的:

EXPLAIN SELECT * FROM orders
WHERE user_id = 123
AND status = 'completed'
ORDER BY created_at DESC;

重點看這幾個欄位:

  • type:最好是 refconst,如果是 ALL 代表全表掃描
  • key:實際使用的索引
  • rows:預估掃描的行數
  • Extra:注意有沒有 Using filesortUsing temporary

在 Laravel 中可以這樣使用:

// 印出查詢的 SQL
DB::enableQueryLog();
$orders = Order::where('user_id', 123)->get();
dd(DB::getQueryLog());

索引設計原則

複合索引的欄位順序很重要

MySQL 的索引遵循「最左前綴原則」:

-- 建立複合索引
ALTER TABLE orders ADD INDEX idx_user_status_date
  (user_id, status, created_at);

-- 會使用索引
SELECT * FROM orders WHERE user_id = 123;
SELECT * FROM orders WHERE user_id = 123 AND status = 'completed';

-- 不會使用索引(跳過了 user_id)
SELECT * FROM orders WHERE status = 'completed';

在 Laravel Migration 中建立索引

Schema::create('orders', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained();
    $table->string('status');
    $table->decimal('total', 10, 2);
    $table->timestamps();

    // 複合索引
    $table->index(['user_id', 'status', 'created_at']);
});

避免過多索引

索引不是越多越好。每個索引都會:

  • 佔用磁碟空間
  • 減慢 INSERT、UPDATE、DELETE 的速度
  • 增加資料庫維護成本

一般來說,一張表的索引數量控制在 5-6 個以內比較合理。

N+1 查詢問題

這是 Laravel 專案中最常見的效能殺手:

// N+1 問題:假設有 100 筆訂單,會產生 101 次查詢
$orders = Order::all();
foreach ($orders as $order) {
    echo $order->user->name; // 每次迴圈都查一次 users 表
}

// 使用 Eager Loading 解決:只需要 2 次查詢
$orders = Order::with('user')->get();
foreach ($orders as $order) {
    echo $order->user->name; // 不再額外查詢
}

我建議在開發環境安裝 Laravel Debugbar,它會自動偵測 N+1 問題:

composer require barryvdh/laravel-debugbar --dev

查詢優化技巧

只選需要的欄位

// 不好:選出所有欄位
$users = User::all();

// 好:只選需要的欄位
$users = User::select('id', 'name', 'email')->get();

使用分頁取代全部載入

// 不好:載入全部資料
$products = Product::all();

// 好:分頁載入
$products = Product::paginate(20);

// 更好:使用 cursor pagination 處理大量資料
$products = Product::cursorPaginate(20);

善用 chunk 處理大量資料

// 不好:一次載入 10 萬筆資料到記憶體
$users = User::all();
foreach ($users as $user) {
    // 處理邏輯
}

// 好:分批處理
User::chunk(1000, function ($users) {
    foreach ($users as $user) {
        // 處理邏輯
    }
});

使用 Raw Query 處理複雜統計

$stats = DB::select('
    SELECT
        DATE(created_at) as date,
        COUNT(*) as order_count,
        SUM(total) as revenue
    FROM orders
    WHERE created_at >= ?
    GROUP BY DATE(created_at)
    ORDER BY date DESC
', [now()->subDays(30)]);

慢查詢日誌

開啟 MySQL 的慢查詢日誌,找出需要優化的查詢:

# my.cnf
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

這會記錄所有執行時間超過 1 秒的查詢,是線上環境除錯的好工具。

快取策略

對於不常變動的資料,使用快取減少資料庫壓力:

// 快取 60 分鐘
$categories = Cache::remember('categories', 3600, function () {
    return Category::with('children')->get();
});

// 資料更新時清除快取
Cache::forget('categories');

實際案例分享

最近有個電商專案,商品列表頁載入時間高達 5 秒。經過分析發現:

  1. 有 N+1 問題(商品圖片關聯)
  2. 缺少 category_id 的索引
  3. 排序欄位沒有索引

修正後載入時間降到 200 毫秒以內,效果非常顯著。

結語

資料庫優化不需要很高深的技術,掌握好索引設計和查詢寫法就能解決大部分問題。建議在開發階段就養成好習慣,不要等到上線後才來救火。定期檢查慢查詢日誌,及早發現並處理效能瓶頸。

#MySQL #資料庫 #效能優化 #Laravel
Thousand Lab

Thousand Lab