2010-01-08 89 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替換第二個計數。