При загрузке данных в Highload-блоки возможна ситуация, когда объем загружаемых данных очень велик.

Тем не менее, самый распространенный путь для добавления данных - их перебор в цикле, и последующее добавление построчно в Highload-блок. При большом количестве добавляемой информации этот вариант становится слишком медленным и неэффективным, хотя и остается повсеместно используемым.

На самом деле, основных пути три (привет старым добрым былинам): добавление в цикле, добавление прямым запросом и добавление массово через ORM-коллекции

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


Рассмотрим каждый из способов подробнее. Подчеркиваем, что речь идет именно о массовой записи.

Информационная справка

$arData - массив с данными для загрузки в Highload

В качестве примера взят реальный файл с базой городов мира, в котором содержится ~3.6 млн записей

Под использованием "штатного инструмента" подразумевается внутренний функционал 1С-Битрикс, который был создан специально для работы этой системы, обновляется разработчиками и содержит свои дополнительные проверки и способы защиты

1. Добавление в цикле

use Bitrix\Highloadblock as HL;
use Bitrix\Main\Entity;
 
$hlblockID = 1;		
$hlblock = HL\HighloadBlockTable::getById($hlblockID)->fetch();
$entity = HL\HighloadBlockTable::compileEntity($hlblock);
if (!empty($hlblock)) {
	foreach ($arData as $dataRow) {
		$dataLoad = [
			"UF_PROPERTY_1" => $dataRow["PROPERTY_1"],
			"UF_PROPERTY_2" => $dataRow["PROPERTY_2"]
		]; // массив для добавления записи в HL
 
		$entityDataClass = $entity->getDataClass();
		$entityDataClass::add($dataLoad);
	}
}

Скорость выполнения на ~3.6 млн записей => ~1.5ч

Представьте себе, что ваша задача - наполнить водой огромный сосуд. Но из инструментов у вас есть только пипетка. Вы можете контролировать каждый шаг, переливая по капле воду в сосуд, но вы потратите уйму времени и устанете от однотипных действий.

Преимущества данного подхода: работа со штатным инструментом, использование ООП подхода

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

2. Добавление прямым запросом

$strFields = "UF_PROPERTY_1, UF_PROPERTY_2";
$strValues = "";
 
foreach ($arData as $dataRow) {
	$strValues .= (!!strlen($strValues) ? ", " : "")
		. "('" . $dataRow["PROPERTY_1"] . "', "
		. "'" . $dataRow["PROPERTY_2"] . "')"
	;
}
 
$DB->Query('INSERT INTO highload_table_name (' . $strFields . ') VALUES ' . $strValues);

Скорость выполнения на ~3.6 млн записей => ~5 мин

Представьте себе, что вы решили собрать пазл. Вы успешно справляетесь с задачей, и в конце решаете его склеить, чтобы повесить на стену. Но после склеивания замечаете, что один кусочек мозаики лег как-то криво, а то и вовсе этот кусочек пазла попал сюда из другой картины, или оказался пропитан ацетоном. Маленький, практически незаметный кусочек может разрушить всю картину. Да, с прямым запросом вы можете делать практически что угодно. Но, цитируя классиков, “с большой силой приходит и большая ответственность”.

Преимущества данного подхода: высокая скорость выполнения, передача через 1 запрос

Минусы данного подхода: работа с прямым запросом к БД может ей навредить, если в запросе будет ошибка, возможность небезопасной передачи данных или в неправильном формате (необходимо добавить дополнительную обработку, часто используется метод forSql).

3. Добавление через ORM-коллекции

use Bitrix\Highloadblock as HL;
use Bitrix\Main\Entity;
 
$hlblockID = 1;		
$hlblock = HL\HighloadBlockTable::getById($hlblockID)->fetch();
$entity = HL\HighloadBlockTable::compileEntity($hlblock);
 
$valuesCollection = $entity->createCollection();
 
foreach ($arData as $dataRow) {
	$collectionObj =
		$entity->createObject()
			->set("UF_PROPERTY_1", $dataRow["PROPERTY_1"])
			->set("UF_PROPERTY_2", $dataRow["PROPERTY_2"])
	;
	$valuesCollection->add($collectionObj);
}
$valuesCollection->save(true);

В примере в метод save передан параметр $ignoreEvents = true, который отменяет выполнение событий ORM во время добавления записей (подробнее - тут).

Результирующий запрос при этом будет выглядеть следующим образом (как и при прямом запросе):

INSERT INTO `table_name` (`UF_PROPERTY_1`, `UF_PROPERTY_2`) VALUES (1, 2), (2, 3), (3, 4), (...), (X, Y)

Скорость выполнения на ~3.6 млн записей => ~9 мин

И вот мы добрались до жемчужины нашей коллекции - коллекции (простите за тавтологию). Как до коробки конфет ручной работы - каждую сделали с любовью, обернули в красивый фантик, и положили в коробку, которую теперь не стыдно и подарить близкому человеку.

Преимущества данного подхода: использование штатных инструментов, запись всех данных через 1 запрос, использование ООП подхода

Минусы данного подхода: понимание работы механизма требует повышенного уровня знаний

Вывод


Таким образом, при выборе стратегии добавления записей следует ориентироваться на несколько моментов:

  1. Какое количество записей вы планируете обработать,

  2. Какой запас прочности имеет ваша система,

  3. Насколько высок ваш уровень программирования (ну или ваши амбиции в его достижении)

Если записей мало (совсем немного, до 1000), то можно воспользоваться добавлением в цикле.

Если производительность системы невысока, и несколько минут могут сильно повлиять на ее работу, а при этом количество обрабатываемых записей большое - то можно производить запись прямым запросом (но стоит помнить: вариант небезопасен).

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

Надеемся, наша статья была для вас полезна. Успешных всем проектов!

Автор статьи: @DasBinIch

Комментарии (3)


  1. Pitcentr0
    09.08.2023 08:37

    Полезно, спасибо!


  1. dcrm
    09.08.2023 08:37
    +2

    Минусы данного подхода: понимание работы механизма требует повышенного уровня знаний

    И поймать memory_limit. Спасибо, не надо.


  1. rpsv
    09.08.2023 08:37

    Статья хорошая, но в целом про работу с сохранением из коллекции в доке есть: https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&LESSON_ID=11749&LESSON_PATH=3913.3516.5748.11743.11749

    Плюс в комментариях выше правильно написали про использованную память. Я думаю стоит также добавить в статью сколько памяти сожралось при том или ином варианте.

    Ну и еще как вариант можно запросы делать пачками по 1К, 10К, 100К элементов, а не собирать один большой монструозный запрос