MySQL:如何优化这个简单的GROUP BY + ORDER BY查询?

时间:2011-10-29 11:26:28

标签: mysql query-optimization

我有一个mysql表:

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个最高ID 按ID排序升序

我玩GROUP BY,ORDER BY和MAX(Id),但查询太慢了,所以我提出了这个问题:

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秒内获取正确的数据,那么我需要使用最快的查询来获取数据。

那么,如何优化上述查询以使其运行得更快?

1 个答案:

答案 0 :(得分:0)

此查询可能缓慢的原因:

  • 这是很多数据。很多可能会被退回。它返回每个SenderId / ReceiverId组合的最后一条记录。
  • 数据的划分(许多发件人/收件人组合,或相对较少的组合,但有多个'版本'。
  • 整个结果集必须按MySQL排序,因为您需要前100条记录,按ID排序。

这使得很难在不重组数据的情况下优化此查询 。一些尝试的建议:
- 您可以尝试使用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,SenderId和Id上使用适当的索引。尝试在三列上创建组合索引。尝试两个版本,一个是Id是第一列,另一个是Id是最后一个。

稍加数据库修改 - 您可以将SenderId / ReceiverId的组合保存在一个单独的表中,其中LastId指向您想要的记录。 - 您可以为每条记录保存一个“PreviousId”,使每个Sender / Receiver的最后一条记录保持为NULL。您只需查询previousId为null的记录。