第20回: 論理削除とソフトデリート — データを「消す」のではなく「隠す」設計

章: 第2章: データベース応用

DELETE FROM posts WHERE id = 1 —— 本当に消してしまっていいですか?

ユーザーが投稿を削除したあと、「やっぱり復元したい」と言われたら?法的な理由でデータを保持しなければならない場合は?物理削除(レコードの完全消去)は取り返しがつきません。

論理削除(ソフトデリート)は deleted_at というタイムスタンプを使って「削除済み」フラグを立てるだけで、レコード自体はDBに残り続けます。

ソフトデリートの実装


<?php
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model {
    use SoftDeletes;
}
// 削除(deleted_atに日時が入る)
Post::find(1)->delete();
// 削除済みを含めて取得
Post::withTrashed()->get();
// 復元
Post::withTrashed()->find(1)->restore();

物理削除 vs 論理削除

観点 物理削除(DELETE) 論理削除(ソフトデリート)
データの復元 不可能 restore() で復元できる
監査ログ 記録が消える deleted_at で削除日時が残る
通常の取得 削除済みは取得されない 同じ(deleted_at IS NULLで絞られる)
DBの肥大化 起きない 削除データが蓄積する

チェックポイント: SoftDeletes トレイトを使うと、Post::all() は自動的に deleted_at IS NULL の条件が付きます。削除済みを取得したいときは明示的に withTrashed() が必要です。

マイグレーションでの設定


<?php
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->timestamps();
    $table->softDeletes(); // deleted_at カラムを追加
});

完全削除が必要なとき


<?php
// 論理削除(通常の削除)
$post->delete();

// 物理削除(本当に消す)
$post->forceDelete();

チェックポイント: GDPRなどの規制対応で「ユーザーのデータを完全削除する」必要があるときは forceDelete() を使いますが、通常はソフトデリートを優先しましょう。

まとめ & 次のステップ

  • 論理削除はレコードを消さずに deleted_at タイムスタンプで「削除済み」を表現する
  • SoftDeletes トレイトで通常のクエリは自動的に削除済みを除外する
  • 復元・監査・規制対応など、データを保持する理由は実務で頻出する

次回から第3章: RESTful API設計に入ります。まずREST設計の基本として、リソースとHTTPメソッドを使ったAPI設計の原則を学びます。

Related Articles