Задать вопрос
@Vadik7777

Как максимально быстро добавить огромное количество записей в БД без дублей?

Всем привет!

Как максимально быстро добавить огромное количество записей в БД без дублей?

Пробовал так:
foreach($numbers as $num){
   $exist = Model::where('num', $num)->exists();
   if (!exist) {
      $new = new Model();
      $new->num = $num;
      $new->save();
   }
}

Это очень долгий процесс, даже для 1000 записей, а что если будет несколько сотен тысяч записей, сразу повесится)

Подскажите каким образом можно вставить быстро и без дублей?
  • Вопрос задан
  • 509 просмотров
Подписаться 1 Простой 3 комментария
Решения вопроса 1
@mrFlyer
Много записей это сколько? Вы просто каждый раз делаете запрос к базе для проверки. Если переложить проверку поиска дубля на пхп, то проблема решится.

Получите все записи разом из бд, в пхп найдите несуществующие записи и через DB::insert проведите вставку.

Что то типа такого:
$models = Model::select("id","num")->get();
        foreach($numbers as $num){
            if (!$models->where("num",$num)->first()) {
                \DB::table("table_name")->insert(["num"=>$num]);
            }
        }


Ну и как вариант вытащить все записи из списка numbers и сравнить каких не хватает:

$models = Model::select("id","num")->whereIn("num",$numbers)->get();
        foreach($numbers as $num){
            if (!$models->where("num",$num)->first()) {
                \DB::table("table_name")->insert(["num"=>$num]);
            }
        }
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
Frostealth
@Frostealth
Backend Developer
Как вариант: отфильтровать `$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 ускорит работу.
На большие данные и нагрузки нужно мощное железо. Может потребоваться масштабирование и т.д.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы