优化PHP页面:MySQL瓶颈

时间:2009-04-25 23:52:52

标签: php optimization mysql

我有一个页面需要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

5 个答案:

答案 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条件不能的。

所以我会在下面添加索引:

  • tbl_news(news_id)
  • tbl_tag_relations(news_id)
  • tbl_tag_relations(tag_id)

,查询几乎会立即执行。

最后,请勿使用*来选择所需的所有列。明确命名它们。稍后添加列时,您会遇到麻烦。

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

如果我理解正确,这只是列出一组特定标签的新闻报道。

  1. 首先,你真的不应该 永远SELECT *

  2. 其次,这可能是
    在单个查询中完成, 从而降低了开销成本 多个查询。看起来好像 得到相当琐碎的数据 它可以在一个 单次呼叫而不是20。

  3. 使用IN的更好方法可能是使用JOIN来代替WHERE条件。使用IN时,基本上会有很多OR语句。
  4. 您的tbl_tag_relations肯定应该有tag_id
  5. 的索引

答案 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等)会将原始查询作为有效的半连接处理。我个人认为子查询语法更易读,更容易编写,特别是对于大型查询。