4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel で複数削除 where (whereIn)+ delete vs destroy はどちらが早いのかを検証してみた。

Last updated at Posted at 2023-01-06

はじめに

Laravel を使っていて、複数レコードを削除する方法が2つありましたので、
どちらが処理速度早いのかを検証してみました。

携わっていた案件で、ユーザーが選択したものを一括削除する機能があったことがきっかけです。

まあ、該当するidがフロントから送られてきたら、そのidをもとに削除すればいいだけだから、
複雑じゃないし気楽にやるか〜と思っていたところ、
モデルを複数削除する方法って複数あるんやな〜と見つけました。

それでどの処理が良いんだろうかと調査することにしました。

Laravel のEloquent 削除処理について

まずは、基本的な削除について改めて確認をします。
なお、検証で使用するlaravel のバージョンは8となっております。

以下例などは、公式docs : laravel Eloquent の準備 から抜粋
https://readouble.com/laravel/8.x/ja/eloquent.html

基本的な削除処理

use App\Models\Flight;

$flight = Flight::find(1);

$flight->delete();

該当のモデルを取得後、 delete メソッドで削除ができますね。

流れとしては、
該当のモデルを find で取得し、取得後にデータを削除する 処理となっています。

では、複数削除の場合はどうでしょう。

Laravel のモデル 複数削除について

パターン1: destroy メソッド

image.png

1つ目が、destroyメソッドです。

入れる引数は、

  • 削除したいid
  • 削除したい複数のid
  • id の配列
  • id (modelでも可) のcollection
    上記となっています。

docs の注意書きを見ると、

モデルの主キーがわかっている場合はdestroyメソッドを呼び出してモデルを明示的に取得せずにモデルを削除できます
destroyメソッドは単一の主キーを受け入れることに加えて複数の主キー主キーの配列または主キーのコレクションを引数に取ります

と書かれており、処理のフローとしては
モデルを明示的に取得せず削除できる と書かれていますね。

ただ、下のNote部分を見ると、

Note: destroyメソッドは各モデルを個別にロードしdeleteメソッドを呼び出して
deletingイベントとdeletedイベントが各モデルに適切にディスパッチされるようにします

と記載がありますね。
Eloquent のイベント処理を発火させるために、
内部的に、削除する該当モデルは取得して1つずつdeleteメソッドを適用している処理みたいですね。

パターン2: where or whereIn + delete メソッド

image.png

2つ目が、whereやwhereIn + deleteメソッド です。

Eloquent クエリ 時に where句やwhereIn句で該当のモデルを絞り込んだあと、deleteメソッドで削除する流れですね。

docs の説明とNote を見てみます。

一括更新と同様に一括削除では削除されたモデルのモデルイベントはディスパッチされません

Note: Eloquentを介して一括削除ステートメントを実行すると削除されたモデルに対してdeletingおよびdeletedモデルイベントがディスパッチされません
これはdeleteステートメントの実行時にモデルが実際には取得されないためです

destroy メソッドとは違い、Eloquent イベントの発火はしないみたいですね。
となると、この情報からモデル1つ1つをロードしていないことがわかりますね。

2つの処理を比較すると、

:point_up:Eloquent イベント が発火するかどうか:point_up: の違いだとわかります。

処理速度の比較

それでは、先ほどのメソッドを使用して処理速度をそれぞれ計測していきます。

計測条件については下記の通りです。

  1. laravel のUnit test を使って計測
  2. 任意のユーザーデータを1000件を作成し、削除
  3. 3回計測し、その平均値をもとに比較

destroy メソッド

/**
 * @group test_delete_speed
 */
public function test_delete_speed()
{
    $userIds = User::factory()->count(1000)
        ->create()->pluck('id');

    $startTime = microtime(true);

    User::destroy($userIds);
    $runningTime =  microtime(true) - $startTime;
    Log::info('destroy: running time: ' . $runningTime . ' [s]');
}

計測結果

平均速度:0.54864454269409 [s]

testing.INFO: destroy: running time: 0.55044484138489 [s]  
testing.INFO: destroy: running time: 0.54447293281555 [s]  
testing.INFO: destroy: running time: 0.55101585388184 [s] 

where or whereIn + delete メソッド

/**
 * @group test_delete_speed
 */
public function test_delete_speed()
{
    $userIds = User::factory()
        ->count(1000)
        ->create()->pluck('id');

    $startTime = microtime(true);

    User::whereIn('id', $userIds)->delete();
    $runningTime =  microtime(true) - $startTime;
    Log::info('whereIn+delete: running time: ' . $runningTime . ' [s]');
}

計測結果

平均速度:0.011776367823283 [s]

testing.INFO: whereIn+delete: running time: 0.011615991592407 [s]  
testing.INFO: whereIn+delete: running time: 0.011584043502808 [s]  
testing.INFO: whereIn+delete: running time: 0.012129068374634 [s]  

比較してみると

  1. destroy => 0.54864454269409 [s]

  2. where or whereIn + delete => 0.011776367823283 [s]

以上のことから where (whereIn) + delete の方が早い とわかります。

ではなぜ処理速度に差が出るのか詳しく確認してみます。

メソッド内部処理を見てみる

destroy
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php

/**
 * Destroy the models for the given IDs.
 *
 * @param  \Illuminate\Support\Collection|array|int|string  $ids
 * @return int
 */
public static function destroy($ids)
{
    if ($ids instanceof EloquentCollection) {
        $ids = $ids->modelKeys();
    }

    if ($ids instanceof BaseCollection) {
        $ids = $ids->all();
    }

    $ids = is_array($ids) ? $ids : func_get_args();

    if (count($ids) === 0) {
        return 0;
    }

    // We will actually pull the models from the database table and call delete on
    // each of them individually so that their events get fired properly with a
    // correct set of attributes in case the developers wants to check these.
    $key = ($instance = new static)->getKeyName();

    $count = 0;

    foreach ($instance->whereIn($key, $ids)->get() as $model) {
        if ($model->delete()) {
            $count++;
        }
    }

    return $count;
}

データをwhereInで取得したのちに、1つずつforeach で 削除を行なっているみたいですね。
確かに、foreachでまわす処理を内部的に行なっているのであれば、処理が遅くなりますね。

delete
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php

/**
 * Delete records from the database.
 *
 * @return mixed
 */
public function delete()
{
    if (isset($this->onDelete)) {
        return call_user_func($this->onDelete, $this);
    }

    return $this->toBase()->delete();
}

delete メソッドは内部処理を追いかけるとかなりのファイル量になるので一旦は上記までを表示します。

最終的にはSQLのdelete を行なっており、該当データに対して取得などはせず削除する処理が行われます。

その違いで速度処理が違うみたいです。

まとめ

一括でデータを削除する場合は、

処理速度の観点から where or whereIn などのクエリ文でデータを絞り込んだあと、
deleteメソッドで削除した方が良いですね!

ただ、Eloquent イベントを発火させたい場合はdestroyを指定する必要がありますね。

そのため、用途によって使い分ける形になりますが、
基本的にはdeleteをメインで使っていければと思います。

ここまでお読みいただきありがとうござました。

参考文献

4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?