MySQL从另一个查询加入最新记录的速度很慢

时间:2014-05-16 19:13:21

标签: mysql optimization greatest-n-per-group

我有以下两个表格:

CREATE TABLE `modlogs` (
  `mod` int(11) NOT NULL,
  `ip` varchar(39) CHARACTER SET ascii NOT NULL,
  `board` varchar(58) CHARACTER SET utf8 DEFAULT NULL,
  `time` int(11) NOT NULL,
  `text` text NOT NULL,
  KEY `time` (`time`),
  KEY `mod` (`mod`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4

CREATE TABLE `mods` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(30) NOT NULL,
  `password` char(64) CHARACTER SET ascii NOT NULL COMMENT 'SHA256',
  `salt` char(32) CHARACTER SET ascii NOT NULL,
  `type` smallint(2) NOT NULL,
  `boards` text CHARACTER SET utf8 NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id` (`id`,`username`)
) ENGINE=MyISAM AUTO_INCREMENT=933 DEFAULT CHARSET=utf8mb4

我想使用mod的名称加入最新的日志条目,但是我的查询非常慢(需要5.23秒):

SELECT *
FROM mods LEFT JOIN
     modlogs
     ON modlogs.mod = mods.id
     AND modlogs.time = (SELECT MAX(time)
                         FROM mods
                         WHERE mods.id = modlogs.mod
                        );

SO上的所有其他答案似乎也使用了从属子查询。有没有办法可以更快地返回结果?

3 个答案:

答案 0 :(得分:1)

这是您的查询:

SELECT *
FROM mods LEFT JOIN
     modlogs
     ON modlogs.mod = (SELECT MAX(time)
                       FROM modlogs
                       WHERE mods.id = modlogs.mod
                      );

此查询没有意义。您正在将名为mod的内容与最大时间进行比较。听起来它对我不起作用,但那时有一些非常“聪明”的数据模型。我怀疑你真的想要:

SELECT *
FROM mods LEFT JOIN
     modlogs
     ON mods.id = modlods.mod and
        modlogs.time = (SELECT MAX(time)
                        FROM mods
                        WHERE mods.id = modlogs.mod
                       );

我不会以这种方式编写查询,因为join子句中的on条件似乎让我感到困惑。但是,你做到了。使用索引可以获得更好的性能。我建议:

create index modlogs_mod_time on modlogs(mod, time);

我会将查询写成:

SELECT *
FROM mods LEFT JOIN
     modlogs
     ON mods.id = modlods.mod
WHERE NOT EXISTS (SELECT 1
                  FROM modlogs ml2
                  WHERE modlogs.mod = ml2.mod and
                        ml2.time > modlogs.time
                 );

答案 1 :(得分:1)

这是另一种解决方案,将子查询放入派生表可以避免依赖子查询的问题。它只运行子查询一次。

SELECT *
FROM mods AS m
LEFT JOIN (
    SELECT ml1.* 
    FROM modlogs AS ml1 
    JOIN (
        SELECT `mod`, MAX(time) AS time
        FROM modlogs 
        GROUP BY `mod`   
    ) AS ml2 USING (`mod`, time)
) AS ml ON m.id = ml.`mod`;

答案 2 :(得分:0)

我认为你也可以用反连接解决这个问题,虽然我对这个问题的表现持怀疑态度:

SELECT mods.*, modlogs.*
FROM mods
LEFT JOIN modlogs
  ON modlogs.mod = mods.id
LEFT JOIN mods m2
  ON m2.id = modlogs.mod
  AND m2.time < modlogs.time
WHERE m2.id IS NULL

确保您拥有modlogs(mod)的索引,并考虑索引mods(id, time)以获得更好的效果。