データベースの「非正規化」とは?SQLのパフォーマンスを劇的に改善する設計のコツを徹底解説
生徒
「先生、データベースの設計について調べていたら『非正規化』という言葉が出てきました。これって一体なんですか?」
先生
「非正規化というのは、あえてデータの重複を許したり、バラバラの表を一つにまとめたりすることを指します。スピードを上げるためのテクニックですね。」
生徒
「えっ、データは整理してバラバラにするのが基本だって習いましたけど……。わざと戻しちゃうんですか?」
先生
「その通りです。きれいに整理整頓しすぎると、逆にデータを取り出すのに時間がかかってしまうことがあるんですよ。今日はその理由と、どうやって使い分けるかを学んでいきましょう。」
1. データベースの基本:正規化と非正規化の違い
データベースの世界には、情報をきれいに整理整頓するための「正規化(せいきか)」というルールがあります。 例えば、お店の注文管理をするとき、「注文した人の名前」「住所」「買った商品」「商品の値段」を一つの大きな表に全部書き込むと、同じ人が何度も買い物をするたびに、同じ住所を何度も書かなくてはいけません。これは無駄ですよね。
そこで、正規化では「顧客名簿」と「注文履歴」というように、表をいくつかに分割して保存します。 しかし、今回学習する「非正規化(ひせいきか)」は、その逆を行います。 あえてバラバラの表を一つにまとめたり、同じデータを複数の場所に持たせたりする設計手法のことです。
なぜそんなことをするのか。それは「読み込み速度(パフォーマンス)」を向上させるためです。 パソコンが複数の表をくっつけて答えを出すのは、人間が辞書を何冊も行き来して調べるのと同じで、とても体力がいる作業なのです。
2. なぜ非正規化が必要なの?パフォーマンスとの深い関係
プログラミング未経験の方にとって、「パソコンは計算が速い」というイメージがあるかもしれません。 確かに計算は速いのですが、データベースからデータを探し出し、バラバラの表を繋ぎ合わせる(これを「結合」や「JOIN」と呼びます)作業は、データ量が増えれば増えるほど時間がかかるようになります。
想像してみてください。図書館で本を探すとき、「著者の名前はAという棚」「本のタイトルはBという棚」「貸出状況はCという棚」に分かれていたら、一冊の本の状態を知るために三つの棚を走り回らなくてはなりません。 もし最初から一つの棚に「著者・タイトル・貸出状況」が全部書いてあったら、一歩も動かずに済みますよね。これが非正規化のメリットです。
特に、数百万件、数千万件という膨大なデータを扱う大規模なWebサービスやアプリでは、この「棚を走り回る時間」が積み重なって、画面が表示されるまで数秒待たされるといった遅延の原因になります。 これを解決するために、あえてデータを重複させて、一度にサッと情報を取り出せるようにするのです。
3. 【実践例1】バラバラの表を一つにまとめて表示速度を上げる
まずは、正規化された「注文表」と「商品表」を見てみましょう。 この状態では、誰がどの商品名を買ったか知るために、二つの表をくっつける指示を出す必要があります。
[注文表 (Orders)]
order_id | customer_name | product_id
---------+---------------+-----------
1 | 山田太郎 | 101
2 | 佐藤花子 | 102
3 | 鈴木一郎 | 101
4 | 田中次郎 | 103
[商品表 (Products)]
product_id | product_name | price
-----------+--------------+-------
101 | りんご | 150
102 | みかん | 100
103 | バナナ | 120
この二つの表から、「名前」と「商品名」を取得するSQLを書いてみます。
ここでは JOIN(ジョイン)という、表と表を合体させる命令を使います。
SELECT Orders.customer_name, Products.product_name
FROM Orders
JOIN Products ON Orders.product_id = Products.product_id;
customer_name | product_name
--------------+-------------
山田太郎 | りんご
佐藤花子 | みかん
鈴木一郎 | りんご
田中次郎 | バナナ
データが少ないうちはこれでも良いのですが、注文が毎日何万件も増えると、この「合体作業」が重くなります。 そこで、非正規化をして、注文表の中に最初から「商品名」を書き込んでしまいます。
4. 【実践例2】計算済みのデータを保存しておく
次に、合計金額の計算を例に挙げてみましょう。 通常、合計金額は「単価 × 個数」でその都度計算するのが基本です。 しかし、過去1年間の売上合計を出すときに、毎回数万行の計算をさせるのは大変です。
あらかじめ「合計金額(total_price)」という列を作って保存しておけば、パソコンは計算することなく、ただ数字を表示するだけで済みます。
[注文詳細表 (OrderDetails)]
id | order_id | item_name | price | quantity
---+----------+-----------+-------+---------
1 | 501 | ノート | 200 | 2
2 | 501 | 消しゴム | 100 | 1
3 | 502 | 鉛筆 | 50 | 10
4 | 503 | 万年筆 | 3000 | 1
5 | 504 | 定規 | 150 | 2
この表から注文ごとの合計金額を出すには、通常は以下のような計算式のSQLを使います。
SELECT order_id, SUM(price * quantity) AS total_sum
FROM OrderDetails
GROUP BY order_id;
order_id | total_sum
---------+----------
501 | 500
502 | 500
503 | 3000
504 | 300
非正規化では、別の表にあらかじめこの 500 や 3000 という数字を書き込んでおきます。
そうすることで、計算処理をゼロにできるのです。
5. 非正規化のデメリット:注意すべきポイント
「じゃあ、全部一つの表にまとめれば最強じゃない?」と思うかもしれませんが、そう上手くはいきません。 非正規化には大きなデメリットがあります。それは「データの更新が面倒になる」ことです。
例えば、商品名を「りんご」から「高級りんご」に変更したとします。 表を分けていれば、「商品表」の1箇所を書き換えるだけで済みます。 しかし、注文表に直接「商品名」を書き込んでいた場合、過去の注文履歴にある数万件の「りんご」をすべて「高級りんご」に書き換えなければなりません。
もし1箇所でも書き換え忘れると、「ある行ではりんご、別の行では高級りんご」というデータの矛盾(ムジュン)が発生してしまいます。 これを「データ不整合」と呼び、システムが壊れる原因になります。
6. 初心者が覚えておくべき設計の優先順位
データベース設計を始めるときは、いきなり非正規化を考えてはいけません。 まずは「しっかりと正規化して、きれいに整理された表を作る」ことが鉄則です。 最初から楽をしようとして表をまとめると、後でデータの修正ができなくなって後悔することになります。
「きれいに整理したけれど、どうしても動作が重くて使い物にならない!」となったときに、初めて検討するのが「非正規化」です。 いわば、薬と同じで「用法・用量を守って正しく使う」ことが求められる高度な技なのです。
SQLの世界では、SELECT(取得)、INSERT(追加)、UPDATE(更新)、DELETE(削除)という4つの操作を基本とします。 非正規化は「SELECT(取得)」を速くしますが、「UPDATE(更新)」を難しくするというバランスを常に意識しましょう。
7. 【実践例3】最新の情報をすぐに出せるようにする
最後に、SNSのようなシステムで「最新の投稿」を表示する場合を考えてみましょう。 ユーザーの情報と投稿の内容が別々の表にある場合、毎回ユーザーの名前を検索しに行くのは効率が悪いです。
[ユーザー表 (Users)]
user_id | user_name | profile
--------+-----------+------------------
1 | 太郎 | 散歩が好き
2 | 花子 | 料理が趣味
3 | 一郎 | 釣りが得意
[投稿表 (Posts)]
post_id | user_id | content | user_name (非正規化で追加)
--------+---------+-------------------+-------------------------
101 | 1 | 今日は快晴! | 太郎
102 | 2 | カレーを作った | 花子
103 | 1 | お腹が空いた | 太郎
104 | 3 | 大物が釣れた | 一郎
本来 user_name は「ユーザー表」にあるので不要ですが、あえて「投稿表」にも持たせています。
これにより、以下のシンプルなSQLだけで、ユーザー名付きの投稿一覧を表示できます。
SELECT user_name, content
FROM Posts
ORDER BY post_id DESC;
user_name | content
----------+-------------------
一郎 | 大物が釣れた
太郎 | お腹が空いた
花子 | カレーを作った
太郎 | 今日は快晴!
このように、非正規化は「ユーザーの利便性(待ち時間の少なさ)」を優先したい場面で非常に有効な手段となります。