WordPress中获取随机文章导致服务器压力过大的问题分析

最近发现有个服务器上的网站打开速度很慢,有时甚至出现完全打不开的情况。查看了一下服务器状态,发现服务器负载很大,最高时候,load average5分钟平均值都到了20(8核心CPU),接着查看了一下服务IO情况,发现Mysql读写非常频繁,如下图

服务IO状态

服务IO状态

这显然是不正常的情况。接着,本人又查看了mysql的状态,发现出现” Copying to tmp table”的状态,查询语句为“SELECT ID, post_title,guid”。问题应该就出现在这里了,大量临时表的读写显然会导致IO过高,按理说,WordPress官方应该对这些查询做过一些优化,如果一个wordpress会导致出现如此大的问题,它应该也不会如此流行。

百度搜索了一下“SELECT ID, post_title,guid”,发现这个网上有很多获取wordpres随机文章的教程包涵这条语句,整条语句是“SELECT ID, post_title,guid from XXX where post_status = ‘publish’ and post_title != ” AND post_password =” AND post_type = ‘post’ ORDER BY RAND() LIMIT XXX”。这里有个RAND函数,比较显眼。猜想是当查询中出现ORDER BY RAND(),并且记录数量较大时,会出现查询缓慢的情况。查询了一下相关资料:You cannot use a column with RAND() values in an ORDER BY clause, because ORDER BY would evaluate the column multiple times. 在ORDER BY从句里面不能使用RAND()函数,因为这样会导致数据列被多次扫描。所以,IO过高的问题基本就已经找到原因。那么,获取随机文章的功能肯定是需要的,考虑到性能问题,不能用这个查询,有没有其它的办法呢?肯定是有的,这里我把原来的方法和我新做的方法做了一个下对比。下面是具体的函数,实验一,是网上流传的获取随机文章的方法,实验二是新方法,整个数据表的记录是9万条左右,下面是测试代码:

<?php
$host="localhost";
$username="******";
$passwd="******";
$dbname="******";
$table="en_posts";

$cono=mysql_pconnect($host,$username,$passwd);
if(!$cono){
	exit("数据库连接失败");
}
mysql_query("set names utf8");
mysql_select_db($dbname);


//实验一,原来的查询,开始执行
$begin = microtime(TRUE);
$sql="SELECT ID, post_title,guid from $table where post_status = 'publish' and post_title != '' AND post_password ='' AND post_type = 'post' ORDER BY RAND() LIMIT 0,5";
$result=mysql_query($sql,$cono);
while($list=mysql_fetch_row($result)){
	print_r($list);
}
//结束执行
$end = microtime(TRUE);
echo "\n<br/>实验一,执行时间:".($end-$begin)."秒";



//实验二,新的查询,开始执行
$begin=microtime(TRUE);
function getlist($num){
	global $table;
	$arr=array();
	$count=mysql_fetch_row(mysql_query("SELECT MIN(ID),MAX(ID) from $table"));
	$startid=$count[0];
	$endid=$count[1];

	$querycount=0;
	for ($i=0;$i<$num;$i++){
		//安全设置,保证不出现死循环
		if($querycount-$num>5) break;
		$id=rand($startid,$endid);
		$sql="SELECT ID, post_title,guid from $table where ID=$id and post_status = 'publish' and post_title != '' AND post_password ='' AND post_type = 'post'";
		$result=mysql_query($sql);
		if(!$result){$i--;continue;}
		$arr[]=mysql_fetch_row($result);
		$querycount++;
	}
	return $arr;
}
print_r(getlist(5));
$end=microtime(TRUE);
echo "\n<br/>实验二,执行时间:".($end-$begin)."秒";

mysql_close($cono);

两次查询的结果如下:

执行结果对比

执行结果对比

可以看到,同样是随机后去5条记录,实验一用了35秒多,实验二才用了不到60毫秒,相差不可谓不大。下面是新的wordpress获取随机文章的方法,把函数放到主题的functions.php中,在页面中即可实现调用:

function random_posts($posts_num=5,$before='<li>',$after='</li>'){
	global $wpdb;
	$count= $wpdb->get_results("SELECT MIN(ID) as min,MAX(ID) as max from $wpdb->posts");
	$startid=$count[0]->min;
	$endid=$count[0]->max;
	$querycount=0;
	for ($i=0;$i<$posts_num;$i++){
		//安全设置,保证不出现死循环
		if($querycount-$posts_num>$posts_num) break;
		$id=rand($startid,$endid);
		$sql="SELECT ID, post_title,guid from $wpdb->posts where ID=$id and post_status = 'publish' and post_title != '' AND post_password ='' AND post_type = 'post'";
		$randposts=$wpdb->get_results($sql);
		if(! $randposts){$i--;continue;}
		$output = '';
		foreach ($randposts as $randpost) {
			$post_title = stripslashes($randpost->post_title);
			$permalink = get_permalink($randpost->ID);
			$output .= $before.'<a href="'
				. $permalink . '"  rel="bookmark" title="';
			$output .= $post_title . '">' . $post_title . '</a>';
			$output .= $after;
		}
		echo $output;
		$querycount++;
	}
}

再去测试,发现速度要快很多,IO也降低不少,当然,在文章数量较少时,它可能会出现重复的问题。但是像文章上文描述的,文章数量达到9万的情况,这样的代码是更好的选择。

结论:
1,Mysql执行大查询,内存放不下时,信息会全部copy到硬盘,导致IO飙升,查询时间增大。
2,使用网上的代码一定要慎重。

发表回复

评论列表:

FesiK
FesiK
好的代码像首诗,坏的代码像坨屎
回复此留言