php垃圾代码优化那档子事

公司有几个网站搭在美国的虚拟主机上,服务器上的mysql服务差不多每一天都会突然不知什么时候挂掉,然后过一会又恢复了,怀疑是超出cpu的使用限制而被自动结束了,但是实际上该服务器上的流量很小。于是早先的时候联系了服务器提供商的印度阿三客服,想看看是不是其他用户搞多了害的大家一起死,阿三们查找了之后,信誓旦旦的拍着长毛的胸部保证不是他们的问题,事情没有解决。悬着不是个事,只好自己查了,好在可以访问到information_schema库,看了看,没话了,user_statistics里面的数据显示我们的一个mysql用户在busy_time,cpu_time等指标上都高到不行,自己的事,好在阿三没有发现。于是赶紧查程序,之前的这个网站程序不是由我做的,但是知道里面问题很多,架构到实现都有问题,但是页面不是一般的多,代码夹杂着html,全看过去还不死,(这种时候就尤为的觉着mvc多美妙),平时能凑合着运行就可以了,反正没有什么访问量。

既然是mysql的负担重,那就先找这个,本地上搞一个网站的镜像运行下,在my.ini里修改添加

 

[mysqld]
log
="d:/temp/mysql.log"
log_slow_queries
="d:/temp/mysql_slow.log"
long_query_time
=1

$sql1="select pz_area.aid,pz_area.ename,tb1.cc from pz_area right join (SELECT aid,count(*) as cc FROM pz_content WHERE uid=$uid group by aid) as tb1 on pz_area.aid=tb1.aid";
$rs1=$db->query($sql1);
if(is_array($rs1)){
  foreach($rs1 as $r1){
输出...
   echo $r1->ename;
  }
}
这个目录是要已经存在的。重启mysql服务,就可以记录了。

 

查看sql记录后大吃一惊,查询的数量惊人,随便一个页面,sql查询都在几十条,多的有上千条!

以论坛来说吧,一个页面的数据库查询次数也就是10次一下,用了缓存的还可以再低。这样算的话,就相当于原来几十倍的负担,能不挂?

14 Query SELECT * FROM pz_content WHERE uid=19 and sec_id=10 and aid=688 and pic_url<>''
14 Query select * from pz_area where aid=688
14 Query SELECT * FROM pz_content WHERE uid=19 and sec_id=10 and aid=689 and pic_url<>''
14 Query select * from pz_area where aid=689
14 Query SELECT * FROM pz_content WHERE uid=19 and sec_id=10 and aid=690 and pic_url<>''
14 Query select * from pz_area where aid=690
14 Query SELECT * FROM pz_content WHERE uid=19 and sec_id=10 and aid=691 and pic_url<>''
14 Query select * from pz_area where aid=691
14 Query SELECT * FROM pz_content WHERE uid=19 and sec_id=10 and aid=692 and pic_url<>''
14 Query select * from pz_area where aid=692
14 Query SELECT * FROM pz_content WHERE uid=19 and sec_id=10 and aid=693 and pic_url<>''
14 Query select * from pz_area where aid=693
14 Query SELECT * FROM pz_content WHERE uid=19 and sec_id=10 and aid=694 and pic_url<>''
14 Query select * from pz_area where aid=694

谁人也不能那边有毅力写下上百条的查询啊,所以肯定是循环查询。sql语句也表明这一点。知道原因了就好改了,找到相关页面,改掉循环查询,例如,有一个页面,要显示所有地区分类和该分类下的文章数,先不考虑数据库的结构优化,就程序而言,原来的大概是这样子的

 

$sql1="SELECT aid,count(*) as cc FROM pz_content WHERE uid=$uid group by aid";
$rs1=$db->query($sql1);
if(is_array($rs1)){
     foreach($rs1 as $r1){
输出...
        echo id2name($r1->aid);
     }
 }
............
function  id2name($aid)
{
    $sql="select ename from pz_area_a where aid_a=".$id;
    $result=mysql_query($sql);
    $row=mysql_fetch_object($result);
    return $row->ename;
}

先不管代码的容错,看实现就知道,他先读取了该用户的相关文章并按地区ID进行了分组和统计个数,然后再每个地区每个地区地输出地区名字,所以,如果有1w个地区,这里就要查询1w次。放上一个计时的代码,看了下,大概耗用内存6M,执行时间16秒,累计查询1001次

 

其实,这里是只要用一句sql就可以搞定的事情,并不需要循环。

 

问题就可以解决了。重新运行下,内存耗用差不多,查询1次,CPU执行时间只有647毫秒,和原来的差了26倍!再看一下,发现这个pz_content的表,记录还算比较多的,有经常要按地区查询划分之类的,但是原来什么索引也没有。顺道在aid上加上一个索引,执行时间缩短到432毫秒。

 

其实,这样的改动会带来性能上的巨大提升原因也是很明了的。

从执行机制上,对于每一次的query,libmysql都要将其做个封包,并将数据发送到mysql服务器上的3306(默认)的端口上,这个过程中,数据的封包,网络的传输、响应,都需要时间,请求数量越多,这样的重复性操作所要耗用的时间也就越多。

再者,mysql对于每次传入sql语句,都会调用mysql optimizer,其将从query parser传递过来的query进行分解拆析,分析出最优的数据检索方式,也就是sql语句的拆解优化。这过程中,首先,解析器会判断出sql的种类是select啊还是其他的,然后会对整个query做个处理,先搞掉一部分的常量表达式运算,直接整成常量值,再做一个简化,去掉些无用的过滤条件,然后分析下query中可能存在的hint信息。然后根据hint信息来决定是否读取相关对象的统计信息,再根据query整成计算分析,再得出最后的执行计划。虽然mysql的sql优化不及Oracle强悍,但是实际上也要经过很多复杂的运算分析,才能得出最后的执行计划,所以,多次的sql查询,也将在这方面增加不必要的性能开销。

大家都说的,存储过程性能好。为什么好?就好在这里了。在没使用存储过程的情况下,在不会造成过分复杂的查询的前提下,减少sql的查询次数,是提升性能的途径之一。至于说能好多少,那要看具体的情况而定了。

 

这个页面的事情算了了,先到这里,下次继续。

 

(默认)
posted @ 2010-08-03 15:54  xin478  阅读(498)  评论(2编辑  收藏  举报