2011-06-02 67 views
4


我已經做了一些搜索,但沒有提出任何內容,也許有人可以指出我在正確的方向。
我有一個網站有很多的內容在一個MySQL數據庫和一個PHP腳本,通過點擊加載最流行的內容。它通過記錄表中的每個內容以及訪問時間來完成此操作。然後,運行選擇查詢以在過去24小時,7天或最多30天內找到最受歡迎的內容。 cronjob會刪除日誌表中超過30天的任何內容。通過點擊優化內容流行度查詢

我現在面臨的問題是,隨着網站的增長,日誌表有1m +的記錄,它確實減慢了我的選擇查詢(10-20s)。起初我雖然問題是我在查詢中獲取內容標題,網址等的連接,但現在我不知道在測試中刪除連接不會像我一樣加快查詢速度,儘管它會。

所以我的問題是做這種人氣存儲/選擇的最佳做法是什麼?他們是否有很好的開源腳本?或者你會建議什麼?

表方案

「人氣」 打日誌表
NID | insert_time | TID
NID:所述內容的節點ID
insert_time:時間戳(2011-06-02 4時08分45秒)
TID:術語/類別ID

「節點」 內容表
NID |標題|狀態| (有更多,但這些都是重要的)
NID:節點ID
標題:內容標題
狀態:在內容發佈(0 = FALSE,1 = TRUE)

SQL

SELECT node.nid, node.title, COUNT(popularity.nid) AS count 
FROM `node` INNER JOIN `popularity` USING (nid) 
WHERE node.status = 1 
    AND popularity.insert_time >= DATE_SUB(CURDATE(),INTERVAL 7 DAY) 
GROUP BY popularity.nid 
ORDER BY count DESC 
LIMIT 10; 
+3

如果您發佈表結構以查看需要添加索引的位置,甚至解釋緩慢查詢,這將非常有用。 – 2011-06-02 08:35:13

+1

或查詢本身 – Belinda 2011-06-02 08:36:33

+0

我已經添加了表格方案和慢SQL查詢。 – Owen 2011-06-02 09:15:49

回答

2

我們剛剛遇到類似的情況,這就是我們如何解決它的。我們決定我們並不在乎發生什麼事情的確切時間,只是發生的那一天。然後,我們這樣做:

  1. 的每個記錄都有其遞增每次有事
  2. 一個日誌表記錄每個記錄這​​些「總點擊數」,每天(在cron作業)一個「總點擊」記錄
  3. 通過選擇此日誌表中兩個給定日期之間的差異,我們可以非常快速地推斷兩個日期之間的「點擊次數」。

這樣做的好處是你的日誌表的大小隻有NumRecords * NumDays的大小,在我們的例子中它非常小。此外,此日誌表上的任何查詢都很快。

缺點是你失去了按時間推斷命中的能力,但如果你不需要這個,那麼它可能是值得考慮的。

+0

好主意,你的權利我不需要分鐘統計。唯一的問題是,「過去24小時內最受歡迎」,但我可以再次顯示昨天的統計數據。 – Owen 2011-06-02 09:22:37

+1

您甚至可以通過從上次記錄的「總點擊數」(可能在午夜拍攝)中減去當前的「總點擊數」來獲得「今天最流行」的效果。這會讓你獲得最新的人氣,雖然不一定是'24h' – cusimar9 2011-06-02 09:36:29

0

您可以添加索引並嘗試調整SQL,但真正的解決方案是緩存結果。

你真的應該只需要每天一次caclulate交通的最後7/30天

,你可以做過去24小時內每小時的?

即使您每5分鐘執行一次,與爲每個用戶的每次點擊運行(昂貴)查詢相比仍然是巨大的節省。

+0

我雖然想要緩存結果,但我想我將來必須這樣做,但現在我認爲查詢可以更好地優化。 – Owen 2011-06-02 09:17:36

0

RRDtool的

許多工具/系統不建立自己的日誌記錄和日誌聚合,但使用RRDtool(輪詢數據庫工具),以有效地處理時間序列數據。 RRDtools還附帶了強大的圖形子系統,並且(根據Wikipedia)還有PHP和其他語言的綁定。

從你的問題中,我假設你不需要任何特別的和花哨的分析,RRDtool會高效地做你所需要的,而不必實施和調整你自己的系統。

0

你可以在背景中做一些'聚合',例如通過一個con作業。一些可能有所幫助的建議(沒有特定順序):

1.創建一個帶小時結果的表格。這意味着您仍然可以創建所需的統計數據,但將數據量減少爲(24 * 7 * 4 =每月每頁大約672條記錄)。

你的表可以某處的臺詞:

 
hourly_results (
nid integer, 
start_time datetime, 
amount integer 
) 

你解析他們進入你的彙總表後,您可以或多或少地將其刪除。

2.使用結果緩存(內存緩存,APC) 可以(每小時不應改變每一分鐘,而是?)輕鬆存儲的結果,無論是在memcache database(這又可以從更新cronjob),請使用apc user cache(不能從cronjob更新),或者在內存不足的情況下通過序列化對象/結果使用file caching

3.優化您的數據庫 10秒鐘是很長的時間。試着找出你的數據庫正在發生什麼。內存不足嗎?你需要更多的索引嗎?

+0

「試着找出你的數據庫正在發生什麼。」 - 錯誤...他發佈的查詢將整個表連接/分組在一起,其中一個具有數百萬行。它*唯一可能*是緩慢的。 :-) – 2011-06-02 10:18:09

+0

是的,你可以說這很慢,就是這樣。這可能與優化完全相反。有些地方讓你開始:http://www.xaprb.com/blog/2006/04/30/how-to-optimize-subqueries-and-joins-in-mysql/。 http://forge.mysql.com,http://20bits.com/articles/10-tips-for-optimizing-mysql-queries-that-dont-suck//wiki/Top10SQLPerformanceTips http://www.mysqlperformanceblog。 com/2007/04/06/using-delayed-join-to-optimize-count-and-limit-queries/ – Arend 2011-06-02 10:36:49

+1

您可能想了解更多關於數據庫如何決定是否使用索引的信息。長話短說,查詢規劃者會選擇小集合的索引,大集合的位圖索引,查詢大多數集合時不需要索引。在他的情況下,他一起加入兩張桌子;所以沒有索引。 – 2011-06-02 11:45:16

1

你實際上有兩個問題需要進一步解決。

其中一個你尚未遇到但你可能比你想要更早的地方是要在你的統計表中插入吞吐量。

另一個,你在你的問題中概述,實際上是使用統計。


讓我們從輸入吞吐量開始。首先,如果你這樣做,不要跟蹤可能使用緩存的頁面上的統計信息。使用一個將腳本廣告爲空的JavaScript腳本或者一個像素的圖像,並在您正在跟蹤的頁面中包含後者。這樣做可以輕鬆緩存您網站的剩餘內容。

在電信業務中,不是通過電話撥打與計費相關的實際插入內容,而是將其放入內存並定期與磁盤同步。這樣做可以管理巨大的吞吐量,同時保持硬盤驅動器的快樂。

要在您的結尾進行類似操作,您需要一個原子操作和一些內存中的存儲。以下是一些基於memcache的僞代碼,用於執行第一部分...

對於每個頁面,您需要一個Memcache變量。在Memcache中,increment()是原子的,但add(),set()等不是。所以,你需要警惕不要錯過計數命中時併發進程,同時加在同一個頁面:

$ns = $memcache->get('stats-namespace'); 
while (!$memcache->increment("stats-$ns-$page_id")) { 
    $memcache->add("stats-$ns-$page_id", 0, 1800); // garbage collect in 30 minutes 
    $db->upsert('needs_stats_refresh', array($ns, $page_id)); // engine = memory 
} 

定期,例如每5分鐘(相應地配置超時),你要同步所有這一切都交給了數據庫,沒有任何影響彼此或現有命中計數的併發進程的可能性。對於這一點,你做任何事情(這使您對現有數據的所有意圖和目的鎖定)之前遞增的命名空間,睡了一點,所以,如果需要引用現有的命名空間現有的流程完成了:

$ns = $memcache->get('stats-namespace'); 
$memcache->increment('stats-namespace'); 
sleep(60); // allow concurrent page loads to finish 

完成後,您可以安全地遍歷頁面標識,相應地更新統計信息,並清理needs_stats_refresh表。後者只需要兩個字段:page_id int pkey,ns_id int)。還有一點比簡單的選擇,插入,更新和刪除語句從你的腳本運行,但是,繼續...

正如另一個回覆者建議,爲您的目的維護中間狀態是相當合適的:存儲批次的命中而不是個人命中。在最多情況下,我假設您需要每小時的統計數據或每小時一次的統計數據,因此處理每15分鐘批量加載的小計就可以了。

更重要的是,爲了您的利益,由於您使用這些總計來訂購帖子,因此您希望存儲總計總計並對後者進行索引。 (我們會到更遠的地方。)

保持總計的一種方法是添加一個觸發器,在插入或更新到統計表時,將根據需要調整統計總數。

這樣做時,要特別警惕死鎖。儘管沒有兩個$ns運行會混合它們各自的統計數據,但是兩個或更多進程同時觸發上述「增量$ ns」步驟仍然存在(儘管很小)的可能性,並且隨後發出尋求同時更新計數的語句。獲得advisory lock是避免與此相關的問題的最簡單,最安全,最快速的方法。

假設您使用諮詢鎖,在update語句中使用total = total + subtotal是完全可以的。

雖然關於鎖的話題,請注意更新總數將需要對每個受影響的行進行排它鎖定。既然你是按他們的順序排列的,你不希望他們一次處理完所有東西,因爲這可能意味着需要長時間保持排他鎖。這裏最簡單的方法是將插入的數據以較小的批次處理(例如1000),然後每次都進行提交。

對於中介統計信息(每月,每週),向您的統計信息表中添加一些布爾字段(MySQL中的bit或tinyint)。讓每個人都存儲它們是否包括每月,每週,每日統計數據等。在他們身上放置一個觸發器,以增加或減少stat_totals表中的適用總數。

作爲結束語,請提供一些想要存儲實際計數的位置。它需要是一個索引字段,而後者將被大量更新。通常,您需要將它存儲在自己的表格中,而不是存儲在網頁表格中,以避免使用(更大)死行來混亂網頁表格。


假設你做你的最終查詢上述所有變爲:

select p.* 
from pages p join stat_totals s using (page_id) 
order by s.weekly_total desc limit 10 

它應該是足夠快與weekly_total索引。最後,我們不要忘記最明顯的一點:如果你反覆運行這些相同的總/每月/每週/每次查詢,它們的結果也應該放在memcache中。