CodeGym /コース /SQL SELF /データの非正規化の例とその影響

データの非正規化の例とその影響

SQL SELF
レベル 25 , レッスン 4
使用可能

正規化は色んな問題を解決してくれるけど、場合によっては逆に問題を生むこともあるんだ、特にパフォーマンス面で。今日はちょっとダーク(時にはライト)な技術、非正規化の世界を紹介するよ。そう、正規化のルールを破ってもいいんだ…でもちゃんと考えて使おうね!

非正規化は正規化とは逆のプロセス。正規化がテーブルを論理的なエンティティに分割して冗長性を減らすのに対して、非正規化はパフォーマンス向上のためにデータをまとめ直すんだ。特に高負荷で複雑なクエリが頻繁に実行されるとき、たくさんのテーブルをJOINしまくるとシステムが遅くなるから、非正規化が使われることが多いよ。

つまり、非正規化はデータのクリーンさとクエリの速さの間の妥協点って感じ。

非正規化はいつ使うべき?

どんなツールもそうだけど、非正規化が適切なタイミングを知るのが大事。こんな場合に使うよ:

  1. よく使うクエリが遅くなってきたとき。 システムが高負荷で同じクエリ(例えば集計レポートやアグリゲート)が何度も実行されると、たくさんのテーブルJOINがかなり時間かかることがある。非正規化でそういうJOINの数を減らせるんだ。

  2. 分析タスクや統計処理。 分析系システム(例えばBI — Business Intelligence)では大量データの分析が必要になる。こういう場合、非正規化で「事前に用意した」データを使うことで処理が速くなるよ。

  3. 複雑なクエリ。 もしクエリのために5個、10個、もっと多くのテーブルをJOINしなきゃいけないなら、DBのパフォーマンスがかなり落ちる。非正規化でクエリ構造をシンプルにできるよ。

  4. JOINの数が常識を超えてるとき。 もし25個のテーブルをJOINしてるクエリがあったら、そろそろやり方を見直すタイミングかも。

非正規化の例

例1: ネットショップ。 正規化されたネットショップのDBにはこんなテーブルがあるとする:

  1. customers — 顧客データ。
  2. orders — 注文情報。
  3. products — 商品データ。
  4. order_items — 注文に含まれる商品。

情報を取得するクエリはこんな感じ:

SELECT
    c.customer_name,
    o.order_date,
    p.product_name,
    oi.quantity
FROM 
    customers c
JOIN 
    orders o ON c.customer_id = o.customer_id
JOIN 
    order_items oi ON o.order_id = oi.order_id
JOIN 
    products p ON oi.product_id = p.product_id
WHERE 
    c.customer_id = 42;

でも、もしこのネットショップが1日に何十万件も注文を処理してたら?このクエリはJOINが多すぎてめっちゃ遅くなるよね。

解決策:非正規化。

よく使う情報用のテーブルを作ってみよう:

CREATE TABLE order_summary AS
SELECT 
    c.customer_id,
    c.customer_name,
    o.order_id,
    o.order_date,
    p.product_id,
    p.product_name,
    oi.quantity
FROM 
    customers c
JOIN 
    orders o ON c.customer_id = o.customer_id
JOIN 
    order_items oi ON o.order_id = oi.order_id
JOIN 
    products p ON oi.product_id = p.product_id;

これでデータが欲しいときはorder_summaryにクエリするだけ:

SELECT * FROM order_summary WHERE customer_id = 42;

例2: 分析システム。 例えばイベントチケット販売会社のDBを扱ってるとしよう。テーブルは:

  1. events — イベント情報。
  2. sales — チケット販売データ。

もしアナリストが全イベントの1枚あたり平均売上をレポートしたい場合、正規化構造だと毎回アグリゲートクエリを実行しなきゃいけない:

SELECT
    e.event_name,
    AVG(s.price) AS avg_ticket_price
FROM 
    events e
JOIN 
    sales s ON e.event_id = s.event_id
GROUP BY 
    e.event_name;

このクエリ、特に販売データが何百万行もあるとかなり遅くなる。

解決策:非正規化。 集計済みデータ用のテーブルを作ろう:

CREATE TABLE event_summary AS
SELECT 
    e.event_id,
    e.event_name,
    COUNT(s.sale_id) AS ticket_count,
    SUM(s.price) AS total_revenue,
    AVG(s.price) AS avg_ticket_price
FROM 
    events e
JOIN 
    sales s ON e.event_id = s.event_id
GROUP BY 
    e.event_id, e.event_name;

これで集計レベルのレポートは速くなる:

SELECT
    event_name, 
    avg_ticket_price 
FROM 
    event_summary;

非正規化の影響

非正規化は確かにクエリを速くできるけど、万能な魔法じゃない。やるときに直面する問題もあるよ。

まずはデータの重複。同じ情報が複数の場所に保存されるから、DBサイズがすぐ大きくなって管理が大変になる。

次に、データの更新が面倒になる。例えばcustomersテーブルに顧客データがあって、order_summaryにもそのコピーがある場合、顧客が名前や住所を変えたら両方更新しないといけない。どっちか忘れるとデータがズレてエラーになる。

さらに、こういう冗長性のせいで混乱しやすくてミスもしやすい。同じドキュメントのバージョンがいくつもあると、どれが正しいのか分からなくなるのと同じだね。

最後に、こういうDBの保守や拡張は難しくなる。全部のデータコピーを同期させるためにトリガーやスクリプトを書かないといけない。開発者の手間が増えるよ。

要するに、非正規化はメリット・デメリットをちゃんと理解して賢く使うべきツールだよ。

1
アンケート/クイズ
データノーマライゼーション、レベル 25、レッスン 4
使用不可
データノーマライゼーション
データノーマライゼーション
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION