2012-09-25 83 views
3

我有一個查詢有一些子查詢(內部選擇),我試着找出哪個更好的性能,更大的查詢或很多較小的查詢,當我的服務器上的所有時間都發生變化時,我發現很難嘗試和計算差異。MYSQL查詢優化,多個查詢或一個大型查詢

我使用下面的查詢一次返回10個結果,以使用分頁(偏移和限制)在我的網站上顯示。

SELECT adverts.*, breed.breed, breed.type, sellers.profile_name, sellers.logo, users.user_level , 
round(sqrt((((adverts.latitude - '51.558430') * (adverts.latitude - '51.558430')) * 69.1 * 69.1) + ((adverts.longitude - '-0.0069345') * (adverts.longitude - '-0.0069345') * 53 * 53)), 1) as distance, 
(SELECT advert_images.image_name FROM advert_images WHERE advert_images.advert_id = adverts.advert_id AND advert_images.main = 1 LIMIT 1) as imagename, 
(SELECT count(advert_images.advert_id) from advert_images WHERE advert_images.advert_id = adverts.advert_id) AS num_photos 
FROM adverts 
LEFT JOIN breed ON adverts.breed_id = breed.breed_id 
LEFT JOIN sellers ON (adverts.user_id = sellers.user_id) 
LEFT JOIN users ON (adverts.user_id = users.user_id) 
WHERE (adverts.status = 1) AND (adverts.approved = 1) 
AND (adverts.latitude BETWEEN 51.2692837281 AND 51.8475762719) AND (adverts.longitude BETWEEN -0.472015213613 AND 0.458146213613) 
having (distance <= '20') 
ORDER BY distance ASC 
LIMIT 0,10 

它會更好,從主查詢中刪除低於2個內選擇,然後在我的PHP循環,調用2選擇10次,一次在迴路中的每個記錄?

(SELECT advert_images.image_name FROM advert_images WHERE advert_images.advert_id = adverts.advert_id AND advert_images.main = 1 LIMIT 1) as imagename, 
(SELECT count(advert_images.advert_id) from advert_images WHERE advert_images.advert_id = adverts.advert_id) AS num_photos 

回答

1

避免子查詢

據我瞭解你內心的選擇,他們有兩個目的:找到任何名稱相關的圖片,並計數相關圖像的數量。你可能會實現雙方使用左連接,而不是內部的選擇:

SELECT …, 
     advert_images.image_name AS imagename, 
     COUNT(advert_images.advert_id) AS num_photos, 
     … 
FROM … 
    LEFT JOIN advert_images ON advert_images.advert_id = adverts.advert_id 
… 
GROUP BY adverts.advert_id 
… 
LIMIT 0,10 

我沒有試過,但也許是MySQL的發動機是足夠聰明,只進行查詢部分的行你」實際上返回。

請注意,根本沒有任何保證哪個圖像名稱此查詢將返回給定的一組圖像。如果你想得到可重現的結果,你應該在那裏使用一些聚合函數,例如MIN(advert_images.image_name)選擇字典中的第一個圖像。

單獨的選擇,但沒有環

如果上述方法無效,即查詢仍然會檢查advert_images所有行計算的結果,那麼你很可能真的被執行更好第二個查詢。然而,你可以嘗試避免for循環,而是在一個查詢中獲取所有這些行:

SELECT advert_images.image_name AS imagename, 
     COUNT(advert_images.advert_id) AS num_photos 
FROM advert_images 
WHERE advert_images.advert_id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 
GROUP BY advert_images.advert_id 

這個查詢中的十個參數對應於十行您當前生成結果。請注意,廣告沒有的相關照片將不包含在該結果中。因此,請確保在您的代碼中將num_photos設爲零,並將imagename設爲NULL

臨時表

另一種方式來實現你試圖做什麼是使用一個明確的臨時內存表:第一,你選擇你感興趣的結果,然後檢索所有相關信息。

CREATE TEMPORARY TABLE tmp 
SELECT adverts.advert_id, round(…) as distance 
FROM adverts 
WHERE (adverts.status = 1) AND (adverts.approved = 1) 
    AND (adverts.latitude BETWEEN 51.2692837281 AND 51.8475762719) 
    AND (adverts.longitude BETWEEN -0.472015213613 AND 0.458146213613) 
HAVING (distance <= 20) 
ORDER BY distance ASC 
LIMIT 0,10; 

SELECT tmp.distance, adverts.*, … 
     advert_images.image_name AS imagename, 
     COUNT(advert_images.advert_id) AS num_photos, 
     … 
FROM tmp 
    INNER JOIN adverts ON tmp.advert_id = adverts.advert_id 
    LEFT JOIN breed ON adverts.breed_id = breed.breed_id 
    LEFT JOIN sellers ON adverts.user_id = sellers.user_id 
    LEFT JOIN users ON adverts.user_id = users.user_id 
    LEFT JOIN advert_images ON advert_images.advert_id = adverts.advert_id 
GROUP BY adverts.advert_id 
ORDER BY tmp.distance ASC; 

DROP TABLE tmp; 

這將確保所有其他表格僅針對您當前正在處理的結果進行查詢。畢竟,關於advert_images表幾乎沒有什麼魔力,除了你可能需要多行。

子查詢作爲從前款的方式加入因子

大廈,你甚至可以避免管理的臨時表,代替它使用子查詢:

SELECT sub.distance, adverts.*, … 
     advert_images.image_name AS imagename, 
     COUNT(advert_images.advert_id) AS num_photos, 
     … 
FROM (SELECT adverts.advert_id, round(…) as distance 
     FROM adverts 
     WHERE (adverts.status = 1) AND (adverts.approved = 1) 
      AND (adverts.latitude BETWEEN 51.2692837281 AND 51.8475762719) 
      AND (adverts.longitude BETWEEN -0.472015213613 AND 0.458146213613) 
     HAVING (distance <= 20) 
     ORDER BY distance ASC 
     LIMIT 0,10; 
    ) AS sub 
    INNER JOIN adverts ON sub.advert_id = adverts.advert_id 
    LEFT JOIN breed ON adverts.breed_id = breed.breed_id 
    LEFT JOIN sellers ON (adverts.user_id = sellers.user_id) 
    LEFT JOIN users ON (adverts.user_id = users.user_id) 
    LEFT JOIN advert_images ON advert_images.advert_id = adverts.advert_id 
GROUP BY adverts.advert_id 
ORDER BY sub.distance ASC 

同樣,你確定相關行僅使用adverts表中的數據,並且僅連接其他表中的必需行。很可能,該中間結果將在內部存儲在一個臨時表中,但這取決於SQL服務器的決定。

+0

嗨,謝謝你的詳細解答。我嘗試了你提到的第一種方法,並刪除內部選擇,但查詢比原始文件慢。臨時表聽起來不錯,但是當查詢在服務器上每秒鐘運行約10次時,它可以正常工作,因爲網站非常繁忙? – user1052096

+0

@ user1052096,只要臨時表方法的兩個查詢足夠接近,應該沒有什麼影響。臨時表是連接本地的,所以不會有任何名稱衝突。與第二個查詢結果的許多列相比,「tmp」表的內存消耗應該很小,所以組合的解決方案可能使用的內存少於原始查詢。但我只是有另一個想法,我會立刻編輯成我的答案。 – MvG

+0

嗨MvG,感謝您的更新使用子查詢哪些工作,但我不知道它的更快。如果我只運行子查詢,它會在0.02秒內運行,但是如果我運行子查詢而不選擇advert_id,它將以0.01的速度運行兩次。 – user1052096

0

我認爲MySQL使用文件排序+臨時表來執行您的查詢。這就是爲什麼在大餐桌上你的建議會帶來更好的結果。一般來說,你最好執行較小的查詢,然後是1大。

+0

嗨,因爲即時通過距離這是一個計算的字段排序,它確實使用文件放慢,當表大。因此,對於主要查詢中的表中的每個記錄是否會運行2個內部選擇?如果是這樣,我會認爲只是在結果集上運行2個內部選擇,會更快。 – user1052096

+0

是內部選擇的將在每行上執行 –