【SQL入門】フルスキャンって何？

## はじめに

こんにちは！チョンさきです！

以前プルリクを出したら上司から「ここのSQLはフルスキャンになってるから修正してね」って言われました。

「フルスキャン...？何それ美味しいの？」状態だったので、猛烈に調べまくりました。
そこで今回の記事は、調べてみた結果を詳しく説明していきたいと思います！

同じように疑問を抱えている初心者エンジニアの皆さんの助けになれば嬉しいです！

## SQLのフルスキャンって何？

フルスキャンって簡単に言うと、
**データベースの表を最初から最後まで全部チェックする方法**です！


例えば、こんなusersテーブルがあるとします：

| id | last_name | first_name | email               | status  | created_at  | last_login  |
|----|-----------|------------|---------------------|---------|-------------|-------------|
| 1  | 山田      | 太郎       | yamada@example.com  | active  | 2025-01-15  | 2025-04-10  |
| 2  | 佐藤      | 花子       | sato@example.com    | active  | 2025-02-20  | 2025-05-05  |
| 3  | 鈴木      | 一郎       | suzuki@example.com  | inactive| 2025-03-10  | 2025-03-11  |
| 4  | 高橋      | 次郎       | takahashi@example.com| active | 2025-03-15  | 2025-04-01  |
| 5  | 田中      | 三郎       | tanaka@example.com  | active  | 2025-04-05  | 2025-05-10  |
| ... | ...      | ...        | ...                 | ...     | ...         | ...         |
| 10000 | 伊藤    | 洋子       | ito@example.com    | active  | 2025-05-20  | 2025-05-25  |

フルスキャンは、この表の行を**id=1から始めて、id=10000まで全部見ていく**ってことです。
行数が10,000件なら全部チェックするので大変ですよね！

日常生活で例えると：
- 友達の家に遊びに行って「あれ、スマホどこ置いたっけ...」ってなって部屋中を隅々まで探し回る感じ
- 図書館で本を探すときに、索引も使わずに棚の端から1冊ずつ「これかな？これかな？」って確認していく感じ
- コンビニでお気に入りのお菓子を探すのに、全ての棚を一つずつ見ていく感じ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3971022/93d79e4a-44f9-46f0-8b21-42d57551ad93.png)

## なんでフルスキャンがダメって言われるの？

少ないデータならまだいいんですが、データがめっちゃ多いと**すっごく遅くなる**んです！

:::note warn
具体的な問題点：
- アプリがモッサモサ遅くなる（ユーザーさんがイライラ）
- サーバーが「ハァハァ」言いながら頑張る（CPU使用率爆上がり）
- 他の処理まで巻き込んで全体的に遅くなる（同僚から怒られる）
:::

## フルスキャンが起きる主な原因

どんな時にフルスキャンになっちゃうのか、私が実際にハマったパターンも含めて紹介します！

1. **インデックスが作られてない**

   例えば、last_loginで検索するとき：
   ```sql
   SELECT * FROM users WHERE last_login > '2023-05-01';
   ```

   last_login列にインデックスがなければ、DBはこんな感じで全行チェックします：

   | id | last_login | チェック内容 |
   |----|------------|------------|
   | 1  | 2023-04-10 | 2023-04-10 > 2023-05-01? →いいえ、次へ |
   | 2  | 2023-05-05 | 2023-05-05 > 2023-05-01? →はい、これ取得！ |
   | 3  | 2023-03-11 | 2023-03-11 > 2023-05-01? →いいえ、次へ |
   | ... | ... | ... |
   | 10000 | 2023-05-25 | 2023-05-25 > 2023-05-01? →はい、これ取得！ |



2. **インデックスあるのに使われてない**
   ```sql
   -- 関数使うと索引無視される
   SELECT * FROM users WHERE UPPER(last_name) = '山田';  
   ```
   last_nameにインデックスがあっても、UPPER関数を使うと効かなくなります。


3. **取りすぎ問題**
   - データの大半を取るなら、そもそも索引使う意味薄いよね
   - 例：100人しかいないチームで「山田さん以外の人」を探す→そりゃ全員見た方が早い


## フルスキャンになってるか確認する方法



```sql
-- MySQLの場合（他のDBでも似たようなのあります）
EXPLAIN SELECT * FROM users WHERE login_date < '2023-01-01';
```

結果に「Table Scan」「Seq Scan」「Full Table Scan」みたいな単語が出てきたら「はい、フルスキャンでーす🙋‍♀️」ってことです。



## フルスキャンを避ける方法


### 1. インデックスを作る（一番効果的！）

検索でよく使う列にインデックスを追加します：

```sql
-- よく検索する列にインデックスを作成
CREATE INDEX idx_users_last_name ON users(last_name);
```

:::note alert

**インデックスなし（フルスキャン）**：
- 100万行のテーブルなら100万回チェック
- 全ての行を順番に確認
:::

:::note info
**インデックスあり**：
- 100万行あっても数十回程度のチェックで済む
- 二分探索みたいにピンポイントで探せる

:::

「図書館に索引を作る」感じですね。これがベスト対応だと思います！

<details><summary>🔰いまいちインデックスを理解できない？こちらへクリック！</summary>


 ### インデックスについてもう少し詳しく

**大切なポイント：インデックスを作っても元のテーブルはそのままです！** 
インデックスは元テーブルとは別に作られる「索引」のようなものです。

例えば、usersテーブルにlast_nameカラム用のインデックスを作るとこうなります：

```sql
CREATE INDEX idx_users_last_name ON users(last_name);
```

すると、DBは内部でこんな感じの索引テーブルを作ります：

**元のusersテーブル**（変わらず）

**last_name用のインデックステーブル**（新しく作られる）

| last_name順の並び | 元テーブルの行ID |
|--------------|--------------|
| 佐藤           | 2            |
| 鈴木          | 3            |
| 中村          | 999999       |
| 山田          | 1            |
| 山田          | 1000000      |
| ...          | ...          |

### 検索するとき何が違うの？

例えば `SELECT * FROM users WHERE last_name = '山田'` というクエリを実行すると：

**インデックスなしの場合**（フルスキャン）:
1. usersテーブルの1行目からスタート
2. 各行のlast_nameが「山田」かチェック
3. 100万行全部をチェックする 😱

**インデックスありの場合**:
1. まず**インデックステーブル**を使って「山田」を高速検索（二分探索みたいな効率の良い方法で）
2. インデックステーブルから該当する行IDを取得（例：id=1とid=1000000）
3. その行IDだけを使って元のテーブルから必要な情報を取り出す

つまり、`last_name = '山田'`の検索では100万行全部をチェックせず、インデックスで該当するIDを見つけてから、その行だけを取得するんです！
これが、インデックスがあると検索が速くなる理由なんです！


</details>


### 2. クエリの書き方を工夫する

```sql
-- ダメな例
SELECT * FROM users WHERE YEAR(created_at) = 2023;

-- 良い例
SELECT * FROM users WHERE created_at >= '2023-01-01' AND created_at < '2024-01-01';
```

関数（YEAR）を使うとインデックスが効かなくなるんです。

### 3. 必要な列だけを取得する

```sql
-- ダメじゃないけど微妙な例
SELECT * FROM users WHERE status = 'active';

-- 良い例
SELECT id, last_name, first_name, email FROM users WHERE status = 'active';
```

「全部ください！」より「これとこれとこれだけください！」の方が効率的なのは納得ですよね！


## 実は全部ダメってわけじゃない！

「フルスキャン=絶対悪」じゃないんです。
こんな時はむしろフルスキャンでOK：

- 小さいテーブル（数百行くらい）→そもそも全部見ても一瞬
- データの大半を取得するとき→索引使うより全部見た方が速いことも
- めったに実行しないバッチ処理→たまになら許して

## 最後に

今回で学んだことまとめ：

- フルスキャン =「データベースの表を全部チェックする処理」
- データ多いと死ぬほど遅くなる
- インデックス作ったりクエリの書き方変えれば回避できる
- EXPLAINコマンドが超便利（実行計画を見られる魔法）
- 完全に避ける必要はないけど、頻繁に実行されるクエリでは特に注意！

同じ悩みを持ってる方の参考になれば嬉しいです！😊