0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

#43.物理削除と論理削除の違いを整理

Posted at

はじめに

こんにちは、nayaaaaです。
今回は物理削除と論理削除についてまとめてみました。
Railsでの動きを実際に確認しながら確認してみます。

物理削除、論理削除

イメージ図のようになっております。

image.png

物理削除
データベースからレコードそのものを完全に削除する。destroy などで行うと、DB上からデータが消え、取得できなくなる。

論理削除
実際にはレコードを残したまま、フラグを立てて「削除されたように見せる」仕組み。
そのため、削除後もデータ自体は残っており、後からリストア(復元)することも可能。

物理削除

practice(dev)> article = Article.create!(title: "test")
  TRANSACTION (0.1ms)  BEGIN immediate TRANSACTION /*application='Practice'*/
  Article Create (4.9ms)  INSERT INTO "articles" ("title", "body", "deleted_at", "created_at", "updated_at") VALUES ('test', NULL, NULL, '2025-08-17 01:49:55.076870', '2025-08-17 01:49:55.076870') RETURNING "id" /*application='Practice'*/
  TRANSACTION (0.4ms)  COMMIT TRANSACTION /*application='Practice'*/
=> 
#<Article:0x000000012cb9dac0
...

practice(dev)> article = Article.find_by(id: article.id)
  Article Load (6.1ms)  SELECT "articles".* FROM "articles" WHERE "articles"."id" = 9 LIMIT 1 /*application='Practice'*/
=> 
#<Article:0x000000012cbbb7c8
...

practice(dev)> article.destroy 
  TRANSACTION (0.1ms)  BEGIN immediate TRANSACTION /*application='Practice'*/
  Article Destroy (1.7ms)  DELETE FROM "articles" WHERE "articles"."id" = 9 /*application='Practice'*/
  TRANSACTION (1.1ms)  COMMIT TRANSACTION /*application='Practice'*/
=> 
#<Article:0x000000012cbbb7c8
 id: 9,
 title: "test",
 body: nil,
 deleted_at: nil,
 created_at: "2025-08-17 01:49:55.076870000 +0000",
 updated_at: "2025-08-17 01:49:55.076870000 +0000">
practice(dev)> article = Article.find_by(id: article.id)
  Article Load (0.5ms)  SELECT "articles".* FROM "articles" WHERE "articles"."id" = 9 LIMIT 1 /*application='Practice'*/
=> nil

実際にDELETE FROM "articles" が発行され、データベースから完全に削除されます。

論理削除

practice(dev)> a = Article.create!(title: "test")
  TRANSACTION (0.3ms)  BEGIN immediate TRANSACTION /*application='Practice'*/
  Article Create (2.9ms)  INSERT INTO "articles" ("title", "body", "deleted_at", "created_at", "updated_at") VALUES ('test', NULL, NULL, '2025-08-16 23:41:46.723211', '2025-08-16 23:41:46.723211') RETURNING "id" /*application='Practice'*/
  TRANSACTION (0.8ms)  COMMIT TRANSACTION /*application='Practice'*/
=> 
#<Article:0x000000012cbb7308
...

practice(dev)> Article.alive.count
  Article Count (0.3ms)  SELECT COUNT(*) FROM "articles" WHERE "articles"."deleted_at" IS NULL /*application='Practice'*/
=> 1

practice(dev)> a.soft_delete!
  TRANSACTION (0.2ms)  BEGIN immediate TRANSACTION /*application='Practice'*/
  Article Update (1.4ms)  UPDATE "articles" SET "deleted_at" = '2025-08-16 23:43:59.140462', "updated_at" = '2025-08-16 23:43:59.141716' WHERE "articles"."id" = 6 /*application='Practice'*/
  TRANSACTION (0.4ms)  COMMIT TRANSACTION /*application='Practice'*/
=> true

practice(dev)> Article.alive.count
  Article Count (0.3ms)  SELECT COUNT(*) FROM "articles" WHERE "articles"."deleted_at" IS NULL /*application='Practice'*/
=> 0

practice(dev)> Article.deleted.count
  Article Count (0.3ms)  SELECT COUNT(*) FROM "articles" WHERE "articles"."deleted_at" IS NOT NULL /*application='Practice'*/
=> 1

practice(dev)> a.soft_deleted?
=> true

deleted_atカラムに値が入り非表示扱いになリます。

practice(dev)> Article.all
  Article Load (1.2ms)  SELECT "articles".* FROM "articles" /* loading for pp */ LIMIT 11 /*application='Practice'*/
=> 
[#<Article:0x000000012cbbcf88
  id: 6,
  title: "test",
  body: nil,
  deleted_at: "2025-08-16 23:43:59.140462000 +0000",
  created_at: "2025-08-16 23:41:46.723211000 +0000",
  updated_at: "2025-08-16 23:43:59.141716000 +0000">]

データ自体は残っているので、allで確認すると、削除済みでもレコード自体は残っていることが分かります。

リストアしてみる

practice(dev)> a.restore!
  TRANSACTION (1.3ms)  BEGIN immediate TRANSACTION /*application='Practice'*/
  Article Update (28.9ms)  UPDATE "articles" SET "deleted_at" = NULL, "updated_at" = '2025-08-17 00:29:06.214015' WHERE "articles"."id" = 6 /*application='Practice'*/
  TRANSACTION (0.4ms)  COMMIT TRANSACTION /*application='Practice'*/
=> true

practice(dev)> Article.alive.count
  Article Count (6.1ms)  SELECT COUNT(*) FROM "articles" WHERE "articles"."deleted_at" IS NULL /*application='Practice'*/
=> 1

practice(dev)> Article.deleted.count
  Article Count (0.3ms)  SELECT COUNT(*) FROM "articles" WHERE "articles"."deleted_at" IS NOT NULL /*application='Practice'*/
=> 0

practice(dev)> a.soft_deleted? 
=> false

データ自体は残っているので、先述した通りリストア(復元)も可能です。

複数レコードの場合
複数のレコードを保持している場合は、以下のようにfindを使用して指定しましょう

practice(dev)> Article.alive.pluck
  Article Pluck (0.6ms)  SELECT "articles".* FROM "articles" WHERE "articles"."deleted_at" IS NULL /*application='Practice'*/
=> 
[[6,
  "test",
  nil,
  nil,
  2025-08-16 23:41:46.723211000 UTC +00:00,
  2025-08-17 00:29:06.214015000 UTC +00:00],
 [7,
  "test",
  nil,
  nil,
  2025-08-17 00:48:25.173997000 UTC +00:00,
  2025-08-17 00:48:25.173997000 UTC +00:00]]
practice(dev)> a = Article.find(6)
  Article Load (0.3ms)  SELECT "articles".* FROM "articles" WHERE "articles"."id" = 6 LIMIT 1 /*application='Practice'*/
=> 
#<Article:0x000000012cbbdc08
...

practice(dev)> a.soft_delete!
  TRANSACTION (0.1ms)  BEGIN immediate TRANSACTION /*application='Practice'*/
  Article Update (1.7ms)  UPDATE "articles" SET "deleted_at" = '2025-08-17 00:56:06.342790', "updated_at" = '2025-08-17 00:56:06.343718' WHERE "articles"."id" = 6 /*application='Practice'*/
  TRANSACTION (0.8ms)  COMMIT TRANSACTION /*application='Practice'*/
=> true

practice(dev)> Article.alive.count
  Article Count (0.3ms)  SELECT COUNT(*) FROM "articles" WHERE "articles"."deleted_at" IS NULL /*application='Practice'*/
=> 1

コールバック(before_destroy/after_destroy)

オブジェクトの削除時(レコードの削除)以下を実行。

  • before_destroy

レコードが破棄される前に呼び出されるコールバックを登録します。

  • after_destroy

レコードが破棄された後に呼び出されるコールバックを登録します。

これらを設定ファイルに記述した場合は、コールバックを呼び出し際に、以下の順番で呼び出されます。

記述されていない場合はスキップされます。

公式ドキュメントからの引用

3 利用可能なコールバック
Active Recordで利用可能なコールバックの一覧を以下に示します。これらのコールバックは、実際の操作中に呼び出される順序に並んでいます。
【省略】
3.3 オブジェクトのdestroy
before_destroy
around_destroy
after_destroy

公式ドキュメント

まとめ

  • 物理削除はデータを完全に削除、論理削除はフラグで「非表示化」。
  • 論理削除ならデータを復元できるが、条件指定を忘れると「削除済み」も参照してしまう点に注意。
  • コールバックを使えば削除時に処理を挟めることができる。

学びながら、意外と覚えておくと使い分けに役立つのかなと感じました。
以上です。ありがとうございました!!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?