sqlアンチパターン 幻の第26章「とりあえず削除フラグ」

Post on 07-Jan-2017

60.873 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

和田 卓人 (@t_wada) Aug 31, 2015

@論理削除 Casual Talks

SQLアンチパターン 幻の26章 「とりあえず削除フラグ」

#ronsakucasual

和田 卓人 id: t-wada @t_wada github: twada

スタンド名は「ワイルド・サバンナ」

おかげさまで高評価を頂いております

第26章?

本書に入れてみたかった章の話をします

(『SQLアンチパターン』は25章まで)

愚者は経験に学び、賢者は歴史に学ぶ。 ─オットー・フォン・ビスマルク

テーマについて

諸君は自らの経験からいくらか学ぶことができるという、全く愚かな考えであろうが、 余はむしろ他人の失敗を学ぶことで、自分の失敗を回避することを好む。

─オットー・フォン・ビスマルク

Nur ein Idiot glaubt, aus den eigenen Erfahrungen zu lernen. Ich ziehe es vor, aus den Erfahrungen anderer zu lernen, um von vorneherein eigene Fehler zu vermeiden.

アンチパターンとは 単なる べからず集 あるある集

では無い

0. 名前 1. 目的 2. アンチパターン 3. アンチパターンの見つけ方 4. アンチパターンを用いても良い場合 5. 解決策

アンチパターンの構成

アンチパターン名

とりあえず 削除フラグ

0. 名前 1. 目的 2. アンチパターン 3. アンチパターンの見つけ方 4. アンチパターンを用いても良い場合 5. 解決策

アンチパターン: とりあえず削除フラグ

目的: データを消さずに、無いことにしたい

エンドユーザから見るとデータが無いことにしたいけど、実際のデータは消したくない

「削除した」データを検索したい

データを消さずにログとして簡単に残したい

誤った操作をなかったことにしたい、すぐに元に戻したい

0. 名前 1. 目的 2. アンチパターン 3. アンチパターンの見つけ方 4. アンチパターンを用いても良い場合 5. 解決策

アンチパターン: とりあえず削除フラグ

CREATE TABLE Bugs ( id SERIAL PRIMARY KEY, bug_code VARCHAR(20) NOT NULL, date_reported DATE NOT NULL, summary VARCHAR(80), description VARCHAR(1000), is_deleted TINYINT(1) DEFAULT '0', ... );

1/true の場合削除されていると見なす

アンチパターン: 削除フラグの導入

アンチパターンのメリット(?)

簡単に元に戻せる気がする なんとなく安心

UPDATE Bugs SET is_deleted = 0 WHERE bug_code = ‘hoge' AND is_deleted = 1

アンチパターンとは何でしょうか。それは、問題の解決を意図しながらも、しばしば他の問題を生じさせてしまうような技法を指します。

─ Bill Karwin

よかれと思って裏目に出てしまうもの

アンチパターンにより起こること

常に WHERE 句が必要 コードが削除フラグだらけ 認識の齟齬を生みやすい

SELECT bug_code, date_reported, summary FROM Bugs WHERE is_deleted = 0

class Bug < ActiveRecord::Base default_scope ->{ where( is_deleted: false ) } …

アンチパターンにより起こること

よかれと思ってコードレベルでデフォルトを変えたらバグがゴロゴロ

SELECT bug_code, date_reported, summary FROM Bugs WHERE is_deleted = 0 ORDER BY id DESC LIMIT 1

アンチパターンにより起こること

データ不整合と 場当たり的クエリの巣窟

アンチパターンにより起こること

削除フラグの立ったデータがテーブルに隠れている

https://www.flickr.com/photos/usoceangov/8290528771

あるエンティティ定義に、論理削除有無を設定する属性が定義されている時点で、開発者は『ああ、この表のデータって削除していいんだ』という暗黙の了解に思考を縛られる

─ @dekasasaki

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

アンチパターンにより起こること

0. 名前 1. 目的 2. アンチパターン 3. アンチパターンの見つけ方 4. アンチパターンを用いても良い場合 5. 解決策

アンチパターン: とりあえず削除フラグ

直面している問題の種類や、メンバー間の会話での何気ない言葉が、そこにアンチパターンがあるかもしれないことに気づくヒントになります。

─ Bill Karwin

アンチパターンの見つけ方

Q: この is_deleted 列はどういう目的で必要なのですか? A: データ上は無い事にしたいけど、実際のデータは消したくないからです (http://qiita.com/Jxck_/items/156d0a231c6968f2a474 より)

Q: なぜこのテーブルにも削除フラグが付いているのですか? A: プロジェクトのルールで、全てのテーブルに削除フラグを定義することになっているんです

Q: この is_deleted2 というカラムは何者ですか? A: ああ、それは管理用フラグです。管理者が非公開設定にしたときに true になります。 is_deleted との組み合わせで表示を制御します

0. 名前 1. 目的 2. アンチパターン 3. アンチパターンの見つけ方 4. アンチパターンを用いても良い場合 5. 解決策

アンチパターン: とりあえず削除フラグ

アンチパターンを用いても良い場合

論理削除(UPDATE)は物理削除(DELETE)よりも大概の場合速い

物理削除(DELETE)と天秤に掛ける際に、高トラフィックのサイトで UPDATE ベースの解を採用することはある

……ただし、できればフラグ以外の実現方法で

0. 名前 1. 目的 2. アンチパターン 3. アンチパターンの見つけ方 4. アンチパターンを用いても良い場合 5. 解決策

アンチパターン: とりあえず削除フラグ

私の経験上は、ユーザーから「論理削除」という言葉を聞いたことがありません。 次のような要件は、聞いたことがあります

•社員が退職(転属)する •(売掛金の回収を諦めて)売上を打ち消す •「お知らせメッセージ」を公開日がくるまで非表示にする •既読メッセージを表示しない •保存期間が過ぎたアンケート結果をオペレーターが見れなくする

─ @ledsun

論理削除フラグという名の死亡フラグ - @ledsun blog http://ledsun.hatenablog.com/entry/2015/03/27/015203

解決策への糸口

解決策1: (問題解決になっていないが)せめて削除日にしてみる

CREATE TABLE Bugs ( id SERIAL PRIMARY KEY, bug_code VARCHAR(20) NOT NULL, date_reported DATE NOT NULL, summary VARCHAR(80), description VARCHAR(1000), deleted_at DATETIME, ... );

Rails の論理削除プラグインの多くがこの設計 (機械的な WHERE 句は減らないが、プラグインに書かせる) しかしカラムに NULL が入るとインデックスを使えないデメリットがある

解決策1: (問題解決になっていないが)せめて削除日にしてみる

CREATE TABLE Bugs ( id SERIAL PRIMARY KEY, bug_code VARCHAR(20) NOT NULL, date_reported DATE NOT NULL, summary VARCHAR(80), description VARCHAR(1000), deleted_at DATETIME, ... );

Rails の論理削除プラグインの多くがこの設計 (機械的な WHERE 句は減らないが、プラグインに書かせる) しかしカラムに NULL が入るとインデックスを使えないデメリットがある

問題解決に なっていない

解決策1: もうちょっとマシに

CREATE TABLE Bugs ( id SERIAL PRIMARY KEY, bug_code VARCHAR(20) NOT NULL, date_reported DATE NOT NULL, summary VARCHAR(80), description VARCHAR(1000), closed_at DATETIME NOT NULL DEFAULT ‘9999-12-31 23:59:59’, ... );

ドメインの言葉(closed_at)を使いつつ、加えてカラムに NOT NULL 制約を付ける(未来日のマジックナンバー)

解決策1: もうちょっとマシに

CREATE TABLE Bugs ( id SERIAL PRIMARY KEY, bug_code VARCHAR(20) NOT NULL, date_reported DATE NOT NULL, summary VARCHAR(80), description VARCHAR(1000), closed_at DATETIME NOT NULL DEFAULT ‘9999-12-31 23:59:59’, ... );

ドメインの言葉(closed_at)を使いつつ、加えてカラムに NOT NULL 制約を付ける(未来日のマジックナンバー)

まだ問題解決に なっていない

“We won’t support soft-delete at all.

If you want to implement a soft-delete alike behaviour its probably a good idea to look into the State pattern instead.”

Doctrine 2 “Behaviours” in a Nutshell

http://www.doctrine-project.org/2010/02/17/doctrine2-behaviours-nutshell.html

解決策2: それはフラグではなく状態である

CREATE TABLE Bugs ( id SERIAL PRIMARY KEY, bug_code VARCHAR(20) NOT NULL, date_reported DATE NOT NULL, summary VARCHAR(80), description VARCHAR(1000), status VARCHAR(20) NOT NULL DEFAULT ‘NEW', ... FOREIGN KEY (status) REFERENCES BugStatus(status) );

“IsDeletedフラグを使う代わりに、Dahan氏はデータの状態を表すフィールドを保持することを提案している。 例えば、有効、中止、キャンセル、廃止予定のような状態だ” http://www.infoq.com/jp/news/2009/09/Do-Not-Delete-

Data

Rails のプラグインでは AASM が便利

解決策2: それはフラグではなく状態である

解決策3: 履歴テーブルに移す

CREATE TABLE BugHistories ( bug_id INTEGER PRIMARY KEY, bug_code VARCHAR(20) NOT NULL, date_reported DATE NOT NULL, summary VARCHAR(80), description VARCHAR(1000), archived_at DATETIME, ... );

二つのテーブルの間の整合性はトリガー等で保つ (詳しくは『理論から学ぶデータベース実践入門』を)

解決策4: そもそも削除も更新もしない

アプリケーションは現実を何かの業務等々の観点で抽象化したものであり、それが扱うデータは事実に忠実にモデル化されたのなら残り続けているはずなのです。現実から事実を消し去ることは不可能。

─ @dekasasaki

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

解決策4: そもそも削除も更新もしない

T字形ER手法というのをベースにしたテーブル設計をしていて、そこでかなり鍛えられたわけですが、その時にはだいたいこのような原則を叩きこまれました。

•テーブルに状態を持たせない •究極には機械が認識するキーと、人間にとって意味のあるデータだけのエンティティだけですべての業務のデータを構成できる •日付を持つデータはイベント(これもひとつのエンティティ) •NULLのデータは絶対に持ってはならない •テーブルはでかく作るな、小さく作れ •テーブル同士の関連は直接持つな、関連を表すテーブルを作れ •1:1の関連になったとしても、イベントとそれに付随するデータは分離しろ •データが増える?金と物理で殴れ(ディスク増強しろ)

─ @mike_neck論理削除が云々について - mike-neckのブログ http://mike-neck.hatenadiary.com/entry/2015/03/24/231422

解決策4: そもそも削除も更新もしない

解決策4: そもそも削除も更新もしない

https://twitter.com/takezoen/status/580147622427537408

解決策4: そもそも削除も更新もしない

http://www.datomic.com/

解決策5: オペミスを防ぐには

「誤った操作をなかったことにしたい、すぐに元に戻したい」の解が無い

これは難しい課題

• 間違えにくい UI を作ったり • 「確認画面」を用意したり

解決策5: 遅延レプリケーションはどうか?

http://dev.mysql.com/doc/refman/5.6/ja/replication-delayed.html

• 「とりあえず」が思考停止 • 全てのテーブルに削除フラグはおかしい

• 「削除」は設計不足を示す • お客様は本当に「削除」と言っているか?

• 「フラグ」以外もある • 状態遷移で考えるほうがマシ • 更新/削除をしない世界もある

• それでもよく考えた末の削除フラグなら OK.

まとめ: とりあえず削除フラグ

まとめ: 二つの世界

Web システムにおける RDBMS はトランザクショナルなキャッシュとしての側面と永続的データストアとしての両面を持っている

企業システムにおける RDBMS は企業活動において発生した事実を余さず記録するトランザクショナルで永続的なデータストアとしての側面が強い

ご清聴ありがとうございました

top related