Mysql - 百万级数据查询优化笔记 (PHP Script) ③
说明:上篇介绍Mysql脚本优化的博客竟然突破了1w点击量,这周又加班熬夜渡过了一段不为人知的黑暗,成长都是苦涩的,愿我能行。
1.PHP脚本配置
1.1 memory_limit设置脚本内存
看一下 PHP官网对memory_limit的解释:
This sets the maximum amount of memory in bytes that a script is
allowed to allocate. This helps prevent poorly written scripts for
eating up all available memory on a server. Note that to have no
memory limit, set this directive to -1.
memory_limit 的值是越大越好么?
当然不是,memory_limit 主要是为了防止程序 bug, 或者死循环占用大量的内存,导致系统宕机。在引入大量三方插件,或者代码时,进行内存限制就非常有必要了。
memory_limit 会使每个 PHP process 都占用固定的内存?
memory_limit 只是限制了每个 PHP进程的内存占用上限,而不是为每个进程分配了固定的内存。
所以,并不会因为 memory_limit 设置越大,导致并发数出现降低。
1.2 set_time_limit设置脚本执行时间
在php中set_time_limit函数是用来限制页面执行时间的,如我想把一个php页面的执行时间定义为5秒
就可以set_time_limit(5)了,规定从该句运行时起程序必须在指定秒数内行结束,0代表不限制。
PHP脚本配置设置代码如下:
set_time_limit(0);
ini_set('memory_limit', '1024M');
2.字典初始化使用Nosql
在执行脚本之前进行初始化加载,把需要处理的数据先从Mysql中取出,用Hash的结构,存储到Redis中,读取时条数不宜过大,不超过1千条,服务器性能高可以使用hGall,建议最好不要使用,Redis单线程一次取出过大,会卡掉服务。
private function _initNovelDict()
{
// 作品
echo '初始化小说字典数据' . PHP_EOL;
$novel_id = 0;
while (true){
$novelData = ChangpeiModule_Cpwxw_Novel_Novel::getInstance()
->getAllByPrepareSql(['novel_id', 'novel_name', 'author_id'], [
'where' => [
'novel_id' => ['>',$novel_id],
'novel_status' => 1,
],
'limit' => 5000
]);
if(!$novelData) break;
$novel_id = end($novelData)['novel_id'];
}
$cacheKey = $this->_GetNovelDictKey();
ChangpeiLib_Redis_Hash::hMSet($cacheKey, $novelData);
echo '初始化小说数据完成' . PHP_EOL;
}
2.复杂的sql使用PHP数组去组装
对于复杂的计算,要减少Mysql之间的聚合,先全量取出然后使用PHP处理。
SELECT SUM(t.`gift_number` * g.`gift_price`) total, `nid` FROM `{$table}` t
LEFT JOIN cpwxw2_db_v2.`wm_gift` g ON g.`gift_id` = t.`gift_id`
WHERE `delete_time` = 0 AND `nid` > 0 AND `uid` = {$uid} AND
g.`gift_id` > 0 AND t.`create_time` BETWEEN {$total_start_time}
AND {$total_end_time} GROUP BY nid ORDER BY total DESC LIMIT 3
以下是PHP代码,$mostRewardNovelData
是开始取出的结果集:
$giftData = array_column($mostRewardNovelData, null, 'gift_id');
$gift_ids = array_keys($giftData);
$gift_sql = " SELECT `gift_id` , `gift_price` FROM `wm_gift` WHERE `gift_id` in (" . implode($gift_ids, ',') . ") ";
$giftPriceData = $this->mysqlCpMain->query($gift_sql);
$giftPrice = array_column($giftPriceData, 'gift_price', 'gift_id');
$tempArr = [];
foreach ($mostRewardNovelData as $value) {
$nid = $value['nid'];
$aid = $value['aid'];
$uid = $value['uid'];
$gold = (int)$value['gift_number'] * (int)$giftPrice[$value['gift_id']];
$tempArr[$uid]['nid'][$nid] += $gold;
$tempArr[$uid]['aid'][$aid] += $gold;
}
3.关于内存
很多时候我们都在说内存,其实内存的概念很笼统,内存分为CPU上的缓存(catch)和内存条上的内存(Memory),平时说的服务器8G内存指的是内存条上的内存。Cpu已经从Lmp总线型,升级成NUMP的方式,Nginx就是利用NUMP很好的例子。
其实有一个问题特别迷惑,Mysql数据库里的数据就是以特殊结构存储(B-tree)的文件,Redis中的数据也是用(list、hash)结构存储的数据,存储上没有什么太大的不同,只是有快慢的区别。
而高性能就是尽最大的程度上利用Cpu和内存,提高吞吐量,词很贴切。
4.解决方法和教训
所有的大块要分而治之,最大的难点在于阅读记录表有上亿条数据,最后根据业务的不同,把大的脚本拆成6块,分着跑,这样最大的表的执行时间大概两个小时跑完,其他的都很快,最后解决。
这一周的时间过的真的是炼狱一般,想了好多个方法,都是架构师之路的荆棘,每一天为明天。