Prestashop 产品 ORDER BY RAND 优化

Prestashop product ORDER BY RAND optimization

我正在使用 Prestashop Related Product PRO 插件,它在显示来自同一类别的一些随机产品时非常有用,但它使用默认的 Prestashop ORDER BY RAND 方法,当我启用此方法时显示 24产品页面空闲加载时间为 4000 毫秒到 7000 毫秒的随机产品,因为它正在等待数据库显示一些随机产品。

当我将它减少到 8 个产品时,它是 1500-2000 毫秒,但在 SEO 分数方面仍然太长。我一直在寻找插入的解决方案,但我无法弄清楚,但我发现了这个:

Presta 1.6.1.4 在这里。在 classes\Category.php 中,关于第 744 行的内容是这样的:

$sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) AS quantity'.(Combination::isFeatureActive() ? ', IFNULL(product_attribute_shop.id_product_attribute, 0) AS 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`, image_shop.`id_image` id_image,
                il.`legend` as legend, m.`name` AS manufacturer_name, cl.`name` AS category_default,
                DATEDIFF(product_shop.`date_add`, DATE_SUB("'.date('Y-m-d').' 00:00:00",
                INTERVAL '.(int)$nb_days_new_product.' DAY)) > 0 AS new, product_shop.price AS orderprice
            FROM `'._DB_PREFIX_.'category_product` cp
            LEFT JOIN `'._DB_PREFIX_.'product` p
                ON p.`id_product` = cp.`id_product`
            '.Shop::addSqlAssociation('product', 'p').
            (Combination::isFeatureActive() ? ' LEFT JOIN `'._DB_PREFIX_.'product_attribute_shop` product_attribute_shop
            ON (p.`id_product` = product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop='.(int)$context->shop->id.')':'').'
            '.Product::sqlStock('p', 0).'
            LEFT JOIN `'._DB_PREFIX_.'category_lang` cl
                ON (product_shop.`id_category_default` = cl.`id_category`
                AND cl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('cl').')
            LEFT JOIN `'._DB_PREFIX_.'product_lang` pl
                ON (p.`id_product` = pl.`id_product`
                AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').')
            LEFT JOIN `'._DB_PREFIX_.'image_shop` image_shop
                ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop='.(int)$context->shop->id.')
            LEFT JOIN `'._DB_PREFIX_.'image_lang` il
                ON (image_shop.`id_image` = il.`id_image`
                AND il.`id_lang` = '.(int)$id_lang.')
            LEFT JOIN `'._DB_PREFIX_.'manufacturer` m
                ON m.`id_manufacturer` = p.`id_manufacturer`
            WHERE product_shop.`id_shop` = '.(int)$context->shop->id.'
                AND cp.`id_category` = '.(int)$this->id
                .($active ? ' AND product_shop.`active` = 1' : '')
                .($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '')
                .($id_supplier ? ' AND p.id_supplier = '.(int)$id_supplier : '');

    if ($random === true) {
        $sql .= ' ORDER BY RAND() LIMIT '.(int)$random_number_products;
    } else {
        $sql .= ' ORDER BY '.(!empty($order_by_prefix) ? $order_by_prefix.'.' : '').'`'.bqSQL($order_by).'` '.pSQL($order_way).'
        LIMIT '.(((int)$p - 1) * (int)$n).','.(int)$n;
    }

如果我没记错的话,它负责在类别页面(包括一些插件)上展示产品。 rand() 的命令真的很糟糕。如您所见,有一行

if ($random === true) {
        $sql .= ' ORDER BY RAND() LIMIT '.(int)$random_number_products;
    }

在我看来,这是我们可以开始进行一些更改的关键。我找到了一篇关于优化 MySQL ORDER BY RAND 查询的文章,结果非常令人满意。你可以在这里阅读它们

https://www.warpconduit.net/2011/03/23/selecting-a-random-record-using-mysql-benchmark-results/

这里

http://jan.kneschke.de/projects/mysql/order-by-rand/(在这种情况下,结果非常惊人)

但有一个案例。我的编程技能仅限于将这些方法实施到 Prestashop 中:(这对我来说太复杂了,所以有人可以帮助我用一种新方法编辑这些行。有更好经验和知识的人可以帮助我吗?或者发明一些东西比我提出的更好吗?

假设您要从所有 n 行的集合中选择 k = $random_number_products你的 prefix_product table。这意味着您希望随机选择 k / n 行。

RAND() 生成范围 [0,1] 内的伪随机数。所以要实现 k / n 选择,你需要 RAND() <= k / n 或者,将它移动到整数比较域, n*RAND() <= k .如果您的应用程序在您的查询选择的随机行太少的情况下失败,您需要提高 k 值以增加选择任何行的概率。我们只说 k+5 为好。

然后您需要在查询中 SELECT 子句的末尾添加一些内容,就在 orderprice 之后,如下所示:

      SELECT...,
      INTERVAL '.(int)$nb_days_new_product.' DAY)) > 0 AS new, product_shop.price AS orderprice,
      (SELECT COUNT(*) FROM `'._DB_PREFIX_.'product`) * RAND() AS selector
      ...

这会为结果集中的每一行分配一个介于 0 和 COUNT(*) 值之间的随机选择器。

最后,在您的查询末尾放上这个。

if ($random === true) {
    $sql .= ' HAVING selector <= ', (5+$random_number_products);
    $sql .= ' ORDER BY selector LIMIT '.(int)$random_number_products;
}

我认为这会奏效。

  • HAVING 选择行的子集。对于此特定应用程序,您需要 HAVING 而不是 WHERE,因为它指的是生成的列。
  • 5+ 稍微高估了该子集的大小。
  • ORDER BY 随机化所选行的顺序。
  • LIMIT 删除因高估而产生的任何额外行。

我可能遗漏了一些语法错误。如果是这样,请原谅。

ORDER BY RAND() LIMIT n 是令人讨厌的反模式 ORDER BY anything LIMIT n 的一个特别臭的例子。浪费服务器资源。它生成整个结果集(在服务器 RAM 中或磁盘上,如果它不适合 RAM),然后将其排序为某种顺序,然后 returns 几行,然后丢弃其余部分。在这些情况下获得良好性能的秘诀是尽早丢弃行,并对最小的结果集进行排序。

但它有效。所以如果查询不经常运行,就保留它。在您的情况下,查询经常运行。

(Prestashop?ORDER BY RAND()?真的吗?当你开始工作时,向他们发送错误报告并修复你的问题。)

几乎所有的算法都是 O(N) 或更糟。在我的blog on faster random searches,我link把他们当成'not adequately fast',包括jan的经典页面。我介绍了 5 个案例;不知道哪个适合你的情况:

  • 案例:连续 AUTO_INCREMENT 没有间隔,返回 1 行
  • 大小写:连续 AUTO_INCREMENT 无间隙,10 行
  • 案例:AUTO_INCREMENT 有间隙,返回 1 行
  • 案例:用于随机化的额外 FLOAT 列
  • 大小写:UUID 或 MD5 列

所有案例 运行 比完整 table 扫描更快。