2012-10-07 66 views
2

我目前運行一個網站,可以追蹤列表中的最新分數和分數。該列表包含數千個經常更新的條目,並且該列表應該可以通過這些評分和評級列進行排序。通過大量連接優化MySQL查詢

我獲得此數據的SQL目前看起來像(大約):

SELECT e.*, SUM(sa.amount) AS score, AVG(ra.rating) AS rating 
FROM entries e 
LEFT JOIN score_adjustments sa ON sa.entry_id = e.id 
    HAVING sa.created BETWEEN ... AND ... 
LEFT JOIN rating_adjustments ra ON ra.entry_id = e.id 
    HAVING ra.rating > 0 
ORDER BY score 
LIMIT 0, 10 

凡表(簡體):

entries: 
    id: INT(11) PRIMARY 
    ...other data... 

score_adjustments: 
    id: INT(11), PRIMARY 
    entry_id: INT(11), INDEX, FOREIGN KEY (entries.id) 
    created: DATETIME 
    amount: INT(4) 

rating_adjustments: 
    id: INT(11), PRIMARY 
    entry_id: INT(11), INDEX, FOREIGN KEY (entries.id) 
    rating: DOUBLE 

有大約300000個score_adjustments條目,它們生長在約一天5000個。 rating_adjustments約爲1/4。

現在,我沒有DBA專家,但我猜打電話SUM()AVG()所有的時間是不是一件好事 - 尤其是當sara包含的記錄數以十萬計 - 對嗎?

我已經對查詢進行了緩存,但我希望查詢本身速度快 - 但仍儘可能保持最新。我想知道是否有人可以共享任何解決方案來優化這種重加入/聚合查詢?如果有必要,我願意做出結構上的改變。

編輯1

添加了有關查詢更多信息。

+0

實際查詢會更好。 –

+0

幾個索引通常會這樣做,但沒有表結構,當前索引,實際查詢和數據量,這是瘋狂的猜測。我們得到這一切後,這只是猜測。 – GolezTrol

+0

@ypercube添加了查詢的關閉表示 – Ryall

回答

2

您的數據不好clustered

InnoDB將存儲具有「近」PK的物理上靠近在一起的行。由於你的子表使用代理PK,它們的行將被隨機存儲。當需要對「master」表中的給定行進行計算時,DBMS必須全程跳轉以收集子表中的相關行。

而是代理鍵的,請嘗試使用更多的「自然」鍵,在前緣父母的PK,與此類似:

score_adjustments: 
    entry_id: INT(11), FOREIGN KEY (entries.id) 
    created: DATETIME 
    amount: INT(4) 
    PRIMARY KEY (entry_id, created) 

rating_adjustments: 
    entry_id: INT(11), FOREIGN KEY (entries.id) 
    rating_no: INT(11) 
    rating: DOUBLE 
    PRIMARY KEY (entry_id, rating_no) 

注:這是假定的created分辨率足夠精細和增加了rating_no以允許每entry_id多個評級。這僅僅是一個例子 - 你可以根據你的需要改變PKs。

這將「強制」屬於同一個entry_id的行在物理上靠得很近,因此只需對PK /集羣密鑰進行範圍掃描即可計算出SUM或AVG,而只需很少的I/O。

或者(例如,如果您使用的MyISAM不支持羣集),cover帶索引的查詢,以便在查詢時根本不觸摸子表。


最重要的是,你可以進行非規範化的設計,並緩存在父表目前的結果:作爲物理場

  • 商店SUM(score_adjustments.amount),並通過觸發器進行調整每次插入,更新或從score_adjustments刪除一行。
  • 將SUM(rating_adjustments.rating)存儲爲「S」 COUNT(rating_adjustments.rating)爲「C」。當一行被添加到rating_adjustments時,將其添加到S並增加C.在運行時計算S/C以獲得平均值。同樣處理更新和刪除。
+0

非常感謝,我現在將詳細閱讀此內容,並讓您知道我所做的更改和結果。 – Ryall

+0

在我的例子中'created'對於分數調整來說不夠好,所以我只是在PK的末尾添加一個'score_no'(或等價物)?另外,不直接存儲'AVG(rating_adjustments.rating)'而不是'SUM()'和'COUNT()'的原因是什麼? – Ryall

+0

@Ryall這樣,您可以輕鬆地根據當前子行更新它,而無需查詢子表中的其他行。 –

2

如果您擔心性能問題,可以將評分和評分列添加到相應的表中,並在插入或更新引用表時使用觸發器更新它們。這會在每次更新時緩存新結果,而且不必每次都重新計算它們,從而大大減少了獲取結果所需的連接數量......只是猜測而已,但在大多數情況下,查詢結果可能比更新更經常提取。

看看這個sql小提琴http://sqlfiddle.com/#!2/b7101/1看看如何使觸發器及其效果,我只在插入添加觸發器,您可以添加更新觸發器一樣容易,如果你刪除數據添加觸發器以及刪除。

未添加日期時間字段,如果between ... and ...參數經常發生變化,您可能必須每次都手動執行該操作,否則只需將between子句添加到score_update觸發器即可。

+0

謝謝你會看看:) – Ryall

+2

這是一個很好的例子,謝謝。如果沒有想法SQL小提琴存在,似乎是一個方便的工具! – Ryall

+0

我可以向你保證,它似乎不是,它是,尤其是當與他人交談時,否則我可以在本地數據庫上做到這一點。如果這對你有效,請檢查它是否正確,以便其他人知道。 – xception