最初に
社内の開発で、削除機能を実装する際に「カスケード削除」を使用して開発工数を削減しようというような場面に遭遇したので、ここでカスケード削除の特徴やメリット・デメリットや導入の際の検討フローなどをまとめてみました。
ちなみにカスケードには削除と更新がありますが、本記事ではカスケード削除に焦点を当てて説明をします。(所々でカスケード更新についても言及します。)
カスケード削除・更新とは
外部キー制約(参照整合性)におけるオプションの一つで、依存関係を持った親と子のレコード同士の整合性を保つために、親への操作を子のレコードにも一貫して操作を連動させるような仕組みです。
具体的には、親テーブルのレコードを削除または更新したときに、子テーブルの関連するレコードを自動的に削除または更新してくれる機能になります。
カスケード削除・更新のSQLの書き方
FOREIGN KEY
制約(外部キー制約)オプションにおける、ON UPDATE
、DELETE CASCADE
がそれに当たります。
ON UPDATE | DELETE CASCADE オプション
CREATE TABLE child ( id INT, parent_id INT, FOREIGN KEY (parent_id) REFERENCES parent(id) ON UPDATE CASCADE ON DELETE CASCADE );
他の外部キーオプションの種類
例としてMySQLでは、FOREIGN KEY
でON UPDATE
, ON DELETE
を指定しない場合は、デフォルトはRESTRICT
になります。
種類 | 挙動 |
---|---|
SET NULL | 親の削除・更新時、子テーブルの外部キーカラムの値をNULLにする |
RESTRICT | 親の削除・更新時、その行に該当する外部キーカラムのデータを持つレコードが子テーブルにあれば、親の削除・更新を拒否する |
NO ACTION | MySQLではRESTRICTと同等であり、一部のRDBでは遅延チェックが働く |
SET DEFAULT | MySQLではこの定義文は拒否される |
CASCADE | 上記説明を参照 |
外部キーの目的・メリット
そもそもの外部キーの目的・メリットについてですが(これは色々諸説・考え方があるのでDBAとしての主観が入ります)、整合性の保証という点の中で現場での利益として、意図しない関連・親テーブルのデータの削除などの誤操作の際に、DBレイヤーで操作の反映を止めてくれるものという面が(も)大いにあるかと思います。
具体的には、ある親テーブルの行を削除しようとしたとき、「子の行を持っている場合はその行を削除したらおかしくなりますよ・行を間違えてないですか?」、というようなDBからのメッセージのようなものです。
リスクヘッジの一種であり、アプリケーションでの漏れやバグがあっても、DB・ミドルウェアレベルで適宜・自動的に防止してくれるという安心・安全な機能です。
細かい話をすれば子テーブルの行への更新時(Xロックを取るとき)などに親テーブルの該当行へ共有ロック(Sロック)を取ってくれることで整合性を担保してくれるというような面もあります。
一方で、もちろんカスケードなどが使えるというメリットもあります。
カスケード機能がない場合、親と子の一連のレコードを削除したいとき、アプリ側では一生懸命子から順番に親まで削除するロジックを組まないといけなくなり、開発工数は多少は増えるかもしれません。
そこで、親レコードのみ削除してしまえば紐付く子のテーブルも自動的に削除されるような仕組みは、非常に開発が楽になります。
個人的なカスケードに対する考え方
カスケード削除は、DBA目線や運用フェーズを考慮したときにあまりお勧めできる方法ではないと考えていて、導入するならそれだけの理由が必要かと思っています。
カスケード削除のメリット・デメリット
メリット
- 削除処理の実装コストの削減
- =★開発コストの削減
※これも非常に大事なメリットなので、一切否定はしません。
デメリット
- 外部キーの利点である誤削除防止がなくなって、オペミス・実装ミスの防止ができない
- 明示的にDELETEクエリを投げたテーブル以外のテーブル・レコードに関して何を消したか(何が消えたか)わからない
- =★運用コストの増加(リスク増加・ミス時のリカバリコスト増加)
※パフォーマンス劣化という点も挙がるかもしれませんが、それはカスケード削除かどうか以外にも物理削除・論理削除の方式にも関わってくるので、本記事では除外しました。
メリット・デメリットに対する言及
上記メリット・デメリットから判断すると、開発フェーズと運用フェーズのコストのトレードオフというような関係になるかと思われます。
明示的にDELETEクエリを投げたテーブル以外のテーブル・レコードに関して何を消したか(何が消えたか)わからない
デメリットに挙げた1点目は前半で説明した話の繰り返しになるのですが、実は2点目も非常に大きな点になるかと思います。
カスケード削除に頼り切ってしまうと、親のテーブルに削除を投げた際に、どのテーブルのどの行が削除されたのかが明示的にはわからないというのは、開発において思わぬ想定の挙動を生む可能性があります。
それなのであれば、多少コストがかかっても自前で削除ロジックを組み、削除したいテーブル・レコードに対する削除処理を1つずつ書いたほうが安心に繋がります。
また、DBの操作ログ・監査ログからも、該当削除クエリによる影響範囲などが追いづらいという点もあります。
カスケード削除の導入検討フロー
ここまでカスケード削除のメリットやデメリットをお話ししましたが、プロダクトによっては導入するケースも多々あるでしょう。
その場合の検討フローについても書いてみました。
カスケード削除の導入・検討フロー
カスケード削除を導入するかどうか
カスケード削除を導入する場合の検討項目
カスケード削除を導入するかどうか
まずはカスケード削除を導入するかどうか、一般的な目線で検討します。
- カスケードつけるどうかの観点
- そこまで開発コストを削る必要があるか
- 必要があると判断した場合、リスクを伴う運用コストを請け負っても問題ないか
- それか他の案によってリスク低減できるかどうか、その他の案のコストがどれくらいなのか
カスケード削除を導入する場合の検討項目
開発コストを削減したい、または他の理由などでカスケード削除を導入したい場合、どのテーブルに対してカスケードを適用させるかを定義しておくことで、デメリットを軽減することも可能になります。
- 全テーブルか、一部のテーブル(最悪消えたりしてもいいデータのテーブル)か
カスケード「更新」の場合
ここまでカスケード「削除」に焦点を当てて説明をしましたが、カスケード「更新」の場合はどうでしょうか。
カスケード「更新」のリスク
これも主観が入りますが、カスケード更新の場合はまだリスクは低いと考えます。
そのため、採用することによる懸念は削除のケースよりは減る可能性があるかもしれません。(絶対ではない)(推奨するわけではありません)
カスケード「更新」のリスクが低い理由
ここでおさらいをすると、カスケード「更新」というのは、親テーブルのレコードの値(プライマリーキー)が変更されたとき、関連する子テーブルの外部キーカラムの値も自動的に変更する仕組みです。
これがどのようにリスクが低いと考えられるのかは、以下になります。
- データを残しつつ一貫性・整合性を担保できる
- 親テーブルの値を変更しても、子テーブルの値も変更されるので、親子間の関係の連なり(リレーション)は保持される
- そのため、最悪、再度値を変えることで元の状態に戻せる・なんとかなる(可能性がある)
カスケード「更新」が登場するケース
(アンチパターンとも言われますが)サロゲートキーとしてidなどの自動採番の数値を主キーとする設計も現場では多く(ORMを使っているケースだったり、セカンダリインデックスにプライマリーキーが含まれることによるインデックスサイズの増加を懸念したり)、その様な場合に主キーの値の変更はそこまであるケースではないため、カスケード更新自体が登場する機会は少ないかもしれません。
しかしサロゲートキーを用いず、例えばログインID(文字列)を主キー(ナチュラルキー)にした場合は、ログインIDの変更のタイミングで各子のテーブルにも更新が必要となり、カスケード更新が検討されることもあります。(こういう場合は、カスケード更新は非常に便利だと思います。)
かといって更新の場合でも、やはり使わないに越したことはないと思っています。
(もしかしたら上記のログインIDみたいな絶対的な情報の変更ケースでは使ったほうがいいのかもしれませんが・・・そうではない例も含めてこう書いてます。)
カスケードにおける削除も更新も、本質的には内容は変わらないですし、マクロな視点で見るとデメリットも解消されるわけではありません。
ただ、大事なことなのですが、カスケード機能自体に対して一切の否定はありません。すごく便利です。ケースバイケースであり、最適である場合もあるかと思います。
最後に
賛否両論はあるかとは思いますが、あくまで運用フェーズでのリスクやDBAとしての目線で考えてみて、自分の中でのセオリーをまとめてみました。