達人に学ぶDB設計指南書から学んだこと

Taiga Ishii
8 min readDec 6, 2018

今回は、「達人に学ぶDB設計指南書」という本を読んで自分が学んだことについて述べようと思います。

なぜ私がこの本を読んだのかといいますと、私自身会社でWebアプリケーションを作っているのですが、システムとデータベースは切っても切れない関係にあること、システムにおけるデータベースの役割はそのシステムの根幹に関わる重要性を帯びていることを日々感じて生活しています。

それにもかかわらずデータベース設計を論理的にり理解したことがなかったので、この本を読むことにしました。

この本ではデータベースの中でも特にRDS(リレーショナルデータベース)について言及している本です。そして理論だけでなく、実際の現場でのトレードオフについても同時に言及しています。今回はその中でも「正規化・パフォーマンスとのトレードオフ」について学んだことを書こうと思います。

RDSにおける正規化とは

この本では、データを「ある形式に揃えられた事実」であり、データは情報と文脈を合成して生まれると書かれています。データベースは、データの集積を指す論理的概念であり、そのデータベースを実装したソフトウェアがDBMS(DataBase Management System)というです 。

RDSでは、MYSQLやPostgresqlなどの様々なDBMSが存在しますが、設計はDBMSに依存せずすべてのRDSに対して適応することができるものです。

ここから正規化について書いていきます。

RDSでは、あらゆるデータを「テーブル」という単位で管理します。そのテーブルとは、ある共通点を持ったレコードの集合を二次元表の形で表しているものであり、表とは別物になります。

RDSの正規化とはデータベース設計における論理設計に当たる部分であります。

正規化とは、データベースで保持するデータの冗長性を排除し、一貫性と効率性を保持するためのデータ形式のことです。例えば、複数のテーブルに重複したデータが存在している場合(冗長なデータを保持している場合)更新処理のタイムラグなどによりデータの不整合が生じる場合やそもそもデータを登録できないようなテーブルを作成してしまう恐れがあります。そのようなデータの冗長性や非一貫性を解消するための方法が正規化になります。

正規化には段階があり、一般的には第1~5正規形まであります。ですが一般的に業務で使用するべき正規形は第3までと言われています。まずは第一正規形から。

第1正規形

第1正規形の定義は、「一つのセルの中には一つの値しか含まない」です。このような条件に当てはまる値のことをスカラ値といいます。

上記のようなケースは一つのセルに一つの値というスカラ値の定義に反していることがわかると思います。子供の列に2つの値が入っているからです。第1正規形では2つの方法で上記のような違反を解消することができます

1つ目は、列を増やすという方式です。これを行うことでスカラ値にする第1正規形の定義からは反していないことがわかります。

2つ目は、行を増やすという方式です。これも定義からは反していません。

ただ上記でも問題をはらんでいます。1つ目の問題は、テーブルの主キーを決められないことです。上記の表から子供を取得する場合、{ID,名前,子供}のすべての値が決まって初めてレコードが一意に特定できます。主キーはNULLを含んではいけないので子供がいない人を登録しようとするとNULLの制約により登録することができません。

もう一つは、テーブルの意味やレコードの単位をすぐに理解することができない点にあります。

上記の問題を解消するために「テーブル分割」を使用します。

テーブルを分割することにより、子供がいない人を登録することも可能になりますし、テーブルの意味やレコード単位が前よりはっきりします。

なぜ一つのセルに複数の値を入れてはいけないのか

これは、一つのセルに複数の値の混入を許可してしまうと主キーが各列の値を一意に決定できなくなるからです。このように一部の値が決まるとその他の値が一つに決まることを関数従属性といいます。第1正規形とは関数従属性を満たすように整理していくことなのです。

第2正規形

この正規形では、第1正規形が完了した後に行われる正規化です。

上記の例を見てみると、{会社ID,社員ID}で他のすべての列を特定することができます。しかし会社名だけは主キーの一部である会社IDに従属しています。つまり会社名は会社IDがわかると特定することができるということです。

このように主キーの列の一部に対して従属する列がある場合、この関係を部分関数従属と呼びます。これに対し、主キーを構成するすべての列に従属性がある場合、完全関数従属と呼びます。

第2正規化とは、完全関数従属のみでテーブルを構成することです。上記の場合だと会社テーブルと社員テーブルをテーブル分割することで完全関数従属のみで構成するテーブルを作成することができます。

なぜ第2正規形でないと行けないのか

これは社員がいない会社(本当にあるのかどうかわかりませんが、)を登録しようとするときにそのようなレコードは登録できない特徴があります。

第3正規形

上記のように第2正規形から始まります。上記の中の部署IDと部署名のところに注目してください。このようなテーブル分割だと、例えば一人も社員がいない部署は登録することができません。理由としては、主キーである社員IDをNULL(つまり社員が誰も所属していない状態で)部署を登録することはできないからです。ここでは部署IDが決まると部署名が決まるという関係従属が存在します。一方{会社ID,社員ID}が一意に決まると部署IDがきまるという関係従属も存在しています。

このようにテーブル内部に存在する段階的な従属関係のことを推移的関数従属といいます。

これを解消するためには第2正規形のときに行ったテーブル分割を再度使用します。

これで第3正規形が完了しました。これをすることでデータ更新時の不整合や登録できないデータがかなり起こりづらいテーブル構成になりました。

ここからは実際の業務でトレードオフ関係にある「パフォーマンス」とのバランスについて学んだことを書こうと思います。

パフォーマンスとのトレードオフについて

基本的には上記のように正規化をすることが上記で書いた問題を解決する手段として適切であり、それを行ったほうが良いです。しかし、厳密に正規化をしすぎるとパフォーマンスを低下させてしまうという問題が存在します。

データベースから指定したデータを取得する際にSQLというものを使用します。その際にSQLのボトルネックとなるのが「結合」の処理であるということです。正規化を実施する方法として、テーブル分割がありました。テーブル分割は、テーブルをまたがる集計やデータの取得をしたい場合に「テーブルの結合」をおこなわなければなりません。この結合の処理が増えれば増えるほどパフォーマンスに対して影響が生じます。

この際に非正規化(正規形を一段階前に戻す)をすることにより、パフォーマンスの劣化を防止することができます。例えば、何かのカウント(特定の人が注文した商品の数など)を結合を用いたSQLを使用して取得するのではなく、商品数などの列を用意し参照はその列を参照することで結合を使用することなくカウントを取得することができます。更新タイミングやデータの不整合が正規化をしていたときよりは低下しますがこれによりパフォーマンスが向上するというのも事実です。

まとめ

普段テーブル構成を考えるときに無意識にやっていたこともあれば、やれていなかったことなどもありとても勉強になりました。自分がやっていることに理論的な根拠が示せるとなぜこれをやるのか、これをやると何がいいのかがはっきり理解できるようになります。それにより普段の業務から意識して取り組むことができるようになるなと感じました。それと同時に今回は簡単な例だけでしたが、世の中に出ているシステムやサービスのデータベース構成はこんなに簡単ではないと思うので、自社のプロダクトの性質や特徴を理解してそれと正規化、それに伴うトレードオフを考慮して今後開発をしていこうと思いました。

ありがとうございました。

--

--