如何优化这个简单的JOIN + ORDER BY查询?

时间:2011-10-23 12:41:05

标签: mysql sql query-optimization

我有两个mysql表:

/* Table users */
CREATE TABLE IF NOT EXISTS `users` (
  `Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `DateRegistered` datetime NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/* Table statistics_user */
CREATE TABLE IF NOT EXISTS `statistics_user` (
  `UserId` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `Sent_Views` int(10) unsigned NOT NULL DEFAULT '0',
  `Sent_Winks` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`UserId`),
  CONSTRAINT `statistics_user_ibfk_1` FOREIGN KEY (`UserId`) REFERENCES `users` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

两个表都填充了10.000个随机行,以便使用以下过程进行测试:

DELIMITER //
CREATE DEFINER=`root`@`localhost` PROCEDURE `FillUsersStatistics`(IN `cnt` INT)
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE dt DATE;
DECLARE Winks INT DEFAULT 1;
DECLARE Views INT DEFAULT 1;

WHILE (i<=cnt) DO
        SET dt = str_to_date(concat(floor(1 + rand() * (9-1)),'-',floor(1 + rand() * (28 -1)),'-','2011'),'%m-%d-%Y');

        INSERT INTO users (Id, DateRegistered) VALUES(i, dt);

        SET Winks = floor(1 + rand() * (30-1));
        SET Views = floor(1 + rand() * (30-1));
        INSERT INTO statistics_user (UserId, Sent_Winks, Sent_Views) VALUES (i, Winks, Views);

     SET i=i+1;
END WHILE;

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

问题:

当我为此查询运行EXPLAIN时:

SELECT
t1.Id, (Sent_Views + Sent_Winks) / DATEDIFF(NOW(), t1.DateRegistered) as Score
FROM users t1
JOIN  statistics_user t2 ON t2.UserId = t1.Id
ORDER BY Score DESC

..我得到了这个解释:

Id  select_type table   type    possible_keys   key     key_len     ref             rows    extra
1   SIMPLE      t1      ALL     PRIMARY         (NULL)  (NULL)      (NULL)          10037   Using temporary; Using filesort
1   SIMPLE      t2      eq_ref  PRIMARY         PRIMARY 4           test2.t2.UserId 1   

当两个表的行数超过500K时,上述查询变得非常慢。我想这是因为'暂时使用;在查询说明中使用filesort'。

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

4 个答案:

答案 0 :(得分:1)

我很清楚ORDER BY是什么在扼杀你,因为它无法正确编入索引。这是一个可行的,如果不是特别漂亮的解决方案。

首先,假设您有一个名为Score的列用于存储用户的当前分数。每次用户Sent_ViewsSent_Winks发生更改时,请修改Score列以进行匹配。这可能是通过触发器完成的(我对触发器的体验有限),或者肯定是在更新Sent_ViewsSent_Winks字段的相同代码中完成的。这种变化不需要知道DATEDIFF部分,因为它可以除以Sent_Views + Sent_Winks的旧和并乘以新的。{/ p>

现在您只需要每天更改一次Score列(如果您对用户注册的确切小时数没有挑剔)。这可以通过cron作业运行的脚本来完成。

然后,只需索引Score列并选择SELECT!

注意:已修改以删除错误的首次尝试。

答案 1 :(得分:0)

您应该尝试内部联接,而不是笛卡尔积,接下来您可以做的是根据date_registered进行分区。

答案 2 :(得分:0)

我提供我的评论作为答案:

确定未来日期,远远不会干扰您的申请,例如5000年。在您的分数计算中将当前日期替换为此未来日期。分数计算现在绝对是所有意图和目的,并且可以在更新传情动词和视图时(通过存储的rocedure或atrigger(mysql是否具有触发器?))计算。

score表中添加statistics_user列,以存储计算得分并在其上定义索引。

您的SQL可以重写为:

SELECT
   UserId, score  
FROM
  statistics_user 
ORDER BY score DESC

如果你需要真正的分数,只需一个常数乘法就可以很容易地计算出来,如果它干扰了mysql索引选择,可以在之后进行。

答案 3 :(得分:0)

您是否应该在用户中为DateRegistered编制索引?