两个SQL查询 - 性能差异?

时间:2013-07-30 10:58:15

标签: mysql sql performance

我在PHP中使用MySQL和PDO,我有一个SQL查询,它按预期工作。但是,我关心性能,并想知道我是否可以改进我的查询。我也问,因为我想获得 SQL 的更多背景知识

假设我有两张表少数相等(以及一些附加信息,每张表中不同):

table `blog_comments`: id, userid (int) | timestamp (int) | content (varchar) | other
table `projects_comments`: id, userid (int) | timestamp (int) | content (varchar) | other

字段id主键userid + timestamp在两个表中都有索引,而时间戳只是具有长度的unixtime 10(整数)。

作为简单的垃圾邮件保护,我阻止用户提交新评论(无论是博客,项目还是其他任何内容),直到他上一次评论过去60秒。为实现此目的,我从所有评论表中获取该用户的最新时间戳

这是我的工作查询:

SELECT MAX(`last_timestamp`) AS `last_timestamp`
FROM
(
    SELECT `userid`, max(`timestamp`) AS `last_timestamp`
    FROM `blog_comments`
    GROUP BY `userid`
    UNION ALL
    SELECT `userid`, max(`timestamp`) as `last_timestamp`
    FROM `projects_comments`
    GROUP BY `userid`
) AS `subquery`
WHERE `userid` = 1
LIMIT 0, 1;

您可以注意到,我在子查询中使用 GROUP BY ,在主查询中我只是过滤 strong> 用户ID (在这种情况下:1)。优点:我只需要传递 用户ID一次作为参数。

现在,我对SQL的确切运作方式感兴趣。我认为它将是这样的:SQL首先执行子查询,按所有 现有行由userid组成,将整个集返回到主查询,然后应用where子句来查找所需的用户标识。这对我来说似乎是一个很大的性能泄漏。

所以我想稍微更改查询

SELECT max(`last_timestamp`) AS `last_timestamp`
FROM
(
    SELECT max(`timestamp`) AS `last_timestamp`
    FROM `blog_comments`
    WHERE `userid` = 1
    UNION ALL
    SELECT max(`timestamp`) as `last_timestamp`
    FROM `projects_comments`
    WHERE `userid` = 1
) AS `subquery`
LIMIT 0, 1

现在我必须传递两次userid,然后仍然会查找给定用户ID的整个行集。我不确定这是否真的提高性能。

我还没有任何大数据量来真正测试它,也许我稍后会做一些测试场景。我真的很想知道当这些表中有很多数据集时是否存在差异?

非常感谢任何想法,信息和提示,提前感谢。

修改

MySQL解释第一个查询:

id  select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
1   PRIMARY     <derived2>  ALL     NULL    NULL    NULL    NULL    4   Using where
2   DERIVED     blog_comments   range   NULL    userid  8   NULL    10  Using index for group-by
3   UNION   projects_comments   index   NULL    userid  12  NULL    6   Using index
NULL    UNION RESULT    <union2,3>  ALL     NULL    NULL    NULL    NULL    NULL     

MySQL解释第二个查询:

id  select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
1   PRIMARY     <derived2>  ALL     NULL    NULL    NULL    NULL    2    
2   DERIVED     NULL    NULL    NULL    NULL    NULL    NULL    NULL    Select tables optimized away
3   UNION   NULL    NULL    NULL    NULL    NULL    NULL    NULL    Select tables optimized away
NULL    UNION RESULT    <union2,3>  ALL     NULL    NULL    NULL    NULL    NULL     

2 个答案:

答案 0 :(得分:3)

作为替代方法......

SELECT 'It''s been more than 1 minute since your last post' As result
WHERE  NOT EXISTS (
         SELECT *
         FROM   blog_comments
         WHERE  userid = 1
         AND    timestamp > Date_Sub(Current_Timestamp, INTERVAL 1 MINUTE)
       )
AND    NOT EXISTS (
         SELECT *
         FROM   projects_comments
         WHERE  userid = 1
         AND    timestamp > Date_Sub(Current_Timestamp, INTERVAL 1 MINUTE)
       )

如果userid = 1在两个表中的最后一分钟内都没有时间戳记录,则会有结果。

你也可以交换逻辑......

SELECT 'You''re not allowed to post just yet...' As result
WHERE  EXISTS (
         SELECT *
         FROM   blog_comments
         WHERE  userid = 1
         AND    timestamp > Date_Sub(Current_Timestamp, INTERVAL 1 MINUTE)
       )
OR     EXISTS (
         SELECT *
         FROM   projects_comments
         WHERE  userid = 1
         AND    timestamp > Date_Sub(Current_Timestamp, INTERVAL 1 MINUTE)
       )

这第二个选项可能会更有效(EXISTS vs NOT EXISTS),但您需要进行测试和证明;)

答案 1 :(得分:2)

你的问题的答案是第二个应该在MySQL中表现比第一个好,这正是你给出的原因。 MySQL将对所有数据运行完整的group by然后选择一个组。

您可以通过在查询前放置explain来查看执行路径中的不同内容。这将使您对查询的实际内容有所了解。

如果您在user_id, timestamp上有索引,那么第二个查询将以非常快的速度运行,仅使用索引。即使没有索引,第二个查询也会对两个表进行全表扫描 - 就是这样。第一个将执行全表扫描和聚合的文件排序。第二个需要更长的时间。

如果您只想传递userid一次,您可以执行以下操作:

select coalesce(greatest(bc_last_timestamp, pc_last_timestamp),
                bc_last_timestamp, pc_last_timestamp
               )
from (select (SELECT max(`timestamp`) FROM `blog_comments` bc where bc.userid = const.userid
             ) bc_last_timestamp,
             (SELECT max(`timestamp`) FROM `projects_comments` pc where pc.userid = const.userid
             ) pc_last_timestamp
      from (select 1 as userid) const
     ) t;

查询看起来很神秘,但它应该与第二个类似地进行优化。