1С-Битрикс. Массовая загрузка элементов в Highload-блоки

от автора

При загрузке данных в 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


ссылка на оригинал статьи https://habr.com/ru/articles/753460/