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?

プライマリーキー制約がないテーブルでEloquentを使うとどうなるか

Posted at

はじめに

開発中に面白い現象に遭遇したので残しておく。

Eloquentでレコードを1件だけ削除したはずが、複数件削除されてしまった問題が発生して非常に困った。
この記事では、Eloquentの内部実装を追いながら解説する。

問題のコード

以下は、nameで絞り込んだレコードを1件取得して削除するコード。

$deleteTarget = $model->where('name', $name)->first();
$deleteTarget->delete();

どう見ても1件のレコードを削除するだけのコードだが、実際に実行すると複数のレコードが削除された。

なぜこんな現象が発生するのか。

原因の調査

Eloquentのdelete()メソッドの内部実装を調べてみた。

Laravel 10のEloquentでは、first()で取得したモデルインスタンスのdelete()は、インスタンス取得時のwhere条件を使わず、インスタンスが持つプライマリーキー値のみを使って削除処理を実行する。

つまり、where('name', $name)で絞り込んでも、実際の削除処理ではWHERE プライマリーキー = 値という条件に置き換わる。

ここでDBの設計が怪しいことが分かる。

// Laravel 10.x の Illuminate\Database\Eloquent\Model より抜粋
// https://github.com/laravel/framework/blob/10.x/src/Illuminate/Database/Eloquent/Model.php

namespace Illuminate\Database\Eloquent;

abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToString, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable

public function delete()
{
    // 省略
    $this->performDeleteOnModel();
    return true;
}

protected function performDeleteOnModel()
{
    $this->setKeysForSaveQuery($this->newModelQuery())->delete();
    $this->exists = false;
}

protected function setKeysForSaveQuery($query)
{
    $query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery());
    return $query;
}

public function getKeyName()
{
    return $this->primaryKey;
}

問題の根本原因

テーブルを確認すると原因が見えてきた。

状況の整理:

  • データベースのテーブルにはプライマリーキー制約が設定されていない
  • しかし、Eloquentモデルではidカラムをプライマリーキーとして指定している
  • 実際のテーブルでは、このidカラムに重複する値が存在している

テーブルにプライマリーキー制約がなく、Eloquentモデルで指定しているidカラムに重複値が存在する場合、WHERE id = 値という条件で複数レコードがヒットしてしまう。

結果として、delete()を実行すると、そのid値を持つすべてのレコードが削除される。

以上のようなテーブル構造とモデル設定の不整合が、今回の予期しない動作の原因だった。

応急対処法

今回は一時的な対処として、以下のようにクエリビルダーを使って明示的に条件を指定する方法で対応した。

Model::query()->where('id', $id)->where('name', $name)->delete();

この方法では、Eloquentのモデルインスタンスを通さずに、指定した条件に合致するレコードのみを削除できるため、想定通りの動作になる。

類似の問題

この問題はdelete()だけでなく、update()save()などの他のCRUD操作でも同様に発生する可能性がある。
いずれもEloquentがモデルインスタンスのプライマリーキーを基準として処理を行うため。

特に注意が必要な環境:

  • データベースとアプリケーションが1対多の関係(複数のチームやシステムが同じDBを使用)
  • モデルの設定がテーブル構造を正しく反映していない環境
  • チーム開発でのモデル設定の反映漏れが発生しやすい環境

根本的な解決策

1. テーブル構造の修正(推奨)

可能であれば、テーブルに適切なプライマリーキー制約を設定する

2. 明示的なクエリの使用

重要な処理では、クエリビルダーを使って明示的に条件を指定する

まとめ

ORMは便利だけど、特徴を理解して設計、実装する必要がある。

重要なポイント:

  • フレームワークの自動的な処理を過信せず、動作原理を理解する
  • データベース設計とモデル設定の整合性を保つ
  • 重要な処理では必ずテスト環境で動作確認を行う
  • 必要に応じてソースコードを読み、内部実装を理解する
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?