[警告:提前发帖!]
我现在已经敲了很长时间,但却无法找到共同点。我找到了一个解决方法,最后看到了,但我内心的禅还不满意。
我有一个包含论坛消息的主表(来自Phorum),简化后看起来像这样(暂时忽略anon_user_id
,我会稍后再说):
CREATE TABLE `test_msg` (
`message_id` int(10) unsigned NOT NULL auto_increment,
`status` tinyint(4) NOT NULL default '2',
`user_id` int(10) unsigned NOT NULL default '0',
`datestamp` int(10) unsigned NOT NULL default '0',
`anon_user_id` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`message_id`)
);
软件可以对消息进行匿名处理,在这种情况下,user_id
设置为0
。该软件还允许发布我们认可的完整匿名消息。在我们的例子中,我们仍然需要知道哪个用户发布了一条消息,所以通过Phorum提供的钩子系统,我们有第二个表格,我们会相应更新:
CREATE TABLE `test_anon` (
`message_id` bigint(20) unsigned NOT NULL,
`user_id` bigint(20) unsigned NOT NULL,
KEY `fk_user_id` (`user_id`),
KEY `fk_message_id` (`message_id`)
);
对于个人资料中的视图,我需要获取用户的消息列表,无论他们是否已经被他/她
用户本身始终有权查看他匿名撰写或稍后匿名撰写的邮件。
如果匿名化user_id
设置为0
,我们不能简单地使用WHERE;我们需要加入我们自己的第二张桌子。将上面的内容表达为SQL就像这样(status = 2
是必需的,其他状态意味着帖子被隐藏,等待批准等):
SELECT * FROM test_msg AS m
LEFT JOIN test_anon ON test_anon.message_id = m.message_id
WHERE (test_anon.user_id = 20 OR m.user_id = 20)
AND m.status = 2
ORDER BY m.datestamp DESC
LIMIT 0,10
每当查询缓存为空时,此查询本身需要几秒钟,当前为4秒。当多个用户发出查询并且查询缓存为空(这恰好发生;人们发布消息和缓存的查询无效)时,情况会变得更糟;我们在内部测试阶段遇到过,报告显示系统有时会变慢。由于并发性,我们已经看到查询需要30到60秒。我不想开始想象当我们扩展用户群时会发生什么......
现在不像我没有对瓶颈进行任何分析。
我尝试重写WHERE子句,添加indice并删除它们就像地狱一样。
当我发现当不使用任何索引时,查询会在某些条件下快速执行照明。不使用索引,查询如下所示:
SELECT * FROM test_msg AS m USE INDEX()
LEFT JOIN test_anon ON test_anon.message_id = m.message_id
WHERE (test_anon.user_id = 20 OR m.user_id = 20)
AND m.status = 2
ORDER BY m.datestamp DESC
LIMIT 0,10
现在出现特定条件:LIMIT将结果限制为10行。假设我的完整结果为n = 26
。使用LIMIT 0,10
到LIMIT 16,0
需要零秒(某些值<0.01s):结果总是为10行。
从LIMIT 17,10
开始,结果将只有9行。从此时开始,查询将再次开始四秒。适用于结果集小于LIMIT
限制的最大行数的所有结果。刺激性!
回到第一个CREATE TABLE语句,我也进行了没有LEFT JOIN的测试;我们假设匿名消息为user_id=0
和anon_user_id=<the previous user_id>
,换句话说,完全绕过第二个表:
SELECT * FROM test_msg
WHERE status = 2 AND (user_id = 20 OR anon_user_id = 20)
ORDER BY m.datestamp DESC
LIMIT 20,10
结果:无关紧要。性能仍然在4或5秒内;强制不使用USE INDEX()
的索引不会加速此查询。
这是我现在真的很困惑。索引将始终仅用于status
列,OR
阻止使用其他索引,这也是MySQL文档在这方面告诉我的内容。
我尝试过的另一种解决方案:不要使用test_anon
表仅涉及匿名消息,而只涉及所有消息。这允许我写这样的查询:
SELECT * FROM test_msg AS m, test_anon AS t
WHERE m.message_id = t.message_id
AND t.user_id = 20
AND m.status = 2
ORDER BY m.datestamp DESC
LIMIT 20,10
此查询总是给我即时结果(== <0.01秒),无论LIMIT等等。
是的,我找到了解决方案。我还没有将整个应用程序改写为模型。
但是我想更好地理解我观察到的行为背后的理性(特别是强迫没有索引加速查询)。在纸面上,原始方法没有任何问题。
有些数字(无论如何都不是那么大):
test_anon
中的匿名邮件数量&lt;所有消息的3%所有表格都是MyISAM;我尝试使用InnnoDB,但性能要差得多。
答案 0 :(得分:1)
问题是你正在为整个表做一个连接。您需要告诉优化器您只需要加入两个用户ID:零和您想要的用户ID。像这样:
SELECT * FROM test_msg AS m
LEFT JOIN test_anon ON test_anon.message_id = m.message_id
WHERE (m.user_id = 20 OR m.user_id = 0)
AND (test_anon.user_id = 20 OR test_anon.user_id IS NULL)
AND m.status = 2
ORDER BY m.datestamp DESC
LIMIT 0,10
这会更好吗?
答案 1 :(得分:1)
您实际上在这里有两个不同的查询,可以作为单独的查询进行更好的处理。
要改进LIMIT
,您需要使用LIMIT on LIMIT
技术:
SELECT *
FROM (
SELECT *
FROM test_msg AS m
WHERE m.user_id = 20
AND m.status = 2
ORDER BY
m.datestamp DESC
LIMIT 20
) q1
UNION ALL
SELECT *
(
SELECT m.*
FROM test_msg m
JOIN test_anon a
ON a.message_id = m.message_id
WHERE a.user_id = 20
AND m.user_id = 0
AND m.status = 2
ORDER BY
m.datestamp DESC
LIMIT 20
) q2
ORDER BY
datestamp DESC
LIMIT 20
有关此解决方案的更多详细信息,请参阅我的博客中的此条目:
您需要为此创建两个复合索引才能快速工作:
test_msg (status, user_id, datestamp)
test_msg (status, user_id, message_id, datestamp)
然后,您需要在第二个查询中选择索引的用途:排序或过滤。
在您的查询中,索引不能用于两者,因为您要对message_id
上的范围进行过滤。
有关更多解释,请参阅此文章:
用几句话说:
如果有可能重新设计表格,只需将另一列is_anonymous
添加到表格test_msg
。
它将解决许多问题。