Laravel でパフォーマンスを改善してくれる インデックス どんなものなのかな?
今回は、データベースの検索を速くするための仕組み である インデックス の基本から Laravel での設定方法 について、ご紹介します。
Laravel の 環境構築がまだの場合 は、下記より Laravel の 環境構築 を行なってください。
インデックス とは?
インデックス とは、データベースの検索処理を高速にするための仕組み のことです。
通常、インデックスが設定されていないカラム(項目)を検索する と、データベースはテーブル内のすべてのデータを1件ずつ確認 します。これを フルテーブルスキャン と呼びます。
データが少ないうちは問題ありません が、件数が増えるにつれて処理はどんどん遅くなり、目的のデータを見つけるまでに時間がかかるようになります。
その問題を解決してくれる のが インデックス です。インデックス を設定することで、すべてのデータを順番に確認する必要がなくなり、目的のデータにすばやくアクセスできるようになります。
イメージとしては 本の「索引(さくいん)」と同じ で、索引がない場合は最初のページから順番に探す必要がありますが、索引があれば目的のページにすぐたどり着くことができます。
データベースのインデックスも、これと同じ役割を持っています。
インデックス が設定されていない場合の処理(フルテーブルスキャン)とイメージ図
インデックス が設定されていない 場合、条件に一致するデータが見つかるまで、すべてのレコードを順番に確認していく必要があります(フルテーブルスキャン)
データが少ないうちは問題ありません が、件数が増えるにつれて検索にかかる時間が徐々に長くなっていきます。
-- created_at(作成日時)で並べ替えて、上から5件取得
ORDER BY created_at DESC
LIMIT 5
posts テーブル
ID | created_at
-------------------------
1 | 2024-01-01
2 | 2024-01-05
3 | 2024-01-03
4 | 2024-01-10
5 | 2024-01-02
↓
① すべてのレコードを取得
② created_atで並び替え(ソート)
並び替え後:
4 | 2024-01-10 ← 1件目
2 | 2024-01-05 ← 2件目
3 | 2024-01-03 ← 3件目
5 | 2024-01-02 ← 4件目
1 | 2024-01-01 ← 5件目
↓
上から5件を取得
インデックス が設定されている場合の処理とイメージ図
インデックス が設定されている 場合、データベースはテーブル内のデータをすべて順番に確認するのではなく、インデックス を使って目的のデータに効率よくアクセスします。
これにより、条件に一致するデータを素早く見つけることができ、検索処理の速度が大きく向上します。
例えば、インデックス が設定されていない場合と同じように created_at を基準に並び替え(ORDER BY)してデータを取得する場合でも、インデックス を利用することで、あらかじめ整理された順序から効率よくデータにアクセスできる ため、すべてのレコードを取得して並び替える必要がなくなり、無駄な処理を減らすことができます。
[インデックス有り]
-- created_at(作成日時)で並べ替えて、上から5件取得
ORDER BY created_at DESC
LIMIT 5
インデックス(created_at)
created_at → レコードの位置
posts テーブル
ID | created_at
-------------------------
1 | 2024-01-01
2 | 2024-01-05
3 | 2024-01-03
4 | 2024-01-10
5 | 2024-01-02
↓
インデックス上では created_at 順に整理されている
4 | 2024-01-10
2 | 2024-01-05
3 | 2024-01-03
5 | 2024-01-02
1 | 2024-01-01
↓
上から5件をそのまま取得
ID | created_at
-------------------------
4 | 2024-01-10
2 | 2024-01-05
3 | 2024-01-03
5 | 2024-01-02
1 | 2024-01-01
インデックス が設定されていない場合のコード例 と実行される SQL
インデックス を設定する例 として、Postモデル(postsテーブル) と Userモデル(usersテーブル) としているため、モデルやテーブル、ダミーデータなどまだ準備していない場合 は、下記を参考にご準備ください。
コード例
実行時間 の確認のため、簡易的に microtime関数を使って計測 し、「routes/web.php」に記述しています。
// routes/web.php
use App\Models\Post;
use Illuminate\Support\Facades\Route;
Route::get('/index', function () {
// 実行時間の計測開始
$start = microtime(true);
// postsテーブルから最新データを5件取得
$posts = Post::orderBy('created_at', 'desc')
->limit(5)
->get();
// 実行時間の計測終了
$end = microtime(true);
// 実行時間を表示(秒)
return '実行時間: ' . number_format($end - $start, 6) . ' 秒';
});
実行されるSQL
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 5;
実際の実行時間
「localhost/index」にアクセスする と、実行時間は更新するたびに変わりますが、平均で「0.003 秒」前後ぐらい になります

インデックス が設定されている場合のコード例 と実行される SQL
Laravel では、マイグレーションを使用してインデックスを追加することができます。
既存のテーブルも、Schema::tableを使うことでインデックスを後から追加・削除することが可能 です。
インデックスの追加設定
Postモデル(postsテーブル) の created_at に インデックス を設定するために コマンド から マイグレーションファイルを作成
マイグレーション作成コマンド
php artisan make:migration add_index_to_posts_table
マイグレーションファイルに created_at のインデックスを追加
// /database/migrations/20XX_XX_XX_XXXXXX_add_index_to_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->index('created_at');
});
}
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropIndex(['created_at']);
});
}
};
マイグレーション の実行
php artisan migrate
実際の実行時間
「localhost/index」にアクセスする と、実行時間は更新するたびに変わりますが、平均で「0.002 秒」前後ぐらい に パフォーマンスが改善 されています
※ コード例 と 実行されるSQL は、インデックスを設定していない場合 と同じものになります

複合インデックス が設定されていない場合のコード例 と実行される SQL
複合インデックス とは?
複合インデックス とは、2つ以上の複数のカラムを組み合わせて作るインデックス のことです。
複数条件での検索 や、絞り込みと並び替えを同時に行うクエリのパフォーマンスを改善すること ができます。
コード例
実行時間 の確認のため、簡易的に microtime関数 を使って計測 し、「routes/web.php」に記述しています
// routes/web.php
use App\Models\Post;
use Illuminate\Support\Facades\Route;
// 複合インデックス
Route::get('/multiple-index', function () {
// 実行時間の計測開始
$start = microtime(true);
// postsテーブルから user_id = 100 の投稿を、created_atの降順で取得
$posts = Post::where('user_id', 100)
->orderBy('created_at', 'desc')
->get();
// 実行時間の計測終了
$end = microtime(true);
// 実行時間を表示(秒)
return '実行時間: ' . number_format($end - $start, 6) . ' 秒';
});
実行されるSQL
SELECT * FROM posts
WHERE user_id = 100
ORDER BY created_at DESC;
実際の実行時間
「localhost/multiple-index」にアクセスする と、実行時間は更新するたびに変わりますが、平均で「 0.002 秒」前後ぐらい になります

複合インデックス が設定されている場合のコード例 と実行される SQL
インデックスの追加設定
Postモデル(postsテーブル) の user_id と created_at の 複合インデックス を設定する ために コマンド から マイグレーションファイルを作成
マイグレーション作成コマンド
php artisan make:migration add_multiple_index_to_posts_table
マイグレーションファイルに user_id と created_at のインデックスを追加
※ 通常、外部キー(例: user_id)を作成するとインデックスも自動的に作成 されます。
ただし、複合インデックスに外部キーが含まれる 場合、MySQLが複合インデックスを外部キー用に依存して利用してしまうことがある ため、外部キー用の単体インデックスを明示的に作成して依存関係を分離 させます。
※ 外部キー(例: user_id)に単体インデックスが設定されてある場合は、追加されません
// /database/migrations/20XX_XX_XX_XXXXXX_add_multiple_index_to_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('posts', function (Blueprint $table) {
// 外部キー(user_id)に単体インデックスを明示的に追加
if (!Schema::hasIndex('posts', 'posts_user_id_index')) {
$table->index('user_id');
}
// 検索条件(user_id + created_at)用の複合インデックスの追加
$table->index(['user_id', 'created_at']);
});
}
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
// 検索条件(user_id + created_at)用の複合インデックスの削除
$table->dropIndex(['user_id', 'created_at']);
});
}
};
マイグレーション の実行
php artisan migrate
実際の実行時間
「localhost/multiple-index」にアクセスする と、実行時間は更新するたびに変わりますが、平均で「 0.002 秒」前後ぐらい で 秒数的にはさほど変わらないですが、少し改善されています
※ コード例 と 実行されるSQL は、インデックスを設定していない場合 と同じものになります

単体インデックス と 複合インデックス の違い
単体インデックス は、created_at のように単一カラムでの並び替えや検索に効果があります。
また 複合インデックス は、 user_id で絞り込みつつ created_at で並び替えるようなクエリに対して、より効率的に処理を行うこと ができます。
| 項目 | 単体インデックス | 複合インデックス |
|---|---|---|
| 対象 | 1カラム | 複数カラム |
| 得意な処理 | 単一条件の検索・並び替え | 絞り込み + 並び替え |
| 代表例 | created_at | user_id + created_at |
| 効果的なSQL | SELECT * FROM posts ORDER BY created_at DESC | SELECT * FROM posts WHERE user_id = 100 ORDER BY created_at DESC |
補足: 自動的にインデックスが付くケース
Laravel では、主キー や UNIQUE制約 には多くのデータベースで自動的にインデックスが付与されます。
ただ 外部キー については、データベースによって挙動が異なり、MySQLでは自動的にインデックスが作成されます が、PostgreSQLでは明示的にインデックスを追加する必要があります。
※ timestamps(created_at, updated_at)には自動的にインデックスは付与されません
| 項目 | MySQL | PostgreSQL | 補足 |
|---|---|---|---|
| 主キー | ✅ 自動で付く | ✅ 自動で付く | 一意識別のため必須 |
| UNIQUE | ✅ 自動で付く | ✅ 自動で付く | 重複防止+検索高速化 |
| 外部キー | ✅ 自動で付く | ❌ 自動では付かない | PostgreSQLは明示的に追加が必要 |
まとめ
今回は、データベースの検索処理を高速にするための仕組み である インデックス についてご紹介しましたが、いかがでしたでしょうか?
インデックス を設定することで、すべてのデータを順番に確認する必要がなくなり、目的のデータにすばやくアクセスできるようになります。
また 複数のカラムを組み合わせて作成する 複合インデックス を活用することで、複数条件での検索 や、絞り込みと並び替えを同時に行うクエリのパフォーマンスをさらに改善することができます。
まずは、インデックス の基本的な仕組みを理解し、複合インデックス なども活用しながら、Laravel アプリケーションの パフォーマンス改善 に役立てていきましょう。




コメント