Комментарии 12
Заменить 50к запросов на один - это хорошо, конечно, но как оно в базе выглядит? Может, он там full table scan делает теперь на каждом запросе и под нагрузкой все ляжет.
[offtop]
А что это за жуткий микс $snake_case, $camelCase, $StudlyCase, а потом ещё $SNAKE_CASE_CAPS. В Битрикс так принято?
[/offtop]
Никоим образом не хочу умолять заслуги автора, но каждый раз когда я вижу попытки сделать битрикс лучше мне почему то вспоминается анекдот:
Идёт девушка – видит - парень косит траву в противогазе:
- Ты что, с ума сошёл - зачем противогаз надел?
- Я комсомолец - не могу без трудностей...
- Кончай фигней страдать, пошли лучше переспим.
- Хорошо - но только в гамаке и стоя...
D7 ORM в большинстве случаев всегда повышает производительность, потому что заставляет подумать разработчика о том, как и какие данные он получает из БД.
Пару узких мест, которые я вижу в вашем решении:
1) Как я понял, вы используете иб 2 версии, соответсвенно, на уровне бд есть две таблицы для значений свойств b_iblock_element_prop_s и b_iblock_element_prop_m , в первой хранятся свойства с одиночными значениям, в последней множественные значения свойства. Обдумывали решение для свойств с множественными значениями?
2) Helper::getIblockPropIDByCode($property_code, $CatalogiblockId); -- вижу как запрос в бд, тоже в цикле. В условиях отсутствия кеша - это вызывает проблему N+1
3) Bitrix\Main\Entity\Base::compileEntity -- я могу ошибаться, но метод достаточно прожорлив, для использования в скрипте в фоне, можно пренебречь, но для выполнения кода на хите, я бы подумал в преимуществе такого подхода. Рассматривали ли альтернативы? Почему отказались от обычного join? (при условии, что вы используете только одиночные свойства)
4) Я бы посмотрел в строну пагинации, потому что объем данных, думаю, будет у вас расти. Вероятно у вас есть фильтры для выборки, которые тоже оказывают влияние на производительность запроса.
Можно пример обычного join?
не знаю насколько это кошерно....
while ($arItem = $obItems->fetch()) {
$result[$arItem['ID']] = getItemRes($arItem);
}
function getItemRes($item)
{
$res = [];
foreach ($item as $index => $row) {
if (!empty($row)) {
$tmp = unserialize($row);
if ($tmp != false) {
if (!empty($tmp['VALUE'])) {
$res[$index] = $tmp['VALUE'];
}
} else {
$res[$index] = $row;
}
}
}
return $res;
}
Это просто получить ID свойств по CODE. их в принципе можно и руками прописать. просто привык не пользоваться ID, т.к. на dev и на prod - ID могут различаться
Т.к. скрипт отрабатывает в фоне, альтернативу не рассматривал. Целей оптимизации этого скрипта добился. В качестве академического интереса, можно подумать
Опять же это уже больше исследовательский момент. Но так же можно будет попробовать. Можно и на чистом MySqQL написать.
Спасибо за содержательный комментарий
Мой каммент ниже отвечает на часть ваших вопросов, сначала написал потом прочитал) И да это все для свойства версии 2. Кстати у одного ИБ могут и те и другие свойства, но это вообще мрак, и тот кто хочет с этим заморачивать точно должен знать что он делает и зачем)
Чуть добавлю.
Для получения датакласса одиночных свойств
public function getSinglePropsDataClass($iblockId) {
$className = 'SProps' . $iblockId;
if (class_exists($className . "Table")) {
return $className . "Table";
}
$props = \Bitrix\Iblock\PropertyTable::query()
->setSelect(["ID", "MULTIPLE", "PROPERTY_TYPE"])
->where("IBLOCK_ID", $iblockId)
->where("MULTIPLE", "N")
->where("VERSION", 2)
->exec()->fetchAll();
$sProps = [];
foreach ($props as $prop) {
$key = "PROPERTY_" . $prop["ID"];
$type = $prop["PROPERTY_TYPE"] == \Bitrix\Iblock\PropertyTable::TYPE_NUMBER ? 'float' : 'string';
$sProps[$key] = ['data_type' => $type];
}
$sProps['IBLOCK_ELEMENT_ID']= ['data_type' => 'integer'];
$entitySProps = \Bitrix\Main\Entity\Base::compileEntity(
$className,
$sProps,
['table_name' => sprintf('b_iblock_element_prop_s%s', $iblockId)]
);
return $entitySProps->getDataClass();
}
Ну и для множественных
private function getMultiplePropsDataClass($iblockId) {
$className = 'MProps' . $iblockId;
if (class_exists($className . "Table")) {
return $className . "Table";
}
$entityMProps = \Bitrix\Main\Entity\Base::compileEntity(
$className,
[
'ID' => ['data_type' => 'integer'],
'IBLOCK_ELEMENT_ID' => ['data_type' => 'integer'],
'IBLOCK_PROPERTY_ID' => ['data_type' => 'integer'],
'VALUE' => ['data_type' => 'string'],
'VALUE_ENUM' => ['data_type' => 'string'],
'VALUE_NUM' => ['data_type' => 'float'],
'DESCRIPTION' => ['data_type' => 'string'],
],
['table_name' => sprintf('b_iblock_element_prop_m%s', $iblockId)]
);
return $entityMProps->getDataClass();
}
Еще заметил что работа через эти таблицы на пол порядка быстрее чем работа через orm для инфоблоков, ну и вообще orm для инфоблоков еще крайне сырой, часто входит в бесконечные циклы, поглощая всю оперативку.
Про то что $objItem->GetProperties(); использовать не надо, даже особо писать не буду)
Не очень знаю уместно ли тут использовать sprintf('b_iblock_element_prop_m%s', $iblockId) ибо если $iblockId это число, то и париться с искейпом не надо, да и делает ли sprintf искейп... Но влом было все строчки менять)
Достаточно собрать сущность инфоблока с помощью wakeUp и вызывать getList без джойна таблицы свойств, по крайней мере, явного:
$iblock = \Bitrix\Iblock\Iblock::wakeUp($iblockId);
$result = $iblock->getEntityDataClass()::getList([
'select' => ['ID', 'NAME', 'PROP_1.VALUE', 'PROP_2.VALUE']
]);
А дальше всё стандартно...
Как ускорить выборку в 1с Битрикс в 20 раз