我有一个页面需要37秒才能加载。在加载时,它会通过屋顶固定MySQL的CPU使用率。我没有为这个页面编写代码而且它很复杂,所以瓶颈的原因对我来说并不是很明显。
我对它进行了分析(使用kcachegrind)并发现页面上的大部分时间用于执行MySQL查询(90%的时间用于25个不同的mysql_query调用)。
查询采用以下形式,其中tag_id在25个不同的调用中都发生了变化:
SELECT * FROM tbl_news WHERE news_id IN (select news_id from tbl_tag_relations WHERE tag_id = 20)
每个查询大约需要0.8秒才能完成,并且会有一些较长的延迟时间,因此需要37秒才能完全加载页面。
我的问题是,是否使用导致问题的嵌套选择格式化查询的方式?或者它可能是其他一百万个中的任何一个?关于如何应对这种缓慢的任何建议都值得赞赏。
在查询上运行EXPLAIN给了我这个(但是我不清楚这些结果的影响......主键上的NULL看起来很糟糕,是吗?返回的结果数量对我来说似乎很高以及最后只返回少量结果):
1 PRIMARY tbl_news ALL NULL NULL NULL NULL 1318 Using where 2 DEPENDENT SUBQUERY tbl_tag_relations ref FK_tbl_tag_tags_1 FK_tbl_tag_tags_1 4 const 179 Using where
答案 0 :(得分:5)
我在Database Development Mistakes Made by AppDevelopers中提到了这一点。基本上,支持加入聚合。 IN不是这样的聚合,但同样的原则适用。良好的优化将使这两个查询在性能上相当:
SELECT * FROM tbl_news WHERE news_id
IN (select news_id from
tbl_tag_relations WHERE tag_id = 20)
和
SELECT tn.*
FROM tbl_news tn
JOIN tbl_tag_relations ttr ON ttr.news_id = tn.news_id
WHERE ttr.tag_id = 20
因为我相信Oracle和SQL Server都可以,但MySQL不这样做。第二个版本基本上是即时的。有数十万行我在我的机器上进行了测试,并通过添加适当的索引获得了第一个版本的亚秒级性能。带索引的连接版本基本上是即时的,但即使没有索引也能正常运行。
顺便说一句,我使用的上述语法是您更喜欢使用连接的语法。它比将它们放在WHERE
子句中更清楚(正如其他人所建议的那样),上面的内容可以用ANSI SQL方式做某些事情,而左外连接是WHERE条件不能的。
所以我会在下面添加索引:
,查询几乎会立即执行。
最后,请勿使用*来选择所需的所有列。明确命名它们。稍后添加列时,您会遇到麻烦。
答案 1 :(得分:3)
SQL Query本身绝对是你的瓶颈。查询中有一个子查询,它是代码的IN(...)部分。这基本上是一次运行两个查询。您可以使用JOIN(类似于上面提到的d03boy)或更有针对性的SQL查询将SQL时间减半(或更多!)。一个例子可能是:
SELECT *
FROM tbl_news, tbl_tag_relations
WHERE tbl_tag_relations.tag_id = 20 AND
tbl_news.news_id = tbl_tag_relations.news_id
为了帮助SQL更快地运行,您还希望尽量避免使用SELECT *,并且只选择您需要的信息;最后也提出了一个限制性声明。例如:
SELECT news_title, news_body
...
LIMIT 5;
您还需要查看数据库架构本身。确保您正在索引所有常用的列,以便查询运行得更快。在这种情况下,您可能想要检查news_id和tag_id字段。
最后,您将需要查看PHP代码,看看是否可以创建一个包含所有内容的SQL查询,而不是迭代几个单独的查询。如果您发布更多代码,我们可以提供帮助,这可能是您发布的问题的最大时间节省。 :)
答案 2 :(得分:2)
如果我理解正确,这只是列出一组特定标签的新闻报道。
首先,你真的不应该
永远SELECT *
其次,这可能是
在单个查询中完成,
从而降低了开销成本
多个查询。看起来好像
得到相当琐碎的数据
它可以在一个
单次呼叫而不是20。
IN
的更好方法可能是使用JOIN
来代替WHERE
条件。使用IN
时,基本上会有很多OR
语句。tbl_tag_relations
肯定应该有tag_id
答案 3 :(得分:1)
select *
from tbl_news, tbl_tag_relations
where
tbl_tag_relations.tag_id = 20 and
tbl_news.news_id = tbl_tag_relations.news_id
limit 20
我认为这给出了相同的结果,但我并不是百分百肯定。有时只是限制结果有帮助。
答案 4 :(得分:1)
不幸的是,对于与你的案例展示不相关的子查询,MySQL并不是很好。该计划基本上是说对于外部查询的每一行,将执行内部查询。这将很快失控。其他人提到的重写为普通的旧连接将解决问题,但可能会导致重复行的不良影响。
例如,原始查询将为tbl_news表中的每个限定行返回1行,但是此查询:
SELECT news_id, name, blah
FROM tbl_news n
JOIN tbl_tag_relations r ON r.news_id = n.news_id
WHERE r.tag_id IN (20,21,22)
将为每个匹配的标记返回1行。您可以在那里粘贴DISTINCT,这应该只对数据集的大小产生最小的性能影响。
不要太糟糕,但大多数其他数据库(PostgreSQL,Firebird,Microsoft,Oracle,DB2等)会将原始查询作为有效的半连接处理。我个人认为子查询语法更易读,更容易编写,特别是对于大型查询。