• Час читання ~1 хв
  • 27.02.2023
 

Іноді потрібно оновити дані у своїй базі даних.  Найпростіша можливість - просто запустити оновлення у своїй базі даних MySQL. Це не завжди спрацьовує. Особливо, коли ви використовуєте події або ви також хочете оновити відносини ....

Команди

В цьому випадку рекомендую створити Command. Навіть для разових змін.

php artisan make:command YourCommandName

Індикатор прогресу

Першою порадою буде використання індикатора прогресу. У довготривалих командах корисно бачити, що є прогрес.

Щоб показати вам, як я просто копіюю приклад з Laravel Documentation.

$users = App\Models\User::all();

$bar = $this->output->createProgressBar(count($users));

$bar->start();

foreach ($users as $user) {
    $this->performTask($user);

    $bar->advance();
}

$bar->finish();

Це

чудово працює до пари сотень записів з легкими змінами. Якщо ви хочете змінити більше записів з більшою складністю, вам слід використовувати результати пошуку.

Проблема в тому, що якщо ви завантажите все у свою красномовну колекцію, ваша баранина буде обмеженням. Щоб уникнути цього, ви можете використовувати вбудовану частину функції Laravel на ваших запитах, щоб послідовно повторювати таблицю.

App\Models\User::chunk(200, function ($users){
    foreach($users as $user){
        $user->name .= ' :)';
        $user->save();
    }
});

Одна важлива річ, яку слід зрозуміти щодо функції фрагмента, - це зрозуміти, як виконуються запити. У цьому прикладі після того, як 200 користувачів пройшли ітерацію, базовий запит знову виконується з функцією LMIT на таблиці. 

Уявіть, що у вас є цей випадок

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

У цьому коді він перевищить 200 користувачів, змінивши активне значення на false.  У другому запуску він знову запитає базу даних для користувачів, які мають активну істину. Проблема в тому, що оскільки ми щойно змінили активний статус 200 користувачів, ми б отримали список без них. Але функція Limit обмежить результат від 200 до 400 в результатах. Це означає, що ми пропустимо 200 користувачів, яких насправді хотіли змінити.

Laravel має функцію подолання проблеми, просто важливо розуміти, коли її використовувати. Так що рішенням в цій ситуації буде.

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

Транзакції баз даних

Тепер ми можемо виконати багато змін у наших моделях, і ми уникаємо проблеми з тим, що наші красномовні колекції стають занадто великими. 

Але в нашому останньому прикладі ми виконаємо оновлену Заяву для кожного окремого користувача нашої БД. Щоб уникнути цього, я вважав гарною тактикою використання транзакцій. 

Це дозволяє нам повторно використовувати наші шматки та оновлювати БД на фрагмент.

App\Models\User::where('active', true)
    ->chunkById(200, function ($users){
        try {
            DB::beginTransaction();
            
            foreach($users as $user){
                $user->active = false;
                $user->save();
            }
           
            DB::commit();

        } catch (\Exception $e) {
            //handle your error (log ...)
            DB::rollBack();
        }
    });

У цьому прикладі коду ми об'єднуємо chunkById з транзакціями бази даних. Це може заощадити багато часу на оновлення БД. Детальніше про Транзакції бази даних ви можете прочитати в документації Laravel.

Транзакції можуть спричинити проблеми, якщо їх неправильно використовувати. Якщо ви забудете зробити або відкотити, ви створите вкладені транзакції. Ви можете прочитати більше в Blogpost

Об'єднайте його разом

Щоб завершити цей приклад коду, ми можемо знову ввести індикатор прогресу.

$count = App\Models\User::where('active', true)->count();

$bar = $this->output->createProgressBar($count);
$bar->start();

App\Models\User::where('active', true)
    ->chunkById(200, function ($users){
        try {
            DB::beginTransaction();
            
            foreach($users as $user){
                $user->active = false;
                $user->save();
                $bar->advance();
            }
           
            DB::commit();

        } catch (\Exception $e) {
            //handle your error (log ...)
            DB::rollBack();
            $bar->finish();
        }
    });

$bar->finish();

Отже, це моя стратегія обробки оновлень більших наборів даних. Ви можете змінити розмір шматка відповідно до своїх потреб та експериментів, які принесуть вам хороші результати. З мого досвіду щось з 200 - 1000 - це нормально.

Іноді, особливо коли розрахунок для одного запису складніший, я бачу, що весь процес стає повільнішим після кожної обробки. Він починається приблизно з 2 секунд на планку просування до 30 або 40 секунд. Оскільки я відчував це за різними командами, я не впевнений, що це загальна тема. Якщо хтось має будь-яку інформацію про це, повідомте мене про це.

Сподіваюся, ця стаття вам допоможе.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297