Как вариант: отфильтровать `$numbers` на стороне PHP и вставить пачкой.
// избавляемся от возможных дубликатов
$numbers = collect($numbers)->unique();
// pluck('num') вернет нам список значений атрибута num, а не список моделей
$existingNumbers = Model::query()->whereIn('num', $numbers->toArray())->pluck('num');
// с помощью diff получаем элементы, которых нет в $existingNumbers
$newNumbers = $existingNumbers->diff($numbers)->mapWithKeys(function ($value, $key) {
// ['one', 'two'] => [['num' => 'one'], ['num' => 'two']]
return [$key => ['num' => $value]];
});
Model::query()->getConnection()->transaction(function () use ($newNumbers) {
Model::query()->insert($newNumbers->toArray());
});
При очень больших данных в БД нужно вытаскивать из БД чанками (пачками определенного размера, например по 500 000), а не сразу все.
// избавляемся от возможных дубликатов
$newNumbers = collect($numbers)->unique();
Model::query()->toBase()->whereIn('num', $numbers->toArray())
->chunk(500000, function ($existingNumbers) use (&$newNumbers) {
// с помощью diff получаем элементы, которых нет в $existingNumbers
$newNumbers = $newNumbers->diff($existingNumbers);
});
// ['one', 'two'] => [['num' => 'one'], ['num' => 'two']]
$newNumbers = $newNumbers->mapWithKeys(function ($value, $key) {
return [$key => ['num' => $value]];
});
Model::query()->getConnection()->transaction(function () use ($newNumbers) {
Model::query()->insert($newNumbers->toArray());
});
Также можно воспользоваться
ON CONFLICT
, если СУБД поддерживает подобное. Например, у SQLite -
ON CONFLICT DO
, у MySQL -
INSERT IGNORE
. Это позволит избавиться от выгрузки данных из БД, что уменьшит потребление памяти приложением и сократит количество запросов.
Laravel имеет для этого метод
Query::insertOrIgnore()
, который будет глушить все ошибки от некоторых БД, но для некоторых не поддерживается. Поддерживаемые БД: MySQL, SQLite, PostreSQL.
Необходимо, чтобы на атрибут `num` в БД стоял constraint unique, иначе БД просто вставит дубликат.
$numbers = collect($numbers)->unique()->mapWithKeys(function ($value, $key) {
return [$key => ['num' => $value]];
});
Model::query()->getConnection()->transaction(function () use ($numbers) {
Model::query()->insertOrIgnore($numbers->toArray());
});
Индексы и explain изучить не помешает. Размер чанка подобрать по возможностям железа.
Индекс на num значительно ускорит выборку, но скорость вставки снизится.
Так же отказ от ORM (Eloquent), объектов и использование голого SQL с PDO ускорит работу.
На большие данные и нагрузки нужно мощное железо. Может потребоваться масштабирование и т.д.