2012-08-30 16 views
4

我有一張桌子,每天增長數千萬行。表中的行包含關於頁面查看流量的小時信息。從大型表中檢索聚合數據的更快捷方式?

表中的索引在url和datetime上。

我想按天彙總信息,而不是每小時。我應該怎麼做?這是一個查詢,它舉例說明了我正在嘗試執行的操作:

SELECT url, sum(pageviews), sum(int_views), sum(ext_views) 
FROM news 
WHERE datetime >= "2012-08-29 00:00:00" AND datetime <= "2012-08-29 23:00:00" 
GROUP BY url 
ORDER BY pageviews DESC 
LIMIT 10; 

上述查詢永遠不會結束。表中有數百萬行。有沒有更有效的方式可以獲得這些彙總數據?

+0

什麼是表上的索引和什麼解釋查詢的計劃? – Ben

+0

@Ben,索引在url和datetime上。什麼是解釋計劃? – egidra

+0

@Ben,我如何做一個明確的字符到日期轉換?日期時間列的數據類型是日期時間。 MySQL爲我們做了從字符到日期時間類型的轉換嗎?我認爲它的確如此。應該從0:00到23:59選擇24小時的數據。 – egidra

回答

5

的幾點:

  1. 另外,作爲你在你過濾只有謂語應該 可能有一個指數與datetime作爲第一列。
  2. 您正在訂購pageviews。我會假設你想要訂購sum(pageviews)
  3. 您正在查詢23小時的數據而不是24個。您可能希望從第二天的午夜開始使用小於<的明確數字,以避免遺漏任何內容。
SELECT url, sum(pageviews), sum(int_views), sum(ext_views) 
    FROM news 
WHERE datetime >= '2012-08-29 00:00:00' 
    AND datetime < '2012-08-30 00:00:00' 
GROUP BY url 
ORDER BY sum(pageviews) DESC 
LIMIT 10; 

你可以索引這對datetime, url, pageviews, int_views, ext_views,但我認爲這將是矯枉過正;所以,如果指數不是太大datetime, url似乎是一個好方法。要確定的唯一方法是測試它並決定查詢中的任何性能改進是否值得在索引維護中花費額外的時間。

正如Gordon剛剛在評論中提到的那樣,您可能需要查看partitioning。這使您可以查詢較大的「表」中的較小的「表」。如果您的所有查詢均基於日期級別,則聽起來您可能需要每天創建一個新的查詢。

+0

當我嘗試在datetime,url上創建索引時,出現以下錯誤:錯誤1071(42000):指定的鍵過長;最大密鑰長度是1000字節 – egidra

+0

@Ben:您是否刪除了您的評論re str_to_date()?我看不到它...只是想知道,因爲我不明白爲什麼你提出這個問題:當然這些都是有效的[日期文字](http://dev.mysql.com/doc/en/date-and-time -literals.html)? – eggyal

+0

@eggyal,是的,你是對的...我總是喜歡明確地轉換和矇蔽自己。 – Ben

6

數千行每天是相當多的。

假設:

  • 每天只有10萬個新的記錄;
  • 您的表格僅包含您在問題中提及的列;
  • urlTEXT類型的具有〜77 characters平均(Punycode碼)長度;
  • pageviewsINT類型;
  • int_viewsINT類型;
  • ext_views類型INT;和
  • datetime是類型DATETIME

的然後每一天的數據會佔據周圍9.9 字節,這是幾乎1GiB /天。實際上它可能更多,因爲上述假設非常保守。

MySQL的maximum table size是確定的,除其他事項外,通過在其上的數據文件所在的底層文件系統。如果您在Windows或Linux上不使用分區的情況下使用MyISAM引擎(如您的評論所建議的那樣),那麼幾個GiB的限制並不少見;這意味着桌子在一個工作周內就能達到它的容量!

正如@Gordon Linoff提到的,你應該partition你的表;但是,每個表都有1024個分區的limit。有了1個分區/天(在您的情況下這將是非常明智的),您將被限制在分區開始重用之前將3年以下的數據存儲在單個表中。

因此,我建議你保持每一年的數據在它自己的表,每一天劃分。此外,作爲@Ben explained,在(datetime, url)上的複合索引將有所幫助(我實際上建議創建一個的date列並對其進行索引,因爲它將在執行查詢時啓用MySQL到prune分區);並且,如果行級鎖和事務的完整性是對你並不重要(對於這種表,它們可能不是),使用MyISAM數據可能不會愚蠢:

CREATE TABLE news_2012 (
    INDEX (date, url(100)) 
) 
Engine = MyISAM 
PARTITION BY HASH(TO_DAYS(date)) PARTITIONS 366 
SELECT *, DATE(datetime) AS date FROM news WHERE YEAR(datetime) = 2012; 

CREATE TRIGGER news_2012_insert BEFORE INSERT ON news_2012 FOR EACH ROW 
    SET NEW.date = DATE(NEW.datetime); 

CREATE TRIGGER news_2012_update BEFORE UPDATE ON news_2012 FOR EACH ROW 
    SET NEW.date = DATE(NEW.datetime); 

如果您選擇使用MyISAM,你不僅可以封存已完成年(使用myisampack),但也可以用MERGE一個包括所有潛在的一年表(一個替代方案,也將在InnoDB中工作的UNION是創建一個VIEW替換原來的表,但它將僅對SELECT陳述有用,因爲UNION視圖既不可更新也不可插入):

DROP TABLE news; 
CREATE TABLE news (
    date DATE, 
    INDEX (date, url(100)) 
) 
Engine = MERGE 
INSERT_METHOD = FIRST 
UNION = (news_2012, news_2011, ...) 
SELECT * FROM news_2012 WHERE FALSE; 

然後,您可以在此合併表運行上面的查詢(以及任何其他):

SELECT url, SUM(pageviews), SUM(int_views), SUM(ext_views) 
FROM  news 
WHERE date = '2012-08-29' 
GROUP BY url 
ORDER BY SUM(pageviews) DESC 
LIMIT 10; 
+0

我將從最明顯的優化開始,在datetime和url上創建一個索引。當我嘗試創建索引時,出現以下錯誤:錯誤1071(42000):指定的鍵過長;最大密鑰長度爲1000字節 – egidra

+0

@egidra:建議您指定'url'的前綴長度 - 例如。 'INDEX(datetime,url(100))',我已經在上面更新了我的答案 - 您應該選擇的實際長度取決於您的數據:需要按照順序讀取'url'列的多少個字符將表過濾到相對較少的記錄? – eggyal

+0

沒有那麼多,大概有100個字符可以區分彼此的網址。 – egidra

相關問題