建立專案
建立專案。
1
   | laravel new eloquent-chunking
   | 
 
新增資料庫
為求方便,使用 SQLite 資料庫。
安裝套件
由於需要查看記憶體使用量、執行時間以及資料庫查詢,需要安裝 Telescope 套件。
新增模型
新增 Book 模型,使用 -a 參數同時新增遷移檔、工廠和控制器。
1
   | php artisan make:model Book -a
   | 
 
在遷移檔新增欄位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
   | use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema;
  class CreateBooksTable extends Migration {     
 
 
 
      public function up()     {         Schema::create('books', function (Blueprint $table) {             $table->bigIncrements('id');             $table->string('title');             $table->string('author');             $table->timestamps();         });     }
      
 
 
 
      public function down()     {         Schema::dropIfExists('books');     } }
   | 
 
資料填充
修改 database/factories/BookFactory.php 檔,使工廠產生不重複的資料。
1 2 3 4 5 6 7 8 9
   | use App\Book; use Faker\Generator as Faker;
  $factory->define(Book::class, function (Faker $faker) {     return [         'title' => sprintf('%s (%u)', $faker->sentence(), rand()),         'author' => sprintf('%s (%u)', $faker->name(), rand()),     ]; });
   | 
 
新增 BooksTableSeeder 資料填充。
1
   | php artisan make:seed BooksTableSeeder
   | 
 
分批插入共 10 萬筆資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | use App\Book; use Illuminate\Database\Seeder;
  class BooksTableSeeder extends Seeder {     
 
 
 
      public function run()     {         for ($i = 1; $i <= 100; $i++) {             $books = factory(Book::class)->times(1000)->make()->toArray();
              Book::insert($books);         }     } }
   | 
 
在 database/seeds/DatabaseSeeder.php 檔註冊資料填充。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | use Illuminate\Database\Seeder;
  class DatabaseSeeder extends Seeder {     
 
 
 
      public function run()     {         $this->call(BooksTableSeeder::class);     } }
   | 
 
執行資料填充。
1
   | php artisan migrate --seed
   | 
 
新增路由
在 routes/api.php 檔新增路由。
1
   | Route::get('books', 'BookController@index');
  | 
 
情境
從 10 萬筆資料中找到一筆已存在的資料:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | $max = 100000;
 
  $existingBook = Book::find($max);
 
  $books = Book::where('id', '<=', $max)->get();
 
  $isExisting = $books->some(function ($book) use (&$existingBook) {     return $book->title === $existingBook->title; });
  dump($isExisting);
   | 
 
結果:
實測 5 次的效能分別如下:
| Duration | 
Memory usage | 
queries | 
| 1037 ms | 
119.9 MB | 
2 | 
| 1040 ms | 
117.9 MB | 
2 | 
| 1092 ms | 
113.9 MB | 
2 | 
| 1151 ms | 
111.9 MB | 
2 | 
| 1084 ms | 
103.9 MB | 
2 | 
優化
分塊
在處理大型結果集時,為了節省記憶體使用量,可以使用 chunk() 方法來查詢 Eloquent 模型中的「分塊」,將它們提供給指定的閉包處理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | $max = 100000;
 
  $existingBook = Book::find($max);
  $isExisting = false;
 
  Book::where('id', '<=', $max)->chunk($max / 10, function ($books) use (&$existingBook, &$isExisting) {     // 檢驗是否已存在     $isExisting = $books->some(function ($book) use (&$existingBook) {         return $book->title === $existingBook->title;     });
           return ! $isExisting; });
  dump($isExisting);
   | 
 
結果:
實測 5 次的效能分別如下:
| Duration | 
Memory usage | 
queries | 
| 1053 ms | 
18 MB | 
11 | 
| 1112 ms | 
18 MB | 
11 | 
| 1145 ms | 
18 MB | 
11 | 
| 1114 ms | 
18 MB | 
11 | 
| 1120 ms | 
18 MB | 
11 | 
使用 chunkById() 方法可以更快速地查詢資料庫,但僅限用於主鍵為 AUTO_INCREMENT 的資料表,並且在有 join 的情況下可能不能使用。
游標
使用 cursor() 方法可以藉由游標遍歷資料庫,並且只會執行一次的查詢。處理大量的數據時,可以減少記憶體使用量。
1 2 3 4 5 6 7 8 9
   | $max = 100000;
  $existingBook = Book::find($max);
  $isExisting = Book::where('id', '<=', $max)->cursor()->some(function ($book) use (&$existingBook) {     return $book->title === $existingBook->title; });
  dump($isExisting);
   | 
 
結果:
實測 5 次的效能分別如下:
| Duration | 
Memory usage | 
queries | 
| 2168 ms | 
4 MB | 
2 | 
| 2166 ms | 
4 MB | 
2 | 
| 2214 ms | 
4 MB | 
2 | 
| 2178 ms | 
4 MB | 
2 | 
| 2165 ms | 
4 MB | 
2 | 
其他
如果只是要避免重複插入資料而產生錯誤,可以考慮使用 insertOrIgnore() 方法,但是這個方法只會檢查主鍵而已。
1 2 3 4 5 6 7 8 9 10 11
   | $max = 100000;
  Book::insertOrIgnore([     [         "id" => $max,         "title" => "New Book",         "author" => "Memo Chou",     ], ]);
  dump(Book::count());
   | 
 
結果:
程式碼