SQL初心者必見!JOINが遅い理由とクエリ最適化の基本を徹底解説
生徒
「データベースの勉強を始めたんですけど、『JOIN(結合)』を使うと動きが重くなるって聞きました。どうして遅くなるんですか?」
先生
「それはとても大切な視点ですね。JOINは、別々の表をくっつけて一つにする操作です。バラバラに置かれた何万枚もの書類から、一致するものを探し出す作業を想像してみてください。やり方を間違えると、コンピュータもヘトヘトになってしまうんです。」
生徒
「なるほど。名簿の束から特定の名前を探し出すようなイメージですね。でも、どうすれば速くできるんでしょうか?」
先生
「まずは『なぜ遅くなるのか』という原因を知ることが近道です。今回は、パソコンを触ったことがない方でもイメージしやすいように、データの探し方や、整理のコツを具体的にお話ししますね。」
1. SQLとは何か?
SQL(エスキューエル)は、データベースと呼ばれる「大量のデータを整理して保存する箱」に対して指示を出すための言語です。例えば、会員名簿の中から特定の人を探したり、新しい人を追加したりするときに使います。
データベースは、Excel(エクセル)のような「表」の形式でデータを管理しています。この表のことを、専門用語で「テーブル」と呼びます。一つの大きな表にすべての情報を詰め込むのではなく、「注文リスト」「商品リスト」「顧客リスト」というように、役割ごとに表を分けて管理するのが一般的なルールです。
エンジニアの必須スキル「SQL」を、 図解と豊富な練習問題でゼロから体系的に学びたい人へ。 MySQLやPostgreSQLなど、各種データベースに対応した不朽の入門書です。
SQL 第2版 ゼロからはじめるデータベース操作をAmazonで見る※ Amazon広告リンク
2. JOIN(結合)とは?なぜ使うの?
「JOIN(結合)」とは、バラバラに分かれている複数のテーブルを、特定の共通点をもとに「合体」させる操作のことです。なぜわざわざ分けて保存するのかというと、情報を修正しやすくし、データの重複を防ぐためです。これを「正規化(せいきか)」と言います。
例えば、ネットショップのデータを見てみましょう。注文があった際、注文テーブルに「誰が何を買ったか」を記録しますが、ここにお客さんの住所や電話番号まで毎回書いていると、もしそのお客さんが引っ越したときに、過去のすべての記録を書き直さなければならなくなります。これはとても大変です。
そこで、「顧客ID」という背番号のような数字だけを注文テーブルに書いておき、詳しい住所は「顧客テーブル」に持たせておきます。必要なときだけ、この顧客IDを使って二つの表をくっつけるのがJOINの役割です。
3. JOINが遅くなってしまう主な原因
JOINが遅くなる最大の理由は、「コンピュータが探さなければならない回数が膨大になるから」です。これを、図書館で本を探す作業に例えてみましょう。
もし、インデックス(索引)がない状態で特定の1冊を探そうとしたら、本棚の左端から一冊ずつタイトルを確認していかなければなりません。これを「フルスキャン」と呼びます。表の行数が100件ならすぐ終わりますが、100万件あったらどうでしょうか。コンピュータといえども、相当な時間がかかってしまいます。
特にJOINを行うときは、一方のテーブルの一行に対して、もう一方のテーブルのすべての行を見に行くような動きをすることがあります。1,000行の表と1,000行の表を無計画にくっつけると、最悪の場合100万回(1,000 × 1,000)の確認作業が発生します。これが「JOINが重い」と言われる正体です。
4. SQLクエリ最適化の基本:インデックスを使おう
「最適化(さいてきか)」とは、命令の出し方を工夫して、処理を速く効率的にすることです。そのための最も強力な武器が「インデックス」です。
インデックスは、辞書の「索引」や、本の「目次」のようなものです。これがあれば、目的のページへ一気に飛ぶことができます。データベースにおいても、JOINに使う列(顧客IDなど)にインデックスを設定しておけば、コンピュータは一瞬で該当するデータを見つけ出せます。
実際に、インデックスがない状態でのデータ取得を考えてみましょう。まずは、単純なテーブルのデータを確認します。
id | user_name | city
---+-----------+---------
1 | 田中太郎 | 東京都
2 | 山田花子 | 大阪府
3 | 佐藤健一 | 福岡県
4 | 鈴木良子 | 東京都
5 | 高橋洋介 | 北海道
6 | 伊藤愛子 | 愛知県
このテーブルから「東京都」の人だけを探す指示をSQLで書くと、以下のようになります。この際、cityという項目にインデックスが貼られていないと、コンピュータは1番から6番まで全員分をチェックします。
SELECT *
FROM users
WHERE city = '東京都';
id | user_name | city
---+-----------+---------
1 | 田中太郎 | 東京都
4 | 鈴木良子 | 東京都
5. 実践!JOINを使ったデータの結合
次に、実際に二つのテーブルをJOINでくっつける例を見ていきましょう。今回は「注文履歴」と「商品名」を合体させてみます。これが遅くなる原因になりやすい部分です。
【注文テーブル:orders】
order_id | product_id | quantity
---------+------------+---------
101 | 1 | 2
102 | 3 | 1
103 | 2 | 5
104 | 1 | 1
【商品テーブル:products】
product_id | product_name | price
-----------+--------------+-------
1 | リンゴ | 100
2 | バナナ | 80
3 | メロン | 1500
この二つを「product_id」という共通の数字でつなげます。このとき、もし商品の種類が数万点あり、注文も数百万件あるのに、インデックスが一つもなかったら、画面が固まってしまうほど時間がかかるかもしれません。
SELECT orders.order_id, products.product_name, orders.quantity
FROM orders
JOIN products ON orders.product_id = products.product_id;
order_id | product_name | quantity
---------+--------------+---------
101 | リンゴ | 2
102 | メロン | 1
103 | バナナ | 5
104 | リンゴ | 1
6. 処理を速くするための「絞り込み」の重要性
JOINを速くするもう一つのコツは、「合体させる前にデータを減らす」ことです。1万件同士をガッチャンコするよりも、あらかじめ10件に絞り込んでからJOINしたほうが、コンピュータの負担は劇的に減ります。
例えば、すべての注文履歴を表示するのではなく、「昨日注文された分だけ」にWHERE句(ホウェアく)を使って条件を指定します。WHERE句は、「〜の中から、これだけを選んで!」というフィルタリングの命令です。
以下の例では、特定のユーザー(IDが1の人)の注文だけを取り出してから、商品名をくっつけています。これにより、余計なデータの照合を避けることができます。
SELECT orders.order_id, products.product_name
FROM orders
JOIN products ON orders.product_id = products.product_id
WHERE orders.user_id = 1;
order_id | product_name
---------+--------------
101 | リンゴ
104 | リンゴ
7. 無駄なデータはもらわない「SELECT *」の回避
初心者がついやってしまいがちなのが、「SELECT *(セレクト・アスタリスク)」です。これは「表にあるすべての項目を持ってきて!」という意味です。しかし、名簿の中で「名前」だけ知りたいのに、住所や電話番号、血液型、趣味まで全部持ってくると、データの通信量が増えて速度低下の原因になります。
必要な項目だけを指名して取得するのが、最適化の基本中の基本です。お買い物に行くときに、必要なものだけをメモして買うのと同じで、無駄なものは持ち帰らないようにしましょう。
8. データベース設計の工夫
最後に、少し難しいお話ですが「設計」についても触れておきます。JOINがどうしても遅い場合、あえてデータをバラバラにせず、一つの表にまとめておく「非正規化(ひせいきか)」というテクニックを使うこともあります。ただし、これは管理が難しくなる諸刃の剣です。まずは、適切なインデックスと、無駄のないSQLクエリ(命令文)を書くことから始めましょう。
データベースは、正しく扱えば膨大な情報から一瞬で答えを導き出してくれる魔法の道具です。今回の基本を意識するだけで、あなたの作るシステムやアプリは、ぐっと快適に動くようになるはずです。