【问题标题】:How to fast return and count rows from one table based on filter by two another tables如何根据另一个表的过滤器从一个表中快速返回和计算行数
【发布时间】:2020-10-24 12:16:23
【问题描述】:

我有 3 张桌子:

产品(1 500 000 行)

+------------+-------------+-------------+-------------+
|     id     |     shop    |    brand    |   category  |
+------------+-------------+-------------+-------------+

id int(11), AI, PK;
shop int(11);
brand int(11);
category int(11);

标签(1 300 000 行)

+------------+------------+-------------+
|     id     |   product  |     tag     |
+------------+------------+-------------+

id int(11), AI, PK;
product int(11) - it's id from 'products' table;
tag int(11);

大小(1 300 000 行)

+------------+------------+-------------+
|     id     |   product  |     size    |
+------------+------------+-------------+

id int(11), AI, PK;
product int(11) - it's id from 'products' table;
tag int(11);

每一列都有索引。

我需要过滤可能按品牌、商店、类别、标签、尺寸过滤的所有产品(包括计数行)。因此,例如,如果我只需要显示品牌 = 1 和商店 = 1 的产品,我将不会使用 tagssizes 表。这很简单。但问题是,当我想展示属于某些类别、某些商店、具有某些品牌、某些标签和某些尺寸的所有产品时。

有很多组合可以缓存所有这些并更新。获取实时计数的成本约为 6 秒。由于分页的限制,显示所有过滤的产品很快。

我已经花了一周多的时间来测试什么更快,哪个数据库的选项和结构更好,但绝对不知道让它比这更快:

select p.id
  FROM
products p
  INNER JOIN tags t ON(p.id = t.product and tags=2) 
  INNER JOIN sizes s ON(p.id = s.product and sizes in (1,2))
  WHERE
p.shop in (1,13,31,65)
  and 
p.category in (270,126,127,144,143,145,146,839,147,148,149,150,158,151,155,157,123,124,128,129,602,120,121,122,152,153,154,482,526,40,42,46,115,119,138,142,133,135,136,137,130,131,132,116,117,32,103,485,112,113,114,566,39,107,108,109,110,118,265,516,527,528,529,530,106,159,161,185,30,86,87,88,89,90,91,92,267,531,532,104,105,28,29,31,33,34,35,36,37,38,41,43,44,45,102,165,269,487,2)
  GROUP BY p.id

现在只有 1,500 万个产品,基于具有 20 万个产品的类别的过滤(由于树顶)正在加载(计数和显示数据)大约 5 秒。这是因为过滤的产品在 200k 产品类别中有 13k 行的结果。但仍然需要在所有数据表中进行比较和搜索。解决方案之一可能是将所有产品分离到 f.e. 10 个表格类别(用于服装、电子产品、药店、食品......)并在那里进行搜索。但我不认为这是解决问题的最佳方法。只是比现在更好。

使用order by p.id DESC limit 0,120 执行此 SQL 的结果非常快。大约 0,8 秒,但在某些情况下计算它们大约是 8-12 秒。是否有机会通过更改 SQL 查询或更改 DB 结构来获得更快的结果和计数?

感谢您的帮助。

【问题讨论】:

  • “每列都有索引”不一定是使用索引的最佳解决方案。覆盖索引可能会提高速度(请参阅:stackoverflow.com/questions/8213235/…
  • 您需要一次获取所有数据吗?限制获取的行可能也有帮助
  • 试验索引和 Luuk 建议的覆盖索引的好方法是使用 EXPLAIN
  • @Luuk 我会尽力让你知道的。
  • @MetaPakistani 正如我所说,我不需要每页仅使用 120 个数据。但是,如果我想知道显示了多少页并使用类别标题中的数字,我需要计算所有这些。

标签: php mysql performance filter database-performance


【解决方案1】:
  1. 尝试为所有类型的过滤创建单独的缓存。例如:
$tagProductIds = $yourCachingSystem->getAllIdsByTag($_POST['tag']);
$sizeProductIds = $yourCachingSystem->getAllIdsBySize($_POST['size']); 
$categoryProductIds = $yourCachingSystem->getAllIdsByCategory($_POST['category']); 

//...other filters by the product table

$neededProductIds = array_intersect($tagProductIds, $sizeProductIds, $categoryProductIds
//...other filtered ids
);

return $yourOrm->fetchAll(" ... product.id IN ($neededProductIds)");
  1. 您可以使用MEMORY 引擎创建 MySQL 表。这些表存储在 RAM 中,也许这会加快数据的提取速度。
CREATE TABLE if not exists  `products_tmp`
(
   //cols and indexes
) ENGINE = MEMORY
SELECT * products; //tags, sizes

【讨论】:

  • 1.有趣的解决方案。我会尝试。 2.不明白。你的意思是用在梳子上。带选项 nr。 1 或者它是第二个选项。结果会是 100% 还是只是“记忆中的内容”?
  • 第二种解决方案是另一种解决方案。在 MEMORY 引擎上创建表的副本并从中进行选择。但是您需要使 RAM 中的表保持最新。如果您要更新“产品”,您还应该更新 products_tmp。重新启动服务器后,还必须重新创建表。
  • 天啊!相同,但写为 opt。 1 是 0.3 秒!!!非常感谢您对问题的这种不同看法。 :)
  • 不客气:) 您可能还需要预热缓存。为此,您可以将 CRON 用于: foreach($tags as $tag) { $yourCachingSystem->getAllIdsByTag($tag); }
猜你喜欢
  • 2020-09-24
  • 1970-01-01
  • 1970-01-01
  • 2013-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-15
相关资源
最近更新 更多