1

我在 PHP 中使用带有 PDO 的 MySQL,并且我有一个 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都有一个索引,时间戳就是长度为 10(整数)的 unixtime。

作为一种简单的垃圾邮件防护,我会阻止用户提交新评论(无论是博客、项目还是其他任何内容),直到他上次发表评论后 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,在主查询中我只是过滤用户 ID(在本例中为:1)。优点:我只需要将用户标识作为参数传递一次。

现在,我对 SQL 究竟是如何工作的很感兴趣。我认为它会是这样的:SQL 首先执行子查询,按用户 ID对所有现有行进行分组并将整个集合返回给主查询,然后应用 where 子句来查找所需的用户 ID。这对我来说似乎是一个很大的性能泄漏。

所以我想稍微改变一下查询

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

现在我必须传递用户 ID 两次,仍然会为给定的用户 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     
4

2 回答 2

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)
       )

第二个选项可能会更有效(EXISTSvs NOT EXISTS),但那是你测试和证明;)

于 2013-07-30T11:51:26.697 回答
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;

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

于 2013-07-30T11:08:59.797 回答