2011-10-29 121 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为空的记录。