CodeGym /コース /SQL SELF /正規化とパフォーマンスのバランス

正規化とパフォーマンスのバランス

SQL SELF
レベル 26 , レッスン 3
使用可能

データを完璧に正規化すると、各テーブルはめっちゃコンパクトになって、情報は一つの原則だけに従うようになる。でも、実際のクエリ(例えば「どの学生が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つの大きな箱に変える感じ:データを取り出すのは速くなるけど、また分け直すのは面倒になる。

いつ「正規化ガチ勢」から一息つくべき?

デノーマライズがオススメなシナリオもある:

よく使う集計値

例えば、システムが毎日各コースの学生数を数えるクエリを実行してるとしよう。正規化された構造だと、 JOINCOUNT()を毎回やらなきゃいけない。その代わりに「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_itemsproductsの結合は、データ量が多いとクエリを遅くする。

デノーマライズ構造

今度は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カラムも手動で更新しなきゃいけない。これで不整合が起きるかも。

挿入・更新・削除時の異常

デノーマライズデータの更新を忘れると、情報がすぐ「ズレる」。例えば商品の値段が変わっても、自動で反映されない。

バランス:どうやって「ちょうどいい」を見つける?

何が大事?パフォーマンス?構造? データベースが読み込み中心なら、クエリに合わせて設計しよう。

デノーマライズはピンポイントで。 例えば、重要な数値やレポートだけに使う。

デノーマライズデータの更新は自動化しよう。 トリガーやバッチ処理を使って、不整合を防ごう。

2
タスク
SQL SELF, レベル 26, レッスン 3
ロック未解除
集計を速くするための非正規化
集計を速くするための非正規化
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION