При загрузке данных в 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 запрос, использование ООП подхода
Минусы данного подхода: понимание работы механизма требует повышенного уровня знаний
Вывод
Таким образом, при выборе стратегии добавления записей следует ориентироваться на несколько моментов:
Какое количество записей вы планируете обработать,
Какой запас прочности имеет ваша система,
Насколько высок ваш уровень программирования (ну или ваши амбиции в его достижении)
Если записей мало (совсем немного, до 1000), то можно воспользоваться добавлением в цикле.
Если производительность системы невысока, и несколько минут могут сильно повлиять на ее работу, а при этом количество обрабатываемых записей большое - то можно производить запись прямым запросом (но стоит помнить: вариант небезопасен).
Ну а для всех остальных вариантов лучшим выходом выглядит добавление через коллекции, которое сочетает в себе использование безопасного штатного встроенного функционала и достаточно высокую скорость обработки.
Надеемся, наша статья была для вас полезна. Успешных всем проектов!
Автор статьи: @DasBinIch
Комментарии (3)
dcrm
09.08.2023 08:37+2Минусы данного подхода: понимание работы механизма требует повышенного уровня знаний
И поймать memory_limit. Спасибо, не надо.
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К элементов, а не собирать один большой монструозный запрос
Pitcentr0
Полезно, спасибо!