【问题标题】:Prestashop product ORDER BY RAND optimizationPrestashop 产品 ORDER BY RAND 优化
【发布时间】:2016-07-22 04:20:00
【问题描述】:

我正在使用 Prestashop 相关产品 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 中实现这些方法:(这对我来说太复杂了,所以有人可以帮助我用一种新方法编辑这些行。有更好的经验和知识的人可以帮助我吗?或者发明一些东西比我建议的更好吗?

【问题讨论】:

  • 你是对的。 ORDER BY RAND() 是一个臭名昭著的反模式。您提出的解决方案是可以接受的。尤其是 Jan Kneschke 的。为了实现它们,如果您停止所有LIMIT 操作,您将需要某种方式来了解或估计将出现在结果集中的行数。高估是可以接受的,但高估越大,查询性能就越差。
  • 关键是我尝试实现他的方法,失败了。我只是无法在 Prestashop 中完成它。
  • 您对无限结果集长度的估计是多少?
  • @OllieJones 我不明白你的问题。你能否让我更清楚 - 编程新手。 PS我尝试了简单的方法,就像$sql .= ' AND RAND()<(SELECT (('.(int)$random_number_products.'/COUNT(*))*10) FROM '._DB_PREFIX_.'category_product) ORDER BY RAND() LIMIT '.(int)$random_number_products; 这样的简单方法,但它只显示很少的结果并且一直没有工作。
  • 暂时忘记编程难题。您的产品总列表中有多少行? (你店里有多少产品?)你怎么知道?

标签: php mysql optimization random prestashop


【解决方案1】:

假设您要从prefix_product 表的所有n 行集合中选择k = $random_number_products。这意味着您希望随机选择 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 则在磁盘上),然后将其排序为某种顺序,然后返回几行,然后丢弃其余行。在这些情况下获得良好性能的秘诀是尽早丢弃行,并对最小的结果集进行排序。

但它有效。因此,如果查询不经常运行,请保留它。在您的情况下,查询经常运行。

(Prestashop?ORDER BY RAND()?真的吗?当你完成这项工作时,向他们发送一份错误报告,其中包含你的修复。)

【讨论】:

  • 作为一个模糊相关的评论,我有一个观点,我从某个地方得到 MySQL RAND 是一个效率低下(因此速度慢/处理器昂贵)的命令,并且个人总是用它代替更快更流畅的 PHP 随机洗牌(或类似的)。你同意吗?
  • 不,我不同意RAND() 的 MySQL 实现本身在某种程度上是低效的。这是一个简单而廉价的数值计算。是ORDER BY RAND()RAND() 带来了坏名声。将计算RAND() 想象成克里斯托弗·哥伦布穿过街道与伊莎贝拉女王交谈。将ORDER BY 视为将买家送到另一个城市购买绳索。将结果集中的每一行视为向牙买加发送一艘船。这些是相对成本的准确衡量标准。
  • 啊,知道了,老实说,RAND 的用法我见过的最多的是ORDER BY,所以这解释了我是如何得出结论的。感谢您的澄清:-)
  • 好吧,我复制了所有内容,在 `$sql .= ' HAVING selector
  • 另一个编辑:当编辑 (5+$random_number_products)(900+$random_number_products) 或高于 800 的数字时,一切正常,它比以前快一点(对于 16 个随机产品,大约 1600 毫秒(它是 3000 毫秒或更多)之前)。问题是为什么?这 900+ 在做什么?有时它显示的产品也少于 16 个。
【解决方案2】:

几乎所有的算法都是 O(N) 或更糟。在我的blog on faster random searches 中,我将它们链接为“不够快”,包括 jan 的经典页面。我提出5个案例;我不知道哪个可以适用于你的情况:

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

所有案例的运行速度都比全表扫描快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-12
    • 2022-01-23
    • 2012-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-31
    相关资源
    最近更新 更多