2011-10-29 178 views
1

我有一個MySQL表:MySQL:如何優化這個簡單的GROUP BY + ORDER BY查詢?

CREATE TABLE IF NOT EXISTS `test` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
`SenderId` int(10) unsigned NOT NULL, 
`ReceiverId` int(10) unsigned NOT NULL, 
`DateSent` datetime NOT NULL, 
`Notified` tinyint(1) unsigned NOT NULL DEFAULT '0', 
PRIMARY KEY (`Id`), 
KEY `ReceiverId_SenderId` (`ReceiverId`,`SenderId`), 
KEY `SenderId` (`SenderId`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 

該表通過以下步驟填充了10.000隨機行來進行測試:

DELIMITER // 
CREATE DEFINER=`root`@`localhost` PROCEDURE `FillTest`(IN `cnt` INT) 
BEGIN 
DECLARE i INT DEFAULT 1; 

DECLARE intSenderId INT; 
DECLARE intReceiverId INT; 
DECLARE dtDateSent DATE; 

DECLARE blnNotified INT; 

WHILE (i<=cnt) DO 
SET intSenderId = FLOOR(1 + (RAND() * 50)); 
SET intReceiverId = FLOOR(51 + (RAND() * 50)); 
SET dtDateSent = str_to_date(concat(floor(1 + rand() * (12-1)),'-',floor(1 + rand() * (28 -1)),'-','2008'),'%m-%d-%Y'); 

SET blnNotified = FLOOR(1 + (RAND() * 2))-1; 

INSERT INTO test (SenderId, ReceiverId, DateSent, Notified) 
VALUES(intSenderId,intReceiverId,dtDateSent, blnNotified); 

SET i=i+1; 
END WHILE; 

END// 
DELIMITER ; 
CALL `FillTest`(10000); 

問題:

我需要編寫一個查詢,這將組由「SenderId,ReceiverId」並返回第100最高的I DS每個組,被Id以升序下令

我打了GROUP BY,ORDER BY和MAX(同上),但查詢速度太慢了,所以我想出了這個查詢:

SELECT SQL_NO_CACHE t1.* 
FROM test t1 
LEFT JOIN test t2 ON (t1.ReceiverId = t2.ReceiverId AND t1.SenderId = t2.SenderId AND  t1.Id < t2.Id) 
WHERE t2.Id IS NULL 
ORDER BY t1.Id ASC 
LIMIT 100; 

上述查詢返回正確的數據,但它當test表的行數超過150.000時,它變得太慢。在150.000行上述查詢需要7秒鐘才能完成。我希望test表中有500.000之間 - 1M行,查詢需要在不到3秒返回正確的數據。如果無法在3秒內獲取正確的數據,則需要使用盡可能快的查詢來獲取數據。

那麼,怎樣才能在上面的查詢進行優化,使其運行速度更快?

+0

您有關MAX(ID)太慢了分組,但現在由ID限100訂貨評論...總是返回相同的100條第一記錄(主要是......直到失蹤發送器/接收器組合添加到組...您是否總是希望預先發送實例,或最近(最高100個)缺少發送者/接收者對的實例。 – DRapp

+0

一方面,你說你想爲每個發件人/收件人組設置100個最高的ID號碼。另一方面,你說你提出的查詢返回正確的數據。但它只返回100行。這是什麼? –

回答

0

原因,該查詢可能會很慢:

  • 這是一個很大的數據。很多可能會被退回。它返回每個SenderId/ReceiverId組合的最後一條記錄。
  • 數據的分割(很多發送者/接收者組合,或者相對較少的,但是有多個'版本')
  • 整個結果集必須由MySQL排序,因爲你需要排序前100條記錄通過編號

這使它很難優化這個查詢沒有重組數據一些建議嘗試:
- 你可以嘗試使用NOT EXISTS,但我懷疑它是否會幫助

SELECT SQL_NO_CACHE t1.* 
FROM test t1 
WHERE NOT EXISTS 
    (SELECT 'x' 
    FROM test t2 
    WHERE t1.ReceiverId = t2.ReceiverId AND t1.SenderId = t2.SenderId AND t1.Id < t2.Id) 
ORDER BY t1.Id ASC 
LIMIT 100; 


- 你可以嘗試使用上ReceiverId,Sender ID和標識正確的索引。嘗試在三列上創建組合索引。嘗試兩個版本,其中一個以Id爲第一列,另一個以Id爲最後一個。

有了輕微數據庫修改: - 你可以在一個單獨的表保存SenderId/ReceiverId的組合與LastId指向你想要的記錄。 - 您可以爲每個記錄保存一個'PreviousId',併爲每個發件人/接收者的最後記錄保留NULL。您只需要查詢previousId爲空的記錄。