我有一个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秒内获取正确的数据,那么我需要使用最快的查询来获取数据。
那么,如何优化上述查询以使其运行得更快?
答案 0 :(得分:0)
此查询可能缓慢的原因:
这使得很难在不重组数据的情况下优化此查询 。一些尝试的建议:
- 您可以尝试使用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的记录。