使用自连接按两列分组时缓慢的SQL查询

时间:2013-05-09 17:57:24

标签: mysql sql performance join group-by

我有一个表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

问题是......它真的很慢。使用limit 1执行它需要36秒,而我需要无限制地执行它。 正如我所知,由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 个答案:

答案 0 :(得分:0)

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

您尝试使用此查询回答的问题是什么?根据这一点,可能会有一些更有效的方法。很难说出你想要在这里实现的目标。

答案 1 :(得分: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 对我的一次性数据生成情况来说也不错。

答案 2 :(得分: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,你将消除重复,如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个产品。

答案 3 :(得分: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在查询时不必将数据写入磁盘。它也可以,更重要的是,使用临时表上的预定义索引。