2010-01-08 94 views
5

我还在学习MySQL。我可能会犯一个非常基本的错误,我准备在这里训练...正在执行count()计算减慢我的mysql查询?

这个查询试图做的是从我们的网站上选择最高的成员数量的书和食谱评论他们做了。

我做在SQL查询本身总的计算。查询速度很慢(9秒),并且绝对不会扩展,因为我们目前只有400个成员和几千条评论,并且它的增长速度非常快。

我相信它做一个全表扫描,在这里,而且在计算减缓下来,但我不知道的另一种方式来做到这一点,也许需要一些智慧。

这里的SQL语句:

SELECT users.*, COUNT(DISTINCT bookshelf.ID) AS titles, COUNT(DISTINCT book_reviews.ID) as bookreviews, COUNT(DISTINCT recipe_reviews.ID) AS numreviews, COUNT(DISTINCT book_reviews.ID) + COUNT(DISTINCT recipe_reviews.ID) as reviewtotal 
FROM users 
LEFT OUTER JOIN recipe_reviews ON recipe_reviews.user_id = users.ID 
LEFT OUTER JOIN book_reviews ON book_reviews.user_id = users.ID 
LEFT OUTER JOIN bookshelf ON users.ID = bookshelf.user_id 
GROUP BY users.ID 
ORDER BY reviewtotal DESC 
LIMIT 8 

这里的解释是:

+----+-------------+----------------+-------+-------------------+-------------------+---------+---------------------+------+---------------------------------+ 
| id | select_type | table   | type | possible_keys  | key    | key_len | ref     | rows | Extra       | 
+----+-------------+----------------+-------+-------------------+-------------------+---------+---------------------+------+---------------------------------+ 
| 1 | SIMPLE  | users   | index | NULL    | PRIMARY   | 4  | NULL    | 414 | Using temporary; Using filesort | 
| 1 | SIMPLE  | recipe_reviews | ref | recipe_reviews_fk | recipe_reviews_fk | 5  | users.ID   | 12 |         | 
| 1 | SIMPLE  | book_reviews | ref | user_id   | user_id   | 5  | users.ID   | 4 |         | 
| 1 | SIMPLE  | bookshelf  | ref | recipe_reviews_fk | recipe_reviews_fk | 5  | users.ID   | 13 |         | 
+----+-------------+----------------+-------+-------------------+-------------------+---------+---------------------+------+---------------------------------+ 

UPDATE &解决:

我意识到,和@recursive证实,该查询问题的根源。我从中得到笛卡尔产品。我重写它作为一个子查询系列和最终工作代码是在这里:

SELECT *, bookreviews + recipereviews AS totalreviews 
FROM (SELECT users.*, 
      (SELECT count(*) FROM bookshelf WHERE bookshelf.user_id = users.ID) as titles, 
      (SELECT count(*) FROM book_reviews WHERE book_reviews.user_id = users.ID) as bookreviews, 
      (SELECT count(*) FROM recipe_reviews WHERE recipe_reviews.user_id = users.ID) as recipereviews 
    FROM users) q 

这给我以毫秒为单位的结果。还有一些方法可以用JOIN做到这一点。如果你想跟上这一点,请参阅How to add together the results of several subqueries?

+0

我已经标记了递归的答案是正确的,虽然他的初步答案不是解决方案,但他将它钉在下面的注释中。 – mandel 2010-01-13 16:48:40

回答

2

你可以尝试看是否有改善从删除DISTINCT修饰符。假设DISTINCT ed字段无论如何都是主键,这可能会导致不必要的工作。

+0

我试过了,每个计数字段的计数都是几千。 – mandel 2010-01-08 20:19:57

+0

这听起来像你可能有重复记录你的数据库。你检查过你的桌子,看看它们是否有意义吗? – recursive 2010-01-08 23:12:28

+0

我会检查这些表格以确保 - 也许我需要将其中一些主键作为字段的组合,而不是直接的ID。例如,书架有ID,user_id,cookbook_id。 user_id和cookbook_id的组合应该是唯一的... – mandel 2010-01-09 02:56:17

3

对于这样的功能,它总是有益的一些类型的缓存工作...

它可能已经帮助每晚的基础上为所有用户创建和以及存储与用户的总和。这将有很大帮助并加快您的搜索速度。

你也应该以某种方式缓存此请求至少一分钟,五,因为你将在登录卫生组织独立执行同样的要求。

+0

我建议你除了总和之外还为该批次计算的总和配对一个“截至”日期。 – 2010-01-08 19:57:34

0

我经常发现,从较大的表创建一个较小的临时表将有明显的速度优势。

所以基本过程:

  1. 存储查询(与连接)到临时表
  2. 持续计数/汇总查询的临时表
2

指数上user_id所有表。如果尚未完成,这可以轻松地将这个查询加快几个数量级。

+0

唉,每个user_id字段都有索引。 – mandel 2010-01-08 20:18:18

0

为什么不是每个用户评语数量只是存储在用户表中的列?用户所做的每个新评论还需要将其用户记录审阅计数值增加1。

例如:

user_id user_name number_of_reviews 
1  bob  5 
2  jane  10 

鲍勃提出了新的审查,以及您自己的号码,以6:

review_id user_id review_text 
16  1  "Great!" 

user_id user_name number_of_reviews 
1  bob  6 
2  jane  10 

现在,你可以简单地获得前5的评论是这样的:

SELECT * FROM users ORDER BY number_of_reviews DESC LIMIT 5 
+0

在我的网站设计中,我早就考虑过这样的事情,并被告知(在SO上)我不应该依赖递增的查询列。但是,这可能是一个更普遍的警告,因为我已经开始使用增量列来处理一些事情。 – mandel 2010-01-08 20:23:37

+0

我想不出在你的设计中这个(number_of_reviews)有风险的问题。如果它代表真实的实物库存或金额,我会建议更谨慎。但否则这应该就足够了。不要让自己难过! – 2010-01-08 21:47:16

+0

另外,如果您有疑问计数已关闭,则可以重新计算每个用户在“脱机”数据库副本上的评论数,以查看是否有任何区别,通过执行上述操作(count * with一个连接)。 – 2010-01-08 21:49:47

1

您试图用此查询完成太多事情。我发现你的db/query设计有问题。为什么你在book_shelf中有一个user_id?如何下表结构

CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT , 
name VARCHAR(20) NOT NULL , 
PRIMARY KEY (`id`) 
) 

CREATE TABLE recipe_reviews (
id INT NOT NULL AUTO_INCREMENT , 
review VARCHAR(20), 
user_id INT, 
PRIMARY KEY (id), 
FOREIGN KEY (user_id) references users(id) 
) 

CREATE TABLE bookshelf (
id INT NOT NULL AUTO_INCREMENT , 
name VARCHAR(20) NOT NULL , 
PRIMARY KEY (id) 
) 

CREATE TABLE book_reviews (
id INT NOT NULL AUTO_INCREMENT , 
review VARCHAR(20), 
user_id INT, 
bookshelf_id INT, 
PRIMARY KEY (id), 
FOREIGN KEY (user_id) references users(id), 
FOREIGN KEY (bookshelf_id) references bookshelf(id) 
) 

如果你想要聚合的用户,这里是你的查询:

SELECT users.*, COUNT(book_reviews.ID) as bookreviews, COUNT(recipe_reviews.ID) AS recipereviews, bookreviews + recipereviews as reviewtotal 
    FROM users 
    LEFT OUTER JOIN recipe_reviews ON recipe_reviews.user_id = users.ID 
    LEFT OUTER JOIN book_reviews ON book_reviews.user_id = users.ID 
    GROUP BY users.ID 
    ORDER BY reviewtotal DESC 

您也可以聚集在两个用户和书籍,然后包括recipe_reviews不合理。 PS:你不需要DISTINCT,因为你有密钥处理这件事。

+0

感谢您的想法。但是,书架上有一个user_id,因为每个用户都有自己的书架,可以在该站点上添加任何书籍,因此必须与user_id关联才能找出每个用户在书架上有多少本书。 至于外键,我为这些使用MyISAM表,所以我不能使用FKs。切换到InnoDB和FK会对性能产生真正的影响吗? – mandel 2010-01-08 20:34:07

+1

由于在插入过程中必须执行的约束检查(以及可能的更新/删除),外键通常会影响性能。但特别是对于使用此查询进行数据检索,我没有看到任何区别,因为您有索引。尽管我会去InnoDB--至少为了数据完整性的目的。 – 2010-01-08 21:18:49

2

您需要在user_id上创建索引(如果可能,最好是聚簇索引)。

你确定你已经完成了吗?请记住,拥有一个外键不会自动生成该键上的索引。

如果您要加入4个1k行的B树,这肯定不会花费9秒,而是几毫秒。

长执行时间表示您正在为每个用户执行表扫描。

我相当确信这是正确的答案。

您的查询是罚款,除非您计算您的评论两次,用bookreviews和numreviews替换第二个计数。