我在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
答案 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;
查询看起来很神秘,但它应该与第二个类似地进行优化。