我有两个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'。
如何优化上述查询以使其运行得更快?
答案 0 :(得分:1)
我很清楚ORDER BY是什么在扼杀你,因为它无法正确编入索引。这是一个可行的,如果不是特别漂亮的解决方案。
首先,假设您有一个名为Score
的列用于存储用户的当前分数。每次用户Sent_Views
或Sent_Winks
发生更改时,请修改Score
列以进行匹配。这可能是通过触发器完成的(我对触发器的体验有限),或者肯定是在更新Sent_Views
和Sent_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编制索引?