論理削除がデータを汚している

ベクトルの違うデータ

まあ、それは事実。
ただ、履歴をそのまま残したいということも事実。
いちいち削除履歴テーブルなんて作ってられないのも事実。

※ここでの論理削除は、復活する論理削除じゃなく、物理削除の代わりとして履歴のための論理削除を指します。(復活する論理削除って、そもそも削除とは言えないって気も...)

本来、論理削除されたデータって...

そのテーブルの定義するデータとはベクトルの違うデータ

である考えます。

でも、わざわざ削除されたデータを保持するテーブルを作ると、それはそれで面倒なのでそのまま同じテーブルに持ったままにする。その方が扱いが簡単なことが多いから。削除フラグを true にするだけで済むから。

個人的には、業務上重要なテーブルに関しては、しっかりと「削除履歴テーブル」を用意して、本体のテーブルには常に有効なデータだけがある状態の方が、データメンテもプログラムも遥かに楽になりますし、なんといってもデータが安全です。テーブルを限定すればそんなに数も多くならないし。

ユニーク制約とのトレードオフ

ポイントは、ユニーク制約が貼れるかどうか

FOOとBARカラムで業務上ユニークなテーブルがあるとして、でも、そのテーブルに削除フラグ(DEL_FLG)があったら、少なくともFOOとBARの複合ユニークは貼れません。(削除も含めて、FOOとBARはユニークって仕様ならいいですが、まあ、あんまりそういうことはないかなぁと)

FOOとBARと「削除フラグ=false」でユニークになります。でも、FOOとBARと「DEL_FLG=true」ではユニークにならない。なので、削除フラグをユニーク制約に含めることはできない。「FOO=A, BAR=B, DEL_FLG=true」のデータが大量にあるかもしれないから。

ユニーク制約がないと何が困るかって、一言でずばり「なんでも入っちゃう」ってところ。要は「FOO=A, BAR=B, DEL_FLG=false」も、複数入ってしまう可能性があるってこと。

そりゃ、アプリで一生懸命そうならないようにするわけですが、実際の業務ではこんな単純じゃないわけで、FOO, BAR, BAZ, QUX, QUUX, CORGEってカラム多かったり、例えば、FOOの値によってユニーク性が若干変わったり、そのテーブルに登録するアプリ(アクター)が複数あったり。

とあるすれ違いで入ってしまう可能性はなきにしもあらずです。ふとしたときに重複データが入ってしまって、「一件検索が二件になってエラーになっちゃった」とか。もちろん、しょっちゅうじゃないけど、でも確かにそういうトラブルは世の中発生しています。

スピード重視の今の時代、その辺の厳密なチェック実装に時間をなかなかかけてられない、っていうならなおさら。逆に言うと、少ない労力の仕組みで最低限の担保ができればうれしい。ちゃんとみっちり仕様考えてテストすれば(お金かければ)OKでしょ、っていう考えが通じる現場は徐々に少なくなってきています。テストも大事ですが、一方で仕組みによる防御を加えることも大事。

エラーで困るってだけでなく、そのあとのデータメンテも大変で、同じ落ちるにしても、せめてデータがブロックされていれば。あわよくば、そのブロックを検知して排他制御的な処理がされてれば。(というかそれが理想かな)

本来、ユニーク制約を貼るってのはとても簡単なこと。ERDツールでちょっとカラム選択して制約作っておけば済む話。実はそれだけで、運用後のメリットが享受できてるわけですね。

削除履歴テーブルなんてもの作ってられないというなら、そこはやっぱりトレードオフ、そのリスクは背負わないといけない。でも、リスクは背負いたくない。

「削除フラグがfalseのときだけFOOとBARがユニーク」

っていうユニーク制約が貼れたらいいのになぁって、何人もの人から聞いた言葉ですが、まあDBMSにはないです。かといって、トリガーでぐちゃぐちゃ頑張りたくない。

頑張ってユニーク制約

回避的なやり方をよく見かけます。

削除フラグだけじゃなく、削除日時みたいなカラムを用意して、有効なデータには固定の "9999/12/31" とか入れて、削除されたデータには削除した日時をそのまんま入れる。「FOOとBARと削除日時」でユニーク制約。アプリが有効なデータを登録するときにマジック日付をしっかり固定で登録してくれさえすれば成立します。

回避的ってなんか後ろ向きな感じですが、そして「うへぇ」って叫んじゃいそうなやり方ですが、でも、やらないよりはマシかもなぁとは思います。登録や更新が色々なプロセスから実行されて、構造が複雑なテーブルなら、しのご言ってられないって。

その削除フラグ、どういう…?

自分もまだ思考中のテーマです。そもそも論理削除に関しては、取り扱いが難しいと思っています。みんな気軽に論理削除フラグを追加するけど...

o ほんとにその削除フラグ必要?
o その削除フラグはどういうときにtrueになるの?
o 検索する側は、どういうときにその削除フラグを見るの?

って、見直して欲しい場面が多々あります。(全テーブル削除フラグとかって、ほんと憂鬱に...)

jflute流で言えば、削除フラグにはDBコメントが絶対に必須です。単にポツンと無言で存在している削除フラグほど、プログラマーを惑わせるものはないと考えています。

ユニーク制約の大切さ

そして...

ユニーク制約をどう貼るか!?

DB設計の中ではマニアックな要素だと思いますが、これが運用後のメンテナンスで発揮するんですねー。でも、貼ってしまえばあまりに当たり前のことなので、「ユニーク制約様のおかげです」って声に出して言わないので、やっぱりまあ地味なことには変わりはないんでしょうけど。

とりあえず DBFlute ではユニーク制約違反を、EntityAlreadyExistsExceptionというクラスでcatchできるようにしています。画面ごとに細かく制御しないにしても、統一的に「そのデータ既にあるよんメッセージ」を出す画面に遷移さえしていればって感じで。

注文は削除されません


ちなみにこちらの考え方、とても共感しています。
=> データの削除は非推奨 | InfoQ

一度生まれたものが、状態が変わっただけと言えるので「意味のわかりやすい状態」で管理をしたいなと。実際にそういう風にDB設計することが多いですね。


...

追記: その後、論理削除の記事を見つけた

(2015/3/24)

DB設計するときは、面倒がらずにしっかり考えて、空間をデザインしていきたいものですね。

// DELETE_FLAG を付ける前に確認したいこと | Qiita
http://qiita.com/Jxck_/items/156d0a231c6968f2a474

// 論理削除が奪うもの | 泥箱的なメモ
http://dekasasaki.tumblr.com/post/69487259373