Автор Тема: Ускорение работы магазина (1.5.*)  (Прочитано 686 раз)

16 Августа 2015, 16:38:05
  • Новичок
  • *
  • Сообщений: 3
  • Репутация: +0/-0
  • Сообщество PrestaShop
    • Просмотр профиля
Всем доброго времени суток.

Столкнулся с проблемой очень низкой производительности магазина, даже на мощном железе.
Судя по различным профайлам (как встроенным в престу, так и xdebug) существует несколько причин замедления, но все они связаны в той или иной степени с количеством аттрибутов и комбинаций товаров. В базе магазина достаточно много товаров с большим количеством свойств.

Теперь сами проблемы:
1. После перехода с 1.5.2 на 1.5.6 резко снизилась скорость загрузки страниц некоторых категорий товаров. С 2-3 сек до 25-30сек. 90% этого времени занимает выполнение следующего запроса к базе данных из /classes/Category.php стр. ~615 (метод getProducts()):
SELECT
  p.*,
  product_shop.*,
  stock.out_of_stock,
  IFNULL(stock.quantity, 0) AS quantity,
  MAX(product_attribute_shop.id_product_attribute) id_product_attribute,
  product_attribute_shop.minimal_quantity AS product_attribute_minimal_quantity,
  pl.`description`,
  pl.`description_short`,
  pl.`available_now`,
  pl.`available_later`,
  pl.`link_rewrite`,
  pl.`meta_description`,
  pl.`meta_keywords`,
  pl.`meta_title`,
  pl.`name`,
  MAX(image_shop.`id_image`) id_image,
  il.`legend`,
  m.`name` AS manufacturer_name,
  cl.`name` AS category_default,
  DATEDIFF(product_shop.`date_add`, DATE_SUB(NOW(),
  INTERVAL 3
  DAY)) > 0 AS new,
  product_shop.price AS orderprice
FROM `mb_category_product` cp
  LEFT JOIN `mb_product` p
    ON p.`id_product` = cp.`id_product`
  INNER JOIN mb_product_shop product_shop
    ON (product_shop.id_product = p.id_product
    AND product_shop.id_shop = 1)
  LEFT JOIN `mb_product_attribute` pa
    ON (p.`id_product` = pa.`id_product`)
  LEFT JOIN mb_product_attribute_shop product_attribute_shop
    ON (product_attribute_shop.id_product_attribute = pa.id_product_attribute
    AND product_attribute_shop.id_shop = 1
    AND product_attribute_shop.`default_on` = 1)
  LEFT
  JOIN mb_stock_available stock
    ON (stock.id_product = p.id_product
    AND stock.id_product_attribute = IFNULL(`product_attribute_shop`.id_product_attribute, 0)
    AND stock.id_shop = 1)
  LEFT JOIN `mb_category_lang` cl
    ON (product_shop.`id_category_default` = cl.`id_category`
    AND cl.`id_lang` = 1
    AND cl.id_shop = 1)
  LEFT JOIN `mb_product_lang` pl
    ON (p.`id_product` = pl.`id_product`
    AND pl.`id_lang` = 1
    AND pl.id_shop = 1)
  LEFT JOIN `mb_image` i
    ON (i.`id_product` = p.`id_product`)
  LEFT JOIN mb_image_shop image_shop
    ON (image_shop.id_image = i.id_image
    AND image_shop.id_shop = 1
    AND image_shop.cover = 1)
  LEFT JOIN `mb_image_lang` il
    ON (image_shop.`id_image` = il.`id_image`
    AND il.`id_lang` = 1)
  LEFT JOIN `mb_manufacturer` m
    ON m.`id_manufacturer` = p.`id_manufacturer`
WHERE product_shop.`id_shop` = 1
AND cp.`id_category` = 18
AND product_shop.`active` = 1
AND product_shop.`visibility` IN ("both", "catalog")
GROUP BY product_shop.id_product
ORDER BY `orderprice` ASC
LIMIT 0, 20
Если убрать соединение с таблицей stock то время выполнения запроса с 27 секунд падает до 0.8 сек. В магазине не используется контроль остатков. Временно поставил заплатку в запрос, категории стали открываться приемлимое время.

Вся загвоздка в том, что в старой версии престы (1.5.6.2), этот запрос вообще не выполнялся о_О Возможно есть некая волшебная настройка, которая приводит к выполнению данного запроса?

16 Августа 2015, 17:07:24
Ответ #1
  • Ветеран
  • *****
  • Сообщений: 782
  • Репутация: +7/-0
  • Сообщество PrestaShop
    • Просмотр профиля
Подстроить запрос можно только под свои условия. Вот как в вашем случае, когда не нужен контроль остатков товаров.
16 Августа 2015, 17:16:37
Ответ #2
  • Ветеран
  • *****
  • Сообщений: 33028
  • Репутация: +26761/-0
    • Просмотр профиля
Вся загвоздка в том, что в старой версии престы (1.5.6.2), этот запрос вообще не выполнялся о_О Возможно есть некая волшебная настройка, которая приводит к выполнению данного запроса?
Пишите же, что категории стали открываться приемлимое время.
Значит запрос работает? Зачем вам старая версия если вы обновились?

По оптмизиции и ускорению prestashop есть много тем. А по замене sql запросв не так уж и много информации. Тут действительно какждый под себя делает. Например, как в этой теме
http://prestashop-forum.ru/index.php/topic,4833.msg29137.html#msg29137
16 Августа 2015, 17:18:20
Ответ #3
  • Ветеран
  • *****
  • Сообщений: 33028
  • Репутация: +26761/-0
    • Просмотр профиля
Может у вас много комбинаций у товара? Обычно тормозит в при большом количестве атрибутов.
18 Августа 2015, 16:02:55
Ответ #4
  • Новичок
  • *
  • Сообщений: 3
  • Репутация: +0/-0
  • Сообщество PrestaShop
    • Просмотр профиля
Может у вас много комбинаций у товара? Обычно тормозит в при большом количестве атрибутов.
Именно так. В своё время престу выбрали именно из-за возможности задавать множество свойств и комбинаций.

Пишите же, что категории стали открываться приемлимое время.
Значит запрос работает? Зачем вам старая версия если вы обновились?
К сожалению, этот запрос не единственный.
Вопрос в другому, в предыдущей версии престы (1.5.4.1) тот же самый код, но он просто не вызывается. При открытии категории происходит вызов метода assignProductList() из /controllers/front/CategoryController.php
public function assignProductList()
{
$hookExecuted = false;
Hook::exec('actionProductListOverride', array(
'nbProducts' => &$this->nbProducts,
'catProducts' => &$this->cat_products,
'hookExecuted' => &$hookExecuted,
));

// The hook was not executed, standard working
if (!$hookExecuted)
{
$this->context->smarty->assign('categoryNameComplement', '');
$this->nbProducts = $this->category->getProducts(null, null, null, $this->orderBy, $this->orderWay, true);
$this->pagination((int)$this->nbProducts); // Pagination must be call after "getProducts"
$this->cat_products = $this->category->getProducts($this->context->language->id, (int)$this->p, (int)$this->n, $this->orderBy, $this->orderWay);
}
// Hook executed, use the override
else
// Pagination must be call after "getProducts"
$this->pagination($this->nbProducts);

foreach ($this->cat_products as &$product)
{
if ($product['id_product_attribute'] && isset($product['product_attribute_minimal_quantity']))
$product['minimal_quantity'] = $product['product_attribute_minimal_quantity'];
}

$this->context->smarty->assign('nb_products', $this->nbProducts);
}

Я не силен в php, с этим языком не работал больше 5 лет. В предыдущей версии условие if (!$hookExecuted) не срабатывало и всё "летало". Почему оно срабатывает в новой версии?
$hookExecuted = false;
Hook::exec('actionProductListOverride', array(
'nbProducts' => &$this->nbProducts,
'catProducts' => &$this->cat_products,
'hookExecuted' => &$hookExecuted,
));
После вызова Hook::exec() в старой версии $hookExecuted = true.

Я правильно понимаю, что в старой версии срабатывал какой-то хук, который брал на себя обработку списка товаров для показа в категориях или подготовки данных к отображению? Это мог быть какой-то модуль?

 
18 Августа 2015, 16:04:44
Ответ #5
  • Новичок
  • *
  • Сообщений: 3
  • Репутация: +0/-0
  • Сообщество PrestaShop
    • Просмотр профиля
Еще одно "узкое" место в запросах в 1.5.6.* и снова связано с остатками товаров по аттрибутам.

Модуль blocklayered (/modules/blocklayered/blocklayered.php) метод getProductByFilters() который вызывается из getProducts(). В методе выполняется запрос к БД:
$this->nbr_products = count($product_id_list);

if ($this->nbr_products == 0)
$this->products = array();
else
{
$n = (int)Tools::getValue('n', Configuration::get('PS_PRODUCTS_PER_PAGE'));
$nb_day_new_product = (Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20);


$this->products = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT
p.*,
'.($alias_where == 'p' ? '' : 'product_shop.*,' ).'
'.$alias_where.'.id_category_default,
pl.*,
MAX(image_shop.`id_image`) id_image,
il.legend,
m.name manufacturer_name,
MAX(pa.id_product_attribute) id_product_attribute,
DATEDIFF('.$alias_where.'.`date_add`, DATE_SUB(NOW(), INTERVAL '.(int)$nb_day_new_product.' DAY)) > 0 AS new,
stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity
FROM `'._DB_PREFIX_.'category_product` cp
LEFT JOIN '._DB_PREFIX_.'category c ON (c.id_category = cp.id_category)
LEFT JOIN `'._DB_PREFIX_.'product` p ON p.`id_product` = cp.`id_product`
'.Shop::addSqlAssociation('product', 'p').'
'.Product::sqlStock('p', null, false, Context::getContext()->shop).'
LEFT JOIN '._DB_PREFIX_.'product_lang pl ON (pl.id_product = p.id_product'.Shop::addSqlRestrictionOnLang('pl').' AND pl.id_lang = '.(int)$cookie->id_lang.')
LEFT JOIN `'._DB_PREFIX_.'image` i  ON (i.`id_product` = p.`id_product`)'.
Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1').'
LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$cookie->id_lang.')
LEFT JOIN '._DB_PREFIX_.'manufacturer m ON (m.id_manufacturer = p.id_manufacturer)
LEFT JOIN '._DB_PREFIX_.'product_attribute pa ON (p.id_product = pa.id_product)
WHERE '.$alias_where.'.`active` = 1 AND '.$alias_where.'.`visibility` IN ("both", "catalog")
AND '.(Configuration::get('PS_LAYERED_FULL_TREE') ? 'c.nleft >= '.(int)$parent->nleft.' AND c.nright <= '.(int)$parent->nright : 'c.id_category = '.(int)$id_parent).'
AND c.active = 1
AND p.id_product IN ('.implode(',', $product_id_list).')
GROUP BY product_shop.id_product
ORDER BY '.Tools::getProductsOrder('by', Tools::getValue('orderby'), true).' '.Tools::getProductsOrder('way', Tools::getValue('orderway')).
' LIMIT '.(((int)$this->page - 1) * $n.','.$n));            
}

Если из запроса вырезать таблицы stock то скорость его выполнения возрастает на порядки (в версии 1.5.4 остатки не цеплялись в этом месте). Вариант запросе без остатков:
                        $this->products = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
SELECT
p.*,
'.($alias_where == 'p' ? '' : 'product_shop.*,' ).'
'.$alias_where.'.id_category_default,
pl.*,
MAX(image_shop.`id_image`) id_image,
il.legend,
m.name manufacturer_name,
MAX(pa.id_product_attribute) id_product_attribute,
DATEDIFF('.$alias_where.'.`date_add`, DATE_SUB(NOW(), INTERVAL '.(int)$nb_day_new_product.' DAY)) > 0 AS new
FROM `'._DB_PREFIX_.'category_product` cp
LEFT JOIN '._DB_PREFIX_.'category c ON (c.id_category = cp.id_category)
LEFT JOIN `'._DB_PREFIX_.'product` p ON p.`id_product` = cp.`id_product`
'.Shop::addSqlAssociation('product', 'p').'
LEFT JOIN '._DB_PREFIX_.'product_lang pl ON (pl.id_product = p.id_product'.Shop::addSqlRestrictionOnLang('pl').' AND pl.id_lang = '.(int)$cookie->id_lang.')
LEFT JOIN `'._DB_PREFIX_.'image` i  ON (i.`id_product` = p.`id_product`)'.
                Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1').'
LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$cookie->id_lang.')
LEFT JOIN '._DB_PREFIX_.'manufacturer m ON (m.id_manufacturer = p.id_manufacturer)
LEFT JOIN '._DB_PREFIX_.'product_attribute pa ON (p.id_product = pa.id_product)
WHERE '.$alias_where.'.`active` = 1 AND '.$alias_where.'.`visibility` IN ("both", "catalog")
AND '.(Configuration::get('PS_LAYERED_FULL_TREE') ? 'c.nleft >= '.(int)$parent->nleft.' AND c.nright <= '.(int)$parent->nright : 'c.id_category = '.(int)$id_parent).'
AND c.active = 1
AND p.id_product IN ('.implode(',', $product_id_list).')
GROUP BY product_shop.id_product
ORDER BY '.Tools::getProductsOrder('by', Tools::getValue('orderby'), true).' '.Tools::getProductsOrder('way', Tools::getValue('orderway')).
                ' LIMIT '.(((int)$this->page - 1) * $n.','.$n));
Со старой версией запроса один из разделов сайта не открывался :( и mysql сервер уходил в нирвану даже на мощном железе с  ssd дисками и т.д.
18 Августа 2015, 18:22:08
Ответ #6
  • Ветеран
  • *****
  • Сообщений: 20889
  • Репутация: +25279/-0
  • Prestashop - просто и эффективно
    • Просмотр профиля
То что менялось в версиях prestashop вы можете найти в описании к каждой версии
https://www.prestashop.com/en/developers-versions/changelog/1.5.6.2-stable
Основные отличия prestashop ветки 1.5 начиная с версии 1.5.4.1

Обновите prestashop до версии 1.6. Это позволит использовать новые версии модулей и улучшит работу с кешированием.
Например, в новой версии prestashop модуль blocklayered лучше работает с кеширование запросов. Скорость значительно возрасла.
Эксперт Prestashop - решения всех проблем, написание модулей, создание тем для интернет-магазинов под Prestashop.
Эксперт Magento - создам сайт на Magento, программирование кастомных модулей для Magento, кастомизация тем Magento.
Лучшие цены!!!