参考资料
https://segmentfault.com/u/hpinke### 这个人写的其他博客也很好!
MySQL大数据查询内存使用剖析 https://www.jianshu.com/p/c932ec57b216
https://www.gaojipro.com/a/94475 php访问mysql数据库步骤(php获取mysql数据)
迭代器
什么是迭代器模式
看我的博客 https://blog.csdn.net/S_ZaiJiangHu/article/details/121480758
里面有php的示例,大致如下
class sample implements Iterator
{
private $_items = array(1,2,3,4,5,6,7);
public function __construct() {
}
//重置到到第一个元素
public function rewind() { reset($this->_items); }
//返回当前元素
public function current() { return current($this->_items); }
//返回当前元素到键
public function key() { return key($this->_items); }
//返回下一个元素
public function next() { return next($this->_items); }
//检查当前元素是否有效
public function valid() { return ( $this->current() !== false ); }
}
$sa = new sample();
foreach($sa as $key => $val){
print $key . "=>" . $val;
}
//输出0=>1 1=>2 2=>3 3=>4 4=>5 5=>6 6=>7
生成器-----yield
使用场景
今天我们来看看PHP5.5.0引入的生成器(generator),我们常常忽略了这个功能,其实这是非常有用的功能。
yield可解决运行内存的瓶颈,php在循环的时候,会将整个数组读入内存。所以容易造成超时与内存用尽。
php程序中的变量存储在内存中,之前有遇到过读取Excel文件时候,会出现内存不足,出现:
Fatal Error: Allowed memory size of xxxxxx bytes
所以会设置php 最大运行内存的设置: ini_set(‘memory_limit’, ‘200M’)
但是当我们读取5g 这么大的文件的时候,我们运行内存可能就吃不消了,所以会选择yield
假如有100条数据 用foreach就是一次性把数据放到一个数组,内存长度占100。 用yield是一条一条放进去,而且每次放完一条就“销毁”,然后总内存是一条,内存长度只占1。可以想象yield有点类似 return
生成器和迭代器区别
一句话:生成器只是简单的迭代器。
与标准的PHP迭代器不同,PHP生成器不要求类实现Iterator接口,从而减轻了类的负担。生成器会根据需求计算并产出要迭代的值。这对应用的性能有重大影响。试想一下,假如标准的PHP迭代器经常在内存中执行迭代操作,这要预先计算出数据集,性能低下;如果要使用特定的方式计算大量数据,对性能的影响更甚。此时我们可以使用生成器,即使计算并产出后续值,不占用宝贵的内存资源。
创建生成器
生成器的创建方式很简单,因为生成器就是PHP函数,只不过要在函数中一次或多次使用 yield 关键字。
与普通的PHP函数不同的是,生成器从不返回值,只产出值。
//一个简单的生成器 这里还可以设置形参i确定循环value的次数
function myGenerator() {
yield 'value1';
yield 'value2';
yield 'value3';
/*
for($i=0,$i++,$i<$length){
yield 'value'.$i;
}
*/
}
上面就是一个简单的生成器,调用生成器函数时,PHP会返回一个属于 Generator 类的对象(这样就和前面的迭代器产生联系了)。这个对象可以使用 foreach() 函数来迭代。每次迭代,PHP会要求 Generator 实例计算并提供下一个要迭代的值。
//使用foreach() 来迭代
foreach (myGenerator() as $yieldValue) { //注意这里的函数带了括号!
echo $yieldValue, PHP_EOL;
}
//输出
value1
value2
value3
如上:每一次产出一个值之后, 生成器的内部状态都会停顿;向生成器请求下一个值时,内部状态又会恢复。生成器的内部状态会一直在停顿和恢复之间切换,直到抵达函数定义体的末位或遇到空的return;语句为止。
使用生成器
下面我们以一个简单的函数,用于生成一个范围内的数值,以此说明PHP 生成器是如何节省内存的。
<?php
function makeRangeYield($length) {
//$dataset = [];
for ($i = 0; $i < $length; $i++) {
yield $i;
// $dataset[] = $i;
}
//return $dataset;
}
//使用生成器
$startMemory = memory_get_usage();
$t1 = microtime(true);
$customRangeYield = makeRangeYield(1000000);
//结束
$t2 = microtime(true);
$endMemory = memory_get_usage();
foreach ($customRangeYield as $i) {
echo $i . PHP_EOL;
}
//输出
echo sprintf("内存使用: %f kb\n", ($endMemory - $startMemory) / 1024);
echo sprintf("耗时: %f秒\n", round($t2-$t1,3));
我们做个对照比较,大家可以把makeRangeYield() 函数里面的注释打开,和没打开做个对比
// 使用yield的情况
内存使用: 0.562500 kb
耗时: 0.000000秒
// 没有使用yield的情况
内存使用: 32772.078125 kb
耗时: 0.205000秒
因为makeRangeYield()函数要为预先创建的一个由一百万个整数组成的数组分配内存。PHP生成器能实现相同的操作,不过一次只会为一个整数分配内存。
实战应用
生成器这么强,平常工作中用在哪里呢?
1、读取大文件
<?php
header("content-type:text/html;charset=utf-8");
function readTxt()
{
# code...
$handle = fopen("./test.txt", 'rb');
while (feof($handle)===false) {
# code...
yield fgets($handle);
}
fclose($handle);
}
foreach (readTxt() as $key => $value) {
# code...
echo $value . PHP_EOL;
}
假设我们的test.txt文件有4G,我们敢直接把它读入内存吗?肯定是不合适的,我们因该使用生成器一次只为test.txt文件中一行分配内存,而不是把整个4G的文件读取到内存。
2、处理大sql语句(似乎还可用数据库的游标?)
用法示例1 处理sql查询出来的数组
public static function yieldData($data){
foreach ($data as $datum){
yield $datum;
}
}
$res = Db()->select();
$list = self::yieldData($res);
// 逻辑处理
foreach($list as $k => &$v){}
// 导出等等
Common::csv($list)
用法示例2
例如当前我们需要循环数据库的emp表,这个表中有100万行记录,我们需要循环所有mgr=1并修改mgr的字段的值。
未使用生成器
$db = sf::getLib('db');
$sql = "select * from emp where mgr = 1 order by id asc";
$query = $db->query($sql);//query只是执行 不直接拿结果
//mysql_fetch_array() 函数从结果集中取得一行
//作为关联数组,或数字数组,或二者兼有
//返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false。
while ($row = $db->fetch_array($query)) {
$db->exec("update emp set mgr = 2 where id = '{$row['id']}'");
echo "id:" . $row['id'] . "\n";
}
这在数据量小时并没有什么问题,但数据量比较大的话,就无法执行了,会报内存超过限制的错误。
使用生成器
function test_yield(){
$db = sf::getLib('db');
$sql = "select * from emp where mgr = 1 order by id asc";
$query = $db->query($sql);
while ($row = $db->fetch_array($query)) {
yield $row;
}
}
foreach(test_yield() as $item){
$db->exec("update emp set mgr = 2 where id = '{$row['id']}'");
echo "id:" . $item['id']."\r\n";
}
通过test_yield这个生成器函数,程序可以正常执行,且内存不会超限。
注意:以上方法似乎只是节省php的内存占用,没节省mysql占用?因为似乎原理是 mysql生成结果后,保存在内存中,然后发送给php处理?还是发送给php后,php暂存起来,mysql就销毁了这个内存?
3、生成表格
太大导致超时,此时可以用生成器 边生成边下载 但是数据库依然要分页查
生成器的坑
yield只能一维数组,二维数组无效