正規化(1) なぜ正規化をするのか
作成日時:2025-12-11
更新日時:2025-12-11
1.メリット
主なメリットは下記。
- パフォーマンスの向上
- 操作時異状(更新/挿入/削除)の防止
1.1.パフォーマンスの向上
正規化をすればキャッシュヒット率が増加し、パフォーマンスが向上する可能性がある。
基本的にRDBMSはデータをページ(ブロック)単位で管理する。SELECT * FROM XXX WHERE id = 1とクエリを投げても、そのレコード1件だけがSSDから取得されるわけではない。
そのレコードを含むページ全体が取得される。
取得されたページは一定期間メモリー上に置かれる。
レコードを取得する際、それを含むページがメモリー上にあればメモリー上から、無ければSSDから取得される。
SSDへのアクセス速度はメモリーへのアクセス速度と比べて、はるかに遅い。
取得するレコードがメモリー上に存在する確率(キャッシュヒット率)が高ければ高いほど、パフォーマンスが向上する。
1ページに大量のレコードが含まれていればいるほど、キャッシュヒット率が高まる。
1レコードのサイズが小さければ、1ページに大量のデータが入る。
正規化をすると必然的にカラム数が減るので、レコードサイズも減る。
レコードサイズが減るとキャッシュヒット率が増加し、パフォーマンスが向上する可能性がある。
ただし、テーブル数の増加によるJOIN処理のコストとのトレードオフがある。
1.2.操作時異状(更新/挿入/削除)の防止
受注管理システムを例とする。
このシステムは下記の正規化されていないテーブルのみで、情報を管理している。
受注(受注ID, 顧客名, 顧客電話番号, 顧客住所, …)
正規化をした場合は、下記の構成となる。
受注(受注ID, 顧客ID, …)
顧客(顧客ID, 顧客名, 電話番号, 住所, …)
1.2.1.更新時異状
一部のレコードが更新されずに古い情報のまま残るなどデータの不整合が発生しうる。
非正規化:受注テーブルに下記のデータが登録されているとする。
(1, 顧客A, XX-XXXX, 東京)
(2, 顧客A, XX-XXXX, 東京)
更新条件の指定漏れや、更新と同タイミングで古い情報で挿入、トランザクションの分離レベルなどにより、古い情報が残る可能性がある。
“顧客A”の名称を”顧客A-NEW”に更新。
(1, 顧客A, XX-XXXX, 東京) ←条件指定漏れ
(2, 顧客A-NEW, XX-XXXX, 東京)
(3, 顧客A, XX-XXXX, 東京) ←古い情報で挿入
正規化をすれば発生しない。
顧客IDは不変。顧客テーブルの顧客名だけを変更すればいい。
1.2.2.挿入時異状
非正規化:受注テーブルでは、受注が無ければ顧客の情報を登録することができない。
受注IDがPKのため。
正規化後であれば、受注が無くとも顧客テーブルに顧客情報を登録できる。
1.2.3.削除時異状
非正規化:受注テーブルでは、受注を削除するとその顧客の情報が失われる。
正規化後であれば、受注を消しても顧客情報は失われない。
2.あえて正規化を行わない(非正規化)
パフォーマンスの追求やデータの保持などを行うために、あえて正規化を崩すこともある。
2.1.結合処理を減らす
テーブルの結合は基本的にコストが高い。
最初から結合した状態で保持しておけば、結合処理は発生しない。
サマリーテーブルの作成もこれにあたる。
2.2.その時点における情報の保持
販売システムにおいて、販売情報と商品情報を管理する下記のテーブルがあるとする。
販売(販売ID, 商品CD, 数量, 販売日時)
商品(商品CD, 商品名, 単価)
商品の単価を変更した場合、販売時点における単価の情報が失われる。
つまり販売時点では120円だったが、その後に商品.単価を200円に変更した場合、販売情報も200円で売ったことになる。
そのため、販売時点の金額をあえて販売テーブルに持たせる。
販売(販売ID, 商品CD, “単価”, 数量, 販売日時)
商品(商品CD, 商品名, 単価)
商品マスターが単価の履歴を持つ方法があるが、クエリーが複雑かつ遅くなる。
販売(販売ID, 商品CD, 数量, 販売日時)
商品(商品CD, 適用開始日時, 適用終了日時, 商品名, 単価)
3.個人的な思想と経験
正規化をして冗長さと複雑さを無くそう。
小さくシンプルであれ。
コーディングと一緒で1つのテーブルに複数の責務を負わせるな。
ただし何らかの要件で正規化を崩さなければならないときは、この限りではない。
3.1.非正規化による認知負荷の経験
下記の正規化されていないテーブルがあった。
受注(受注ID, SEQ, 受注日時, 顧客ID, 商品CD, 数量, …)
受注日時と顧客IDは、受注ID単位で同じ値が入る。
しかし、そんなことを知らない私は「同一受注IDで複数の顧客/受注日時が存在しうる」と勘違いして無意味に複雑なSQLを作ってしまった。
正規化をしろ。
不要な認知負荷が発生するし、そもそもテーブルの構造上、複数の顧客/受注日時が許容されるのは問題ではないか。
受注(受注ID, 受注日時, 顧客ID, …)
受注明細(受注ID, SEQ, 商品CD, 数量, …)
これならば、テーブルを見ただけで「1つの受注は1つの顧客を対象に単一の日時で発生する」と分かる。