開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. PHP >
  4. Laravel >
  5. Illuminate\Database\Eloquent\Builder::chunkById()を使用する際のつまづきと注意点
no-image

Illuminate\Database\Eloquent\Builder::chunkById()を使用する際のつまづきと注意点

 

こんにちは!
​気温変化についていけずにバテ気味のヤマモトです。
雨が降ったり止んだりなどうんざりな天気が続く最近ですが皆さんはどうお過ごしでしょうか。

今回はDBレコードの一括更新を行なっていた際にヤマモトがチャンクでつまづいた点や勉強になったことをまとめていきたいと思います。
 

検証環境とソフトウェアのバージョン

  • PHP 8.1.8 
  • MySQL 8.0.27  
  • Laravel Framework 9.22.1
 

chunkを使用する理由

usersテーブルにあるレコードに何かしらの処理を加えたいとします。
以下のように全コード取得した場合のコードの場合、件数が増えていくとメモリ不足になります。
今回は分かりやすくするため簡単な例を使用しています。

User::where('active', false)->get();
foreach ($users as $user) {
      $user->active = true;
      $user->save();
}


そこでchunkを使い複数に渡るDBレコードの処理を行うことでメモリ不足を防ぐことが可能です。
一回のクエリで全件取得する場合、メモリにEloquent Modelのデータを保持するため大量にメモリを使うのに対し、チャンクでは何度もクエリを発行する代わりに使用するメモリを抑えられます。
以下に示すようにDBから指定した数(今回は100)ごとに分割されたデータを取得し処理を加えることができます。

User::where('active', false)->chunk(100, function (Collection $users) {
    foreach ($users as $user) {
        $user->active = true;
        $user->save();
    }
});


 

複数レコードを更新する際の注意点

上記に書いたように複数レコードに対して処理を行う場合にはチャンキングを行いメモリ不足を防ぐ必要がありました。
しかし、結果をチャンクしつつデータベースレコードを更新すると、チャンク結果が意図しない変化(2回目以降のクエリがずれる)を起こす可能性があります。
そこでchunkByIdを使用してあげる必要があります。
 

User::where('active', false)->chunkById(100, function (Collection $users) {
    foreach ($users as $user) {
        $user->active = true;
        $user->save();
    }
});


上記のコードのようにして複数レコードをメモリ不足の心配なく更新することが可能です。

chunkに対してchunkByIdを使用するメリットには速度面もあります。
LengthAwarePaginatorでのページングで、大量データがあるときにページ後半で取得が遅くなる経験はよくあると思います。
chunkでも同様のことが起こります。
これはOFFSET LIMITが遅いためです
chunkByIdはOFFSET LIMITを使わずにidをwhere条件にしているので、chunkよりも高速です。

 

テーブルをjoinする場合

今回自分も使用していたときにつまづいた注意点がもう1つありました!
2つのテーブルをjoinしてchunkByIdを使用する場合にidが曖昧だとエラーになっていました。

User::where('active', false)->join('shops', 'users.id', '=', 'shops.user_id')
    ->chunkById(100, function (Collection $users) {
        ...
    });


今回はselectでusersテーブルの全カラムを取得しshopsテーブルとjoinします。
なのでchunkByIdの第3引数に使用する項目名を指定してやる必要があるので注意が必要です。
第4引数にはそれに取り替わる元の項目名を指定する必要があります。

User::query()
    ->where('active', false)
    ->select(['users.*'])
    ->join('shops', function ($join) {
        $join->on('users.id', '=', 'shops.user_id');
    })
    ->chunkById(100, function (Collection $users) {
        foreach ($users as $user) {
            // 処理
        }
    }, 'users.id', 'id');

 

このようにして複数のDBレコードの更新をメモリ不足の心配なく行うことができます。
この記事が少しでも皆さんの役に立てば幸いです。
ここまで読んでいただきありがとうございました!


参考記事
存在を知らなかったchunkById
Laravel 9.x データベース:クエリビルダ

 

  • posted by やまちゃん
  • MySQL
TOPに戻る