1

我有一个rating略少于 300k 行的表和一个 SQL 查询:

  SELECT rt1.product_id as id1, rt2.product_id as id2, sum(1), sum(rt1.rate-rt2.rate) as sum 
FROM rating as rt1 
JOIN rating as rt2 ON rt1.user_id = rt2.user_id AND rt1.product_id != rt2.product_id 
group by rt1.product_id, rt2.product_id
LIMIT 1

问题是..它真的很慢。执行它需要 36 秒limit 1,而我需要无限制地执行它。正如我所知道的,它的减速是由GROUP BY部分引起的。无论来自哪个表 rt1 或 rt2,它在按一列分组时都可以正常工作。我也尝试过使用索引,我已经为 user_id、product_id、rate 和(user_id、product_id)创建了索引。

EXPLAIN也没有告诉我太多。

 id     select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
1   SIMPLE  rt1     ALL     PRIMARY,user_id,user_product    NULL    NULL    NULL    289700  Using temporary; Using filesort
1   SIMPLE  rt2     ref     PRIMARY,user_id,user_product    user_id     4   mgrshop.rt1.user_id     30  Using where

我只需要执行一次以生成一些数据,因此实现最佳时间并不重要,但合理。

有任何想法吗?

编辑。

全表架构

CREATE TABLE IF NOT EXISTS `rating` (
  `user_id` int(11) NOT NULL,
  `product_id` int(11) NOT NULL,
  `rate` int(11) NOT NULL,
  PRIMARY KEY (`user_id`,`product_id`),
  KEY `user_id` (`user_id`),
  KEY `product_id` (`product_id`),
  KEY `user_product` (`user_id`,`product_id`),
  KEY `rate` (`rate`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
4

4 回答 4

0

您的问题出在加入中,特别是AND rt1.product_id != rt2.product_id. 假设一个用户对 100 个产品进行了评分,对于该用户,此查询将在分组依据之前生成 99,000 行。对于 100 个评级中的每一个,该表都会连接回自身 99 次。

您试图通过此查询回答的问题是什么?根据这一点,可能会有一些更有效的方法。很难说出您在这里想要达到的目标。

于 2013-05-09T18:11:56.600 回答
0

首先我是通过临时表完成的。首先选择没有分组的行并将它们放入专门为它制作的表中。我得到了超过 11kk 行。然后我只是将它们从临时表中分组并放入决赛桌。

然后我也尝试在不创建任何其他表的情况下执行此操作,它也对我有用。

SELECT id1, id2, sum(count), sum(sum) 
FROM (SELECT rt1.product_id as id1, rt2.product_id as id2, 1 as count, rt1.rate - rt2.rate as sum 
        FROM rating as rt1 
        JOIN rating as rt2 ON rt1.user_id = rt2.user_id AND rt1.product_id != rt2.product_id) as temptab
GROUP BY id1, id2

最后得到了大约 19k 行。

执行时间:35.8669 对于我的一次性数据生成情况来说还不错。

于 2013-05-09T19:00:28.833 回答
0

除了 Declan_K 提到的关于您的交叉连接结果集(在您知道之前可能是 100k 行)之外,您可以通过更改为

rt1.product_id < rt2.product_id

代替

rt1.product_id != rt2.product_id

原因... 由于它们是相同的表/记录,因此您只需为 RT1.product_ID 循环一次。由于它低于最高值,因此您已经将最高值作为比较的一部分。就目前而言,如果您(对于单个用户)拥有 5 个产品(1-5 个),您将获得以下结果

(1,2)  (1,3)  (1,4)  (1,5)
(2,1)  (2,3)  (2,4)  (2,5)
(3,1)  (3,2)  (3,4)  (3,5)
(4,1)  (4,2)  (4,3)  (4,5)
(5,1)  (5,2)  (5,3)  (5,4)

通过更改为 LESS than,您将消除重复,例如 1,2 vs 2,1 1,3 vs 3,1

(1,2)  (1,3)  (1,4)  (1,5)
       (2,3)  (2,4)  (2,5)
              (3,4)  (3,5)
                     (4,5)

只是一个较小的结果集,一个人只有 5 个产品。

于 2013-05-09T19:28:43.737 回答
0

我的解决方案不是最简单的,但它应该解释一下并加快您的查询时间。

当你加入 MySQL 时,会创建一个临时表。放入该临时表的行越多,它就越有可能进入磁盘。磁盘很慢。新的临时表没有索引。没有索引的查询很慢。

EXPLAIN 语句中的第一行显示查询将首先连接,创建一大堆行,并将其粘贴到临时表中,并按产品 ID 分组。该key列是空的,表明它不能使用密钥。

我的解决方案是创建另一个表。这个另一个表将包含来自 JOIN 的所有相关列。您需要一个批处理作业来在后台更新表格。这将导致数据稍微陈旧,但运行速度会更快。

CREATE TABLE `rate_tmp` (
  userid ...,
  id1 ...,
  id2 ...,
  rate1 ...,
  rate2 ...,
  PRIMARY KEY (id1, id2, userid)
)

主键上的顺序非常重要。您的查询将如下所示:

SELECT userid, id1, id2, sum(1), sum(rate1-rate2) as sum
from rate_tmp
group by id1, id2;

此时它应该运行得非常快,因为虽然表仍然保存在磁盘上,但 MySQL 不必在查询时将数据写入磁盘。它还可以,更重要的是,使用您在临时表上的预定义索引。

于 2013-05-09T20:15:55.103 回答