InsertIgnore стратегия для безопасной вставки без дублирования
Проблема дублирования записей
При массовой вставке данных в базу через ORM часто возникает проблема дублирования по уникальным полям. Стандартный метод add() генерирует ошибку при попытке вставить запись с существующим первичным ключом или уникальным индексом. Приходится вручную проверять существование записи через getList() перед вставкой, что неэффективно при массовых операциях.
Стратегия InsertIgnore
С версии 22.0 в Bitrix ORM появилась стратегия InsertIgnore, которая использует SQL-конструкцию INSERT IGNORE. Если запись с таким ключом существует, она игнорируется без ошибки. Это атомарная операция на уровне СУБД, что обеспечивает корректность даже при конкурентных запросах.
Переопределение стратегии в Table-классе
Самый простой способ — переопределить метод getAddStrategy() в вашем Table-классе:
namespace Mycompany\MyModule;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Data\AddStrategy;
class UserTokenTable extends DataManager
{
public static function getTableName()
{
return 'b_user_token';
}
public static function getMap()
{
return [
'ID' => ['data_type' => 'integer', 'primary' => true, 'autocomplete' => true],
'USER_ID' => ['data_type' => 'integer', 'required' => true],
'TOKEN' => ['data_type' => 'string', 'required' => true],
];
}
// Переопределяем стратегию для всех операций add()
protected static function getAddStrategy(): AddStrategy\Contract\AddStrategy
{
return new AddStrategy\InsertIgnore(static::getEntity());
}
}
// Теперь add() использует INSERT IGNORE автоматически
$result = UserTokenTable::add([
'ID' => 1,
'USER' => 1,
'TOKEN' => 'abc123'
]);
// Если запись существует - игнорируется без ошибки
// $result->isSuccess() вернёт true
// $result->getId() вернёт ID существующей записи
Массовая вставка с InsertIgnore
Для массовой вставки данных используйте метод addMulti():
use Bitrix\MyModule\UserTokenTable;
$tokens = [
['ID' => 10, 'USER_ID' => 1, 'TOKEN' => 'token1'],
['ID' => 11, 'USER_ID' => 2, 'TOKEN' => 'token2'],
['ID' => 12, 'USER_ID' => 3, 'TOKEN' => 'token3'],
['ID' => 10, 'USER_ID' => 1, 'TOKEN' => 'token1'], // дубликат - будет проигнорирован
];
// Все записи вставляются одним запросом
$result = UserTokenTable::addMulti($tokens);
// Проверка успешности
if ($result->isSuccess()) {
// Операция выполнена, дубликаты проигнорированы
}
Указание уникальных полей
По умолчанию InsertIgnore проверяет первичный ключ. Если нужно проверять другие поля, передайте их в конструктор:
protected static function getAddStrategy(): AddStrategy\Contract\AddStrategy
{
// Проверять уникальность по полям USER_ID и TOKEN
return new AddStrategy\InsertIgnore(
static::getEntity(),
['USER_ID', 'TOKEN']
);
}
Проверка изменений базы
Метод isDBChanged() в результате показывает, была ли реально изменена база:
$result = UserTokenTable::add(['USER_ID' => 1, 'TOKEN' => 'abc']);
if ($result->isSuccess()) {
if ($result->getData()['isDBChanged']) {
// Запись была вставлена
} else {
// Запись уже существовала и была проигнорирована
}
}
Важные ограничения
InsertIgnore работает только с таблицами, имеющими первичный ключ или уникальный индекс. Стратегия не поддерживает таблицы без ограничений уникальности. При попытке использовать InsertIgnore на несовместимой таблице будет выброшено исключение NotSupportedException.
Когда использовать InsertIgnore
Применяйте InsertIgnore для импорта данных, синхронизации справочников, сохранения логов без дублей, кэширования токенов. Для случаев, когда нужно обновить существующую запись, используйте стратегию Merge вместо InsertIgnore.
Похожие советы