Обработка миллионов записей в базе данных с использованием laravel

Все мы знаем, что запускать в цикле запросы к базе данных – плохое решение.

Транзакция к базе данных очень дорогостоящая операция

Допустим, мы разработали систему учета финансов и пользовались ею в течении года. За это время накопилось более миллиона записей в таблице успешных сделок.

Нам поставили задачу, что необходимо для всех цен добавить НДС. А чтобы аналитика не поплыла, НДС надо добавить и для старых записей. Пример так себе, но все же.

Для новых сделок задача простая, можно воспользоваться мутатором:

class Transaction extends Model 
{
    public $vat = 0.20; 

    public function setPriceAttribute($value) 
    {
        $this->attributes['price'] += $value * $this->vat;
    }
}

Но как поступить с историческими данными. Какая первая мысль поступить?

Создаем сидер

php artisan make:seeder AddVatToTransactions

Если у нас не много записей, то мы могли бы поступить следующим образом:

class AddVatToTransactions extends Seeder 
{ 
    public function run() {
        $vat = 0.20; 
        $transactions = Transaction::get(); 
        foreach ($transactions as $transaction) 
        {
            $transaction->price += $transaction->price * $vat;
            $transaction->save();
        }
    }
}

Но если у нас будет миллион записей, то скорее всего наш сайт повиснет от огромного кол-ва запросов к бд.

Есть мысль заменить множество отдельных запросов на один, используя case:

UPDATE db.transactions
SET PRICE = CASE
WHEN id = 3 THEN 500
WHEN id = 4 THEN 300
END
WHERE ID IN (3, 4)

На laravel у нас получится что-то вроде такого:

$vat = 0.20;
$transactions = Transaction::get();
$cases = [];
$ids = [];
$params = [];
foreach ($transactions as $transaction) 
{ 
    $cases[] = "WHEN {$transaction->id} then ?";
    $params[] = $transaction->profit * $vat;
    $ids[] = $transaction->id;
} 

$ids = implode(',', $ids);
$cases = implode(' ', $cases);
if (!empty($ids)) {
    \DB::update("UPDATE transactions SET `price` = CASE `id` {$cases} END WHERE `id` in ({$ids})", $params);
}

Но система явно так же будет не в восторге от миллиона case’ов. Давайте воспользуемся коллекциями Laravel и разобьем на чанки. Т.к. нам требуются только id и profit, то добавим выборку только этих данных:

$vat = 0.20;
$transactions = Transaction::select('id', 'price')->get();
foreach ($transactions->chunk(5000) as $chunk) {
    $cases = [];
    $ids = [];
    $params = [];
    foreach ($chunk as $transaction) {
        $cases[] = "WHEN {$transaction->id} then ?";
        $params[] = $transaction->profit * $vat;
        $ids[] = $transaction->id;
    }
    $ids = implode(',', $ids);
    $cases = implode(' ', $cases);
    if (!empty($ids)) {
        \DB::update("UPDATE transactions SET `price` = CASE `id` {$cases} END WHERE `id` in ({$ids})", $params);
    }
}

Рейтинг
( Пока оценок нет )
Maxyc Webber/ автор статьи
Мне 35 лет. Опыт профессиональной разработки 15 лет. Занимаюсь разработкой и поддержкой корпоративных систем автоматизации бизнеса, а также высоконагруженными проектами. Мне нравится решать нестандартные проблемы бизнеса. Имею опыт формирования команд под проект, налаживания процесса разработки, коммуникации программистов и заказчиков. Есть опыт работы с зарубежными заказчиками (ОАЭ, Польша, Германия, Швейцария).
Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.