繁体   English   中英

通过自连接按两列分组时的SQL查询速度慢

[英]Slow SQL query when grouping by two columns with self join

我的表rating略低于30万行,并且有一个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;

您的问题出在AND rt1.product_id != rt2.product_id ,特别是AND rt1.product_id != rt2.product_id 假设某个用户对100个产品进行了评分,那么该用户在进行分组依据之前将生成99,000行。 对于100个评分中的每个评分,该表都会重新链接到自身99次。

您要通过此查询回答的问题是什么? 因此,可能会有一些更有效的方法。 很难在这里说出您要实现的目标。

首先,我是通过临时表完成的。 首先选择没有分组的行,然后将它们放入专门为其创建的表中。 我超过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对于我一次性生成数据的情况还不错。

除了Declan_K提到的关于交叉联接结果集的内容(在您不知道的情况下可能是10万行)之外,您还可以通过更改为

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与2,1 1,3与3,1之类的重复项

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

结果集稍微小了一点,一个人只有5个产品。

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

当您加入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不必在查询时将数据写入磁盘。 更重要的是,它还可以使用临时表上的预定义索引。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM