一、需求
之前公司某个管理后台的用户管理分页功能非常得慢,最后分析才知道其实是慢在select count语句,当存在某些筛选条件的时候,select count的执行时间在10s以上。用户表的数据量大概一千三百多万,单表。产品的要求是:每次点查询后必须实时知道某筛选条件下总用户量和总页数,不可以模糊。(好吧,你说怎样就怎样咯)
二、优化方法
1、思路
其实count慢,就是因为数据量大,那为什么不把数据分段呢,然后并发查询每一段的数据量,再相加就是总数据量了。这就要使用到Guzzle了。Guzzle是一个PHP的HTTP客户端,可以用来轻而易举地发送请求。详情请看官方文档:guzzle文档
2、代码实现
每次并发查询十段,每段的大小为10万,假设用户表的主键id为id,那么第一段只要查询id范围在0-10万之间有多少用户符合筛选条件。假如查询id范围在0-10万之间的时间是查询id范围在0-100万之间的十分之一,因为这十段是并发请求,所以理论上的查询速度可以提高十倍(理论上,实际达不到)。每次并发查询判断是否查完了,还要不要继续,就OK了。最后把每段的count累加起来就行了。使用这种方法,查询出count的时间不会超过3s,这个速度可以接受,比之前的十几秒好多了。下面直接上代码。
$client = new Client(['base_uri' => 'http://httpbin.org/']); //此处替换为你自己的url
$i = 1;
$total = 0; //总数据量
$resultsArr = [];
do {
$promises = [];
for ($j=0; $j < 10; $j++) { //每次并发查询十段
$promises[$j] = $client->getAsync('查询的接口地址', ['query' => ['where'=>$selCondition2, 't' => $i + $j]]); //selCondition2为查询条件,t标记为第几段
}
$results = Promise\unwrap($promises);
for ($j = 0; $j < 10; $j++) {
$resultsArr = json_decode($results[$j]->getBody()->getContents(), true)['content'];
$total = bcadd($total, $resultsArr['count']);
}
$i = $i + 10;
} while (1 != $resultsArr['ifStop']); //是否要停止查询
查询每段数据量的接口,代码为:
$dvbUser = new DvbUser();
$pageSize = 100000; //10万一段
$t = $this->request->get('t', 'int', 1);
$where = $this->request->get('where');
$result['ifStop'] = 0;
$where['AND']['id[>]'] = ($t - 1) * $pageSize;
$where['AND']['id[<=]'] = $t * $pageSize;
if (0 == $t % 10) { //每次查询的最后一段判断是否还要继续
$maxId = $dvbUser->max('id');
$result['ifStop'] = ($t * $pageSize > $maxId) ? 1 : 0;
}
$result['count'] = $dvbUser->count($where);
$this->apiResultStandard(1000, '查询成功!', $result);