Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Laravel Eloquentで複数レコードが削除された話

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Rei Rei
July 25, 2025

Laravel Eloquentで複数レコードが削除された話

Avatar for Rei

Rei

July 25, 2025
Tweet

More Decks by Rei

Other Decks in Programming

Transcript

  1. 問題のあるDB構成 元々のテーブル設計(独自プライマリーキー) CREATE TABLE product_reviews ( review_id INT, -- プライマリーキー制約なし!

    product_id INT, user_name VARCHAR(100), rating INT, comment TEXT, created_at TIMESTAMP ); 実際のデータ review_id product_id user_name rating comment 1 123 田中太郎 5 素晴らしい商品です 1 456 佐藤花子 4 普通に良いです 3
  2. Eloquentモデルの設定 class ProductReview extends Model { protected $table = 'product_reviews';

    protected $primaryKey = 'review_id'; // 独自のプライマリーキーを指定 public $incrementing = false; // AUTO_INCREMENTではない public $timestamps = false; } 問題: Eloquentは review_id をプライマリーキーとして認識しているが、DB側では制約がな い 4
  3. 何が起こったのか? Eloquentの delete() メソッドの内部動作: 1. 検索時: WHERE product_id = 123

    AND user_name = '田中太郎' 2. 取得: review_id = 1 のレコードを取得 3. 削除時: WHERE review_id = 1 のみで削除実行 4. 結果: review_id = 1 を持つすべてのレコードが削除される // Eloquent内部で実行されるクエリ DELETE FROM product_reviews WHERE review_id = 1; → 田中太郎と佐藤花子の両方のレビューが削除! 5
  4. Eloquentの内部実装を確認してみた namespace Illuminate\Database\Eloquent; public function delete() { $this->performDeleteOnModel(); return true;

    } protected function performDeleteOnModel() { $this->setKeysForSaveQuery($this->newModelQuery())->delete(); } protected function setKeysForSaveQuery($query) { // 最初の検索条件は無視され、プライマリーキーのみが使用される $query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery()); return $query; } 6
  5. 応急対処法 クエリビルダーを使用して明示的に条件指定 // 複数条件を明示的に指定 ProductReview::where('product_id', 123) ->where('user_name', '田中太郎') ->where('review_id', $targetReviewId)

    ->delete(); // または、LIMITを使用 ProductReview::where('product_id', 123) ->where('user_name', '田中太郎') ->limit(1) ->delete(); 注意: update() でも同様の問題が発生する可能性あり 7
  6. 根本的な解決策 Laravel標準の $table->id() を使用(最推奨) // マイグレーションファイル Schema::create('product_reviews', function (Blueprint $table)

    { $table->id(); // Laravel標準のid(確実にユニーク) $table->unsignedBigInteger('product_id'); $table->string('user_name'); $table->integer('rating'); $table->text('comment'); $table->index(['product_id', 'user_name']); }); 8
  7. Laravel標準構成のメリット Eloquentモデル(デフォルト設定で十分) class ProductReview extends Model { protected $fillable =

    ['product_id', 'user_name', 'rating', 'comment']; // プライマリーキーは自動的に'id'になり、AUTO_INCREMENTが有効 } 生成されるテーブル CREATE TABLE product_reviews ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, -- 確実にユニーク product_id BIGINT UNSIGNED, user_name VARCHAR(255), rating INT, comment TEXT, ); 9
  8. 既存システムの移行方法 段階的な移行 // 移行用マイグレーション Schema::table('product_reviews', function (Blueprint $table) { $table->id()->first();

    // Laravel標準のidカラムを追加 $table->dropPrimary('review_id'); // 古いプライマリーキーを削除 }); データ移行後、モデルを標準設定に変更 class ProductReview extends Model { // protected $primaryKey = 'review_id'; ← 削除 // public $incrementing = false; ← 削除 protected $fillable = ['product_id', 'user_name', 'rating', 'comment']; } 10