データベース・リファクタリング
作成日時:2025-12-09
更新日時:2025-12-11
データベース・リファクタリングを読んだメモ。
全体
全体の感想としては、当り前のことを書いているな、ぐらい。
マルチアプリケーション・データベースアーキテクチャの場合の移行手法はためになったが、
そういうアプリを扱うことは個人的にはなさそう。
DBアクセスはAPIサーバーのみが管理するだろうし、それより大規模になればマイクロサービスで、
各APサーバーがそれぞれ単一のDBを参照するだろうし。
トリガーやストアドなどを多用しているが、個人的には好みではない。
DBにビジネスロジックを持たせてはいけない。
変更/管理がしづらいから。
APは簡単に増やせるし変更もできる。デバッグ/テストも楽だし、DBにロックインされない。
これらはリファクタリングやデータ移行のための手法と考えている。
もしくは監査(トリガーでDB操作を登録)
もしくは極限までパフォーマンスを高める場合。
全部ストアドにすればingresは減るよね。
SQL全文渡す必要ないし。
マルチにおいてはシステム全体におけるDRY原則順守とも言えそう。
テーブル継承
STIはテーブル制約が煩雑になる。
NOT NULLやユニーク制約を付けることができない。
似通った内容を1カラムにまとめたがるが、1カラムに複数の意味を持たせてはならない。
会員→個人/法人でSTIの場合、創業日と誕生日を同一カラムに持たせたりなど。
具象TIは情報が複数テーブルに跨るからユニークを担保できない。
スキーマと設計
ANSI/SPARC 3層スキーマアーキテクチャ
- 外部スキーマ: ビュー。ユーザーやアプリからの見た目。
- 概念スキーマ: 論理な構造。概念設計と論理設計。
- 内部スキーマ: 物理な格納構造。物理設計。
下位スキーマの変更は上位スキーマに影響を与えない。
設計
- 概念設計; データと関係性洗い出し
- 論理設計: DBMSが理解できる形式。テーブル定義、カラムの型、制約、キーなど。
- 物理設計: 物理的な格納方式の決定。
ストアド
プロシージャ
複数の処理をまとめて行える。
SELECT/INSERT/UPDATE/DELETEなんでも。
ファンクション
単一値を返す。
SELECTのみ。
ドキュメント
- DDLを管理しないのは論外
- 検索/発見容易性を重視
P.11 1.6
当時は「書いては直し」(code-and-fix)の開発方法が一般的であった。
ITコミュニティは、この方法では質が低く保守しにくいコードしかできないと判断し、
今のvibe codingみたい。
リファクタリングにおけるデータベースアーキテクチャ
シングルアプリケーション・データベースアーキテクチャ
1つのアプリのみがDBを使う。
この場合は、データベースを自由にリファクタリングできる。
影響範囲が分かる。
アプリケーションをリファクタリング内容に合わせて修正すればいいだけ。
マルチアプリケーション・データベースアーキテクチャ
複数のアプリがDBを使う。
こっちは気軽にDBのリファクタリングができなくなる。
書籍「データベース・リファクタリング」はこちらをどうするべきかを書いている。
情報を2つ持ち、それらを同期させる(トリガーを使用)。
古いほうが参照されなくなったら、それを削除。
というのが、本の大部分で書かれていたリファクタリング手法。
ORMとの相性やアスタ使用問題、カラムに制約付けられないなど、
問題が発生しそう。
制約
シングルアプリなら制約は緩めを許容できるかもしれない。
そのAPしか使わないのだから。そのAPで担保できる。
マルチだと複数の概念の異なるAPからアクセスされるのだから、
DB側でデータの整合性を担保しなければならない。
権限によるアクセス制御とかも。
不吉なにおい(P.23)
- 複数の目的に使われるカラム
- 複数の目的に使われるテーブル(=STIなど)
- 冗長なデータ
- カラムの多すぎるテーブル
- 行の多すぎるテーブル
- 「スマート」カラム(桁に情報を持つ)
- 変更の恐怖
削除前の退避
SET UNUSED
カラムを削除せずに非表示にする
CREATE TABLE XXX AS SELECT
計算カラム
ストレージとパフォーマンスのトレードオフ。
情報を最新に保つようにしなくてはならない。(トリガーやバッチなど)
イミュータブルならばいいけれども。
同期のタイミングをどうするか。
ビュー
ビュー自身にインデックスは貼れない。
元の表のインデックスが使われるかもしれない。
マテビューならインデックスを付与できる。
サマリーテーブルとの違いは、リフレッシュコマンドですぐ再構築できるくらい?
更新可能ビュー
テーブル名変更に使える。
古いテーブル名でビューを作っておく。
テーブル/カラムの同期
名称変更時に、これらのカラムを並行して保持する場合、同期する必要がある。
トリガーかビューかバッチ。
サロゲートキー
P.80
マイクロサービスはDBリファクタリングが容易では?
厳密なマイクロサービスであれば、アプリごとにDBを保持している。
つまりそのDBを扱うのはそのアプリしか存在しないのだから、
アプリとDBの整合性を容易に保てる。
テーブルの分割
- パーティショニング(横)
- 属性に応じたテーブル分割(横)
- 正規化(縦)
- 5NF(=あまり使用されないカラムを別テーブルに退避)(縦)
外部キーによる参照制約
取りうる値をPKとしたテーブルを作成。
対象のカラムに上記のテーブルに対するFKを貼れば、ドメインが保証される。
カラム制約や列挙型ではなく、FKで制約を入れた。
コードの追加も楽。
ON UPDATE CURRENT_TIMESTAMP
トリガー
トリガー失敗時のロールバック。
トリガー循環問題。
論理削除
WHEREでフラグを見なければならないから面倒。
レコード数も増えてパフォーマンスが低下する。
- 物理削除
- データを残したいならば削除テーブルに移動
後者において、削除データも含めてユニーク制約を持たせたい場合、
クラステーブル継承で親テーブルにその制約を持たせるか。
もしくは部分インデックスで、有効なデータのカラムにのみユニーク制約を持たせる。
PostgreSQLであれば継承が使えるが、複雑になるしORMもサポートしているか分からないので使用しない。
5.11. 継承
「スマート」カラム
桁に情報を持たせる奴。
NFに反するけど、状況によっては許容できるか。
ビットで情報を持たせたり。
レコードサイズも減るよね。