はじめに
前回の記事では、インデックス設計について説明しました。
データベースでは、データを正しく保存するだけでなく、
- どのように検索するか
- どのように更新するか
- どのように削除するか
といった、実際の運用を考えた設計も重要になります。
今回はその中でも、データの削除について見ていきます。
アプリケーション上では、ユーザーが「削除」ボタンを押せばデータが消えたように見えます。
しかし、データベース上でも本当にそのデータを削除するとは限りません。
例えば、ユーザーを削除するとき、
DELETE FROM users
WHERE id = 1;
のようにデータそのものを削除する方法があります。
一方で、
deleted_at = 2026-09-09 10:00:00
のように、「このユーザーは削除済みである」という情報だけを記録し、データそのものは残しておく方法もあります。
この2つは、それぞれ
- 物理削除
- 論理削除
と呼ばれます。
今回は、
- 論理削除とは何か
- 物理削除とは何か
- それぞれのメリット・デメリット
-
deleted_atとis_deleted - 論理削除を使いすぎると何が起こるのか
について、実務を意識しながら説明していきます。
この記事の対象者
この記事は、次のような人を対象としています。
- エンジニアだけど、データベースについてあまり詳しくない
- データベース設計の経験が浅い
- 「論理削除」という言葉は聞いたことがあるが、なぜ使うのか分からない
- とりあえず
is_deletedを付けている - 物理削除と論理削除をどう使い分ければよいのか知りたい
特に、
削除するデータは全部論理削除にしておけば安全なのでは?
と考えている人には、ぜひ読んでもらいたい内容です。
データの「削除」とは
まず、簡単な users テーブルを考えてみます。
users
-------------------------
id
name
email
created_at
例えば、次のようなユーザーが登録されているとします。
| id | name | |
|---|---|---|
| 1 | 田中太郎 | tanaka@example.com |
| 2 | 鈴木花子 | suzuki@example.com |
ここで、id = 1 の田中さんがサービスを退会したとします。
このとき、データベース上での対応方法は大きく2つあります。
1つは、
田中さんのレコードそのものを削除する
方法です。
もう1つは、
レコードは残したまま、「削除済み」という状態にする
方法です。
前者が物理削除、後者が論理削除です。
物理削除とは
物理削除とは、データベースからレコードそのものを削除する方法です。
例えば、次のSQLです。
DELETE FROM users
WHERE id = 1;
実行前が、
users
---------------------------------------
id | name | email
---------------------------------------
1 | 田中太郎 | tanaka@example.com
2 | 鈴木花子 | suzuki@example.com
だった場合、実行後は、
users
---------------------------------------
id | name | email
---------------------------------------
2 | 鈴木花子 | suzuki@example.com
となります。
id = 1 のデータは、users テーブルからなくなります。
このように、
データそのものをデータベースから削除する
のが物理削除です。
物理削除のメリット
データが増え続けない
物理削除では、不要になったデータそのものを削除します。
そのため、削除済みのデータがテーブル内に残り続けることがありません。
例えば、100万人が登録し、そのうち50万人が退会した場合を考えてみます。
物理削除を行えば、単純化するとテーブルに残るユーザーは50万人です。
一方、論理削除では退会した50万人分のレコードも残るため、100万人分のデータを管理し続けることになります。
大量のデータを扱う場合、この違いは無視できなくなることがあります。
SELECT文が分かりやすい
物理削除されたデータはそもそもテーブルに存在しないため、
SELECT *
FROM users;
とするだけで、現在存在しているユーザーを取得できます。
論理削除のように、
WHERE deleted_at IS NULL
といった条件を毎回考える必要がありません。
そのため、SQLやアプリケーションの実装を比較的シンプルに保ちやすいというメリットがあります。
物理削除のデメリット
一度削除すると元に戻すのが難しい
物理削除の大きなデメリットは、
削除したデータを簡単には元に戻せない
ことです。
例えば、ユーザーが誤って退会ボタンを押してしまい、
DELETE FROM users
WHERE id = 1;
を実行したとします。
レコードそのものが削除されているため、
やっぱり元に戻したい
となっても、単純な UPDATE では復元できません。
バックアップや別の履歴データなどから復元する必要があります。
関連データへの影響を考える必要がある
例えば、次のような関係があるとします。
ユーザー
|
└── 注文
|
└── 注文商品
ユーザーを物理削除するとき、
DELETE FROM users
WHERE id = 1;
だけでよいとは限りません。
そのユーザーに紐づく注文データをどうするのか考える必要があります。
例えば、
users
1
|
+--- orders
101
102
という状態でユーザーだけを削除すると、
「存在しないユーザーの注文」
が残ってしまう可能性があります。
外部キー制約が設定されていれば削除自体が拒否される場合もあります。
そのため物理削除では、
関連するデータをどこまで削除するのか
を設計段階で考える必要があります。
論理削除とは
論理削除とは、レコードそのものは削除せず、
削除された状態である
という情報を保存する方法です。
例えば、users テーブルに次のカラムを追加します。
users
-------------------------
id
name
email
deleted_at
created_at
通常のユーザーは、
deleted_at = NULL
とします。
そしてユーザーを削除するとき、
UPDATE users
SET deleted_at = '2026-09-09 10:00:00'
WHERE id = 1;
のように更新します。
すると、
users
----------------------------------------------------------
id | name | email | deleted_at
----------------------------------------------------------
1 | 田中太郎 | tanaka@example.com | 2026-09-09 10:00:00
2 | 鈴木花子 | suzuki@example.com | NULL
となります。
田中さんのレコードそのものは残っています。
しかし、
deleted_at に値が入っている
ため、
このユーザーは削除済み
と判断できます。
これが論理削除です。
論理削除したデータを検索から除外する
論理削除ではデータそのものが残っているため、
SELECT *
FROM users;
とすると、削除済みのユーザーも取得されます。
そのため通常は、
SELECT *
FROM users
WHERE deleted_at IS NULL;
のようにします。
つまり、
deleted_at = NULL
↓
現在有効なデータ
deleted_at に値がある
↓
削除済みデータ
という扱いです。
アプリケーション上では削除済みユーザーを表示しないため、
WHERE deleted_at IS NULL
という条件を付けることになります。
論理削除のメリット
データを復元しやすい
論理削除ではデータそのものが残っています。
そのため、削除を取り消したい場合、
UPDATE users
SET deleted_at = NULL
WHERE id = 1;
とすることで、比較的簡単に復元できます。
例えば、
- ユーザーの退会取り消し
- 管理者による誤削除
- 一時的な非表示
- 削除後一定期間だけ復元可能にする
といった要件では便利です。
削除された日時を記録できる
deleted_at を利用する場合、
2026-09-09 10:00:00
のように削除日時を保存できます。
そのため、
いつ削除されたのか
という情報も残せます。
例えば、
SELECT *
FROM users
WHERE deleted_at < '2026-08-01';
とすれば、一定期間以上前に削除されたデータを探すこともできます。
これは、
論理削除してから30日経過したデータを物理削除する
といった処理にも利用できます。
論理削除のデメリット
毎回削除済みデータを除外する必要がある
論理削除で最も分かりやすい問題がこれです。
例えば、
SELECT *
FROM users;
では、削除済みユーザーまで取得されます。
そのため、
SELECT *
FROM users
WHERE deleted_at IS NULL;
とする必要があります。
簡単な例では大した問題に見えません。
しかし、テーブル同士を結合するようになると複雑になります。
例えば、
SELECT *
FROM users
JOIN orders
ON users.id = orders.user_id
WHERE users.deleted_at IS NULL;
といった形です。
さらに orders も論理削除している場合、
SELECT *
FROM users
JOIN orders
ON users.id = orders.user_id
WHERE users.deleted_at IS NULL
AND orders.deleted_at IS NULL;
となります。
論理削除するテーブルが増えるほど、
削除済みデータを除外する条件
を意識する必要があります。
削除済みデータが増え続ける
論理削除ではデータそのものを削除しません。
そのため、
有効データ
削除済みデータ
有効データ
削除済みデータ
削除済みデータ
有効データ
...
のように、テーブル内に削除済みデータが蓄積していきます。
サービスを長期間運用すればするほど、このデータは増える可能性があります。
その結果、
- テーブルサイズが大きくなる
- インデックスも大きくなる
- バックアップ対象のデータ量が増える
- 検索やメンテナンスへの影響が出る
といった問題につながる場合があります。
deleted_at と is_deleted
論理削除を実装するとき、よく利用される方法として、
deleted_atis_deleted
があります。
それぞれ見てみます。
is_deleted を使う方法
例えば、次のようにします。
users
-------------------------
id
name
email
is_deleted
created_at
値は、
is_deleted = false
なら通常のデータ、
is_deleted = true
なら削除済みとします。
例えば、
UPDATE users
SET is_deleted = true
WHERE id = 1;
です。
検索するときは、
SELECT *
FROM users
WHERE is_deleted = false;
とします。
考え方としては非常に分かりやすい方法です。
deleted_at を使う方法
一方、deleted_at を使う場合は、
deleted_at = NULL
なら通常のデータ、
deleted_at = 2026-09-09 10:00:00
なら削除済みとします。
例えば、
UPDATE users
SET deleted_at = '2026-09-09 10:00:00'
WHERE id = 1;
です。
検索するときは、
SELECT *
FROM users
WHERE deleted_at IS NULL;
とします。
deleted_at と is_deleted はどちらがよいのか
どちらでも論理削除は実現できます。
ただし、deleted_at には、
削除済みかどうか
だけでなく、
いつ削除されたのか
も記録できるという特徴があります。
例えば、
is_deleted = true
だけでは、
いつ削除されたのか
は分かりません。
一方、
deleted_at = 2026-09-09 10:00:00
なら、
- 削除されている
- 2026年9月9日10時に削除された
という2つの情報を持っています。
そのため、削除日時も必要なのであれば deleted_at の方が扱いやすいケースがあります。
両方持てばよいのか
では、
is_deleted
deleted_at
の両方を持てばよいのでしょうか。
例えば、
is_deleted = true
deleted_at = NULL
という状態になったらどうでしょう。
is_deleted を見ると削除済みですが、
deleted_at を見ると削除されていないようにも見えます。
逆に、
is_deleted = false
deleted_at = 2026-09-09 10:00:00
という状態になる可能性もあります。
つまり、同じ意味を持つ情報を複数管理すると、
どちらが正しい状態なのか
という問題が生まれます。
特別な理由がなければ、
deleted_at が NULLかどうか
だけで状態を判断するなど、一つの情報から判定できる設計の方がシンプルです。
論理削除を乱用すると起きる問題
論理削除には、
- データを復元できる
- 過去のデータを残せる
- 誤削除に対応しやすい
といったメリットがあります。
そのため、
とりあえず全部論理削除にしておこう
と考えたくなるかもしれません。
しかし、論理削除をすべてのテーブルに導入すると、設計が複雑になる場合があります。
WHERE deleted_at IS NULL だらけになる
例えば、
users
orders
order_items
products
addresses
payments
すべてのテーブルを論理削除するとします。
すると、検索するときにそれぞれ、
deleted_at IS NULL
を考えなければならなくなります。
例えば複数テーブルをJOINする場合、
SELECT
users.name,
orders.id,
products.name
FROM users
JOIN orders
ON users.id = orders.user_id
JOIN order_items
ON orders.id = order_items.order_id
JOIN products
ON order_items.product_id = products.id
WHERE users.deleted_at IS NULL
AND orders.deleted_at IS NULL
AND order_items.deleted_at IS NULL
AND products.deleted_at IS NULL;
のようになる可能性があります。
当然、SQLは複雑になります。
そして、
AND products.deleted_at IS NULL
を1つ書き忘れただけで、
削除済みの商品が画面に表示される
といった不具合につながる可能性もあります。
UNIQUE制約との組み合わせにも注意する
論理削除では、レコードそのものは残っています。
そのため、第6回で説明した UNIQUE制約 との組み合わせにも注意が必要です。
例えば、
users
-------------------------
id
email
deleted_at
があり、email にUNIQUE制約が設定されているとします。
id | email | deleted_at
-----------------------------------------------
1 | tanaka@example.com | 2026-09-09 10:00:00
田中さんはすでに退会しています。
しかし、このレコード自体は残っています。
その状態で新しいユーザーが、
tanaka@example.com
を使って登録しようとすると、UNIQUE制約に引っかかる可能性があります。
アプリケーション上では、
退会済みだから存在しないユーザー
として扱っていても、データベース上にはまだ存在するからです。
つまり、
論理削除したデータをUNIQUE制約上どう扱うのか
も設計時に考える必要があります。
この部分は、使用するデータベースによって実現方法が異なる場合があります。
「履歴を残したい=論理削除」とは限らない
論理削除を使う理由として、
過去の履歴を残したいから
というものがあります。
しかし、
履歴を残したい
という要件と、
削除済みデータを同じテーブルに残しておきたい
という要件は、必ずしも同じではありません。
例えば、ユーザーの操作履歴を残したいのであれば、
users
に削除済みユーザーをずっと残すのではなく、
user_logs
のような履歴用テーブルを設計する方法もあります。
また、
退会ユーザー
という概念そのものを保存したいのであれば、別の履歴管理方法を検討することもできます。
つまり、
データを残したいから論理削除
と単純に考えるのではなく、
なぜそのデータを残す必要があるのか
を考えることが重要です。
論理削除と物理削除を組み合わせる
実際のシステムでは、
最初は論理削除し、一定期間後に物理削除する
という方法もあります。
例えば、
ユーザー退会
↓
論理削除
deleted_at = 現在日時
↓
30日間保持
↓
30日経過
↓
物理削除
という流れです。
この場合、
ユーザーが誤って退会した場合には30日以内なら復元できます。
一方で、長期間経過した不要なデータは物理削除するため、削除済みデータが永遠に増え続けることも防げます。
例えば、
DELETE FROM users
WHERE deleted_at < '2026-08-01';
のような処理を定期的に行うイメージです。
もちろん実際には、関連するテーブルや業務要件を確認したうえで削除する必要があります。
どちらを選べばよいのか
物理削除と論理削除のどちらを使うべきかは、システムの要件によって変わります。
例えば、
物理削除を検討しやすいケース
- 削除後に復元する必要がない
- 不要なデータを残したくない
- データ量をなるべく増やしたくない
- 個人情報など、一定条件で本当に削除する必要がある
論理削除を検討しやすいケース
- 誤削除から復元したい
- 一定期間、データを復元可能にしたい
- 削除日時を管理したい
- 業務上、削除済みデータを参照する必要がある
といった違いがあります。
重要なのは、
論理削除の方が安全だから
という理由だけで決めないことです。
削除したデータを残すのであれば、
なぜ残す必要があるのか
を説明できる設計にすることが大切です。
物理削除と論理削除の比較
最後に整理すると、次のようになります。
| 項目 | 物理削除 | 論理削除 |
|---|---|---|
| データ | 削除する | 残す |
| DELETE文 | 使用する | 基本的にUPDATE |
| 復元 | 難しい | 比較的容易 |
| 削除日時 | 別途記録が必要 |
deleted_at で管理可能 |
| SELECT | シンプル | 削除済み除外が必要 |
| データ量 | 減る | 増え続ける可能性がある |
| 設計・実装 | 比較的シンプル | 複雑になりやすい |
どちらにもメリットとデメリットがあります。
そのため、
物理削除が正解
でも、
論理削除が正解
でもありません。
システムの要件に応じて選択することになります。
まとめ
今回は、データベースにおける論理削除と物理削除について説明しました。
物理削除は、
DELETE FROM users
WHERE id = 1;
のように、データそのものを削除する方法です。
一方、論理削除は、
deleted_at = 2026-09-09 10:00:00
のように削除された状態を記録し、データそのものは残しておく方法です。
論理削除はデータを復元しやすいというメリットがありますが、
-
WHERE deleted_at IS NULLが必要になる - 削除済みデータが増え続ける
- JOINが複雑になる
- UNIQUE制約との組み合わせを考える必要がある
といった問題もあります。
特に重要なのは、
「削除する可能性があるテーブルには、とりあえず論理削除を付ける」
と考えないことです。
まず、
本当に削除済みデータを残しておく必要があるのか
を考えます。
そのうえで、
物理削除
論理削除
論理削除後、一定期間経過したら物理削除
など、システムに合った方法を選択することが重要です。
データベース設計では、データをどのように保存するかだけでなく、
データが不要になったときに、どのように扱うのか
まで考える必要があります。