データを完璧に正規化すると、各テーブルはめっちゃコンパクトになって、情報は一つの原則だけに従うようになる。でも、実際のクエリ(例えば「どの学生がSQLコースに登録してる?」みたいなやつ)を実行するには、たくさんのテーブルを結合しなきゃいけないこともある。テーブルが多いほどクエリは複雑になって、システムが「シャベルで掘る」みたいに大変になるんだ。
もうJOINは前の講義で知ってるよね。これが、ちゃんと設計されたデータベースで必要になるクエリの例だよ:
SELECT students.name, courses.title
FROM students
JOIN enrollments ON students.id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.id
WHERE courses.title = 'SQL';
見た目はシンプルだけど、裏側ではサーバーがめっちゃ頑張ってる:各テーブルを読んで、データを結合して、フィルタリングして…もしテーブルがすごく大きかったらどうなる?パフォーマンスが落ちるのは当然だよね。
バトル:正規化 vs スピード
幸い(それとも残念?)、現実のデータベースは妥協の産物なんだ。完全な正規化はデータの整合性を守るけど、複雑なクエリの実行は遅くなる。もしデータベースが分析やレポート用なら、デノーマライズした方が得な場合もある。10個の小さい箱を1つの大きな箱に変える感じ:データを取り出すのは速くなるけど、また分け直すのは面倒になる。
いつ「正規化ガチ勢」から一息つくべき?
デノーマライズがオススメなシナリオもある:
よく使う集計値
例えば、システムが毎日各コースの学生数を数えるクエリを実行してるとしよう。正規化された構造だと、JOINと
COUNT()を毎回やらなきゃいけない。その代わりに「Courses」テーブルに
student_countカラムを追加して、レコードの追加・削除時に自動で更新するのがアリ。
-- デノーマライズされたカラム
UPDATE courses
SET student_count = (
SELECT COUNT(*)
FROM enrollments
WHERE enrollments.course_id = courses.id
);
よく作るレポート
もしクライアントが毎日「誰が、どこで、いつ買った?」みたいなレポートを欲しがるなら、「顧客名、商品、日付」みたいな完成済みの行を持つデノーマライズテーブルを保存した方が楽。メインテーブルは大きくなるけど、データ取得は速くなる。
読み込み多め、書き込み少なめ
データベースが主に読み込み(例えば分析)で使われるなら、スピードのために正規化を犠牲にするのもアリ。
複雑なリレーションのJOIN最小化
もしテーブル間の関係が多層(ネスト)で、JOINが悪夢みたいになってるなら、正規化のレベルをちょっと下げてみよう。
例:デノーマライズでどれだけ速くなる?
ネットショップの正規化されたテーブルがあるとする:
テーブル products |
テーブル orders |
テーブル order_items |
|---|---|---|
| id | id | id |
| name | date | order_id |
| price | customer_id | product_id |
| quantity |
各注文(orders)は注文アイテム(order_items)の行で構成されてる。ショップがいくら稼いだか計算してみよう:
SELECT SUM(order_items.quantity * products.price) AS total_revenue
FROM order_items
JOIN products ON order_items.product_id = products.id;
order_itemsとproductsの結合は、データ量が多いとクエリを遅くする。
デノーマライズ構造
今度はorder_itemsテーブルに「余計な」total_priceカラム(デノーマライズ)を追加したとしよう:
テーブル order_items |
|---|
| id |
| order_id |
| product_id |
| quantity |
| total_price |
これでクエリは超シンプルになる:
SELECT SUM(total_price) AS total_revenue
FROM order_items;
JOINを避けられるから、実行が速くなるんだ。
実践課題:「販売」データベースの最適化
前提:正規化されたテーブル
テーブル products |
テーブル sales |
|---|---|
| id | id |
| name | product_id |
| price | date |
| quantity |
課題:「各商品でいくら稼いだ?」みたいな頻繁なクエリを速くすること。
ステップ1: salesテーブルにtotal_priceカラムを追加しよう:
ALTER TABLE sales ADD COLUMN total_price NUMERIC;
ステップ2: 既存データでこのカラムを埋める:
UPDATE sales
SET total_price = quantity * (
SELECT price
FROM products
WHERE products.id = sales.product_id
);
ステップ3: クエリが速くなる:
SELECT product_id, SUM(total_price) AS total_revenue
FROM sales
GROUP BY product_id;
でも!デノーマライズにはデメリットもある
「速い=正義」ってわけじゃないよ。デノーマライズにはこんな問題がある:
冗長なストレージ
total_priceカラムはデータのコピーで、追加のスペースが必要。
更新の手間
もしproductsテーブルの商品の値段が変わったら、対応するtotal_priceカラムも手動で更新しなきゃいけない。これで不整合が起きるかも。
挿入・更新・削除時の異常
デノーマライズデータの更新を忘れると、情報がすぐ「ズレる」。例えば商品の値段が変わっても、自動で反映されない。
バランス:どうやって「ちょうどいい」を見つける?
何が大事?パフォーマンス?構造? データベースが読み込み中心なら、クエリに合わせて設計しよう。
デノーマライズはピンポイントで。 例えば、重要な数値やレポートだけに使う。
デノーマライズデータの更新は自動化しよう。 トリガーやバッチ処理を使って、不整合を防ごう。
GO TO FULL VERSION