💬

【Laravel】複合キーを使用している場合に、Eloquentが2回DELETEを発行する問題と対策

に公開

はじめに

複合主キーを設定しているモデルに対して、whereで条件を指定しdelete()で削除処理を行った際に、意図しない DELETE クエリが追加で発行されてしまう現象に遭遇しました。
原因の特定と解決に時間を要したため、備忘録として記録に残します。

現象

複合主キーを持つテーブルに対して、order_noitem_nowhere条件で指定し、delete()を実行しました。

$model = MstDevice::where('order_no', $order_no)
    ->where('item_no', $item_no)
    ->first();

if ($model) {
    $model->delete();
}

発生した現象

  • 1つ目の DELETE クエリ(意図した削除)
    WHERE order_no = ? AND item_no = ?

  • 2つ目の DELETE クエリ(意図しない削除)
    WHERE order_no = ?

order_noのみを条件にした DELETE クエリが追加で発行され、意図しない範囲のレコードが削除されてしまいました。

Eloquentが意図しないDELETEを発行する理由

1. Eloquentは複合主キーをサポートしていない

Laravel の Eloquent は単一の主キーを前提に設計されており、$primaryKeyに複数のカラムを指定できません。
このため、Eloquent のdelete()はモデルの主キーのみを条件に削除を行います。

protected $primaryKey = ['order_no', 'item_no']; // Laravel標準では無効

この設定は Laravel では無効なため、Eloquent はorder_noだけを主キーとして認識し、item_noを無視する可能性があります。

2. delete()の内部動作

Eloquent のdelete()は、以下の手順で削除処理を行います。

  1. whereで条件を指定し、first()でモデルを取得する。
  2. 取得したモデルのprimaryKeyを元に DELETE クエリを発行する。
  3. $primaryKeyorder_noのみと認識されるため、WHERE order_no = ?だけで削除される。

この仕様により、意図しないレコード削除が発生します。

対策

1. 明示的にwhere条件を指定して削除する

delete()モデルのインスタンスで実行せず、クエリビルダで直接削除を行います。

MstDevice::where('order_no', $order_no)
    ->where('item_no', $item_no)
    ->delete();

この方法であれば、意図しない DELETE クエリが発行されることはありません。

2. getKeyName() の設定を適切に行う(非推奨)

一応、以下のようにgetKeyName()をオーバーライドすれば、複合主キーのように扱うことも可能です。

public function getKeyName()
{
    return ['order_no', 'item_no'];
}

※ただし、Eloquent は複合キーを標準サポートしていないため、別の予期しない挙動を引き起こす可能性があります。whereを明示的に指定する方法の方が安全です。

まとめ

方法 説明 推奨度
$model->delete() primaryKeyが単一キーと誤認される ×
Model::destroy() primaryKeyが単一キーと誤認される ×
Model::find() primaryKeyが単一キーと誤認される ×
Model::where()->delete() 安全に削除可能
getKeyName() を複合キーにする Laravel標準では非推奨

推奨される削除方法

MstDevice::where('order_no', $order_no)
    ->where('item_no', $item_no)
    ->delete();

Laravel では複合主キーをサポートしていないため、明示的にwhere条件を指定して削除するのが安全です。
Eloquent の仕様を理解し、意図しないレコード削除を防ぎましょう!

Discussion