Touya
(Humandong)
December 14, 2009, 6:52am
1
CActiveRecord中查询提供的findAll功能,只能一次性取出所有记录,这是一个很废内存的操作,稍有不慎就会内存溢出(超过php.ini中设定的memory_size)
有一个表,共27w数据,并不大,我想遍历一次,针对每一个row,做一些操作,用raw PHP很容易实现:
<?php
mysql_connect('192.168.1.123:3306', 'user', 'xxxx') or die("Could not connect: " . mysql_error());
mysql_select_db("my_schema");
mysql_set_charset("utf8");
$result = mysql_query("select * from prop_value");
while ($row = mysql_fetch_assoc($result)) {
$tmp = $row->vid + $row->pid;
//bla bla
}
上述代码run得很畅快,因为它每次取一行数据,不会有内存溢出问题。
而在AR中没有相应的操作,findAll只能一次性取出所有数据,于是:
public function run($args)
{
$values = PropValue::model()->findAll();
foreach ($values as $value) {
$tmp = $value->vid + $value->pid;
}
}
这就直接挂了,memory_size=128M,不够。。
本来以为是AR文档不够,应该有相应的方法可以遍历结果集而不用全部load到内存,但查看源码发现findAll函数调用了
//in findAll last line:
return $this->query($criteria,true);
//in query:
private function query($criteria,$all=false){
//...
return $all ? $this->populateRecords($command->queryAll()) : $this->populateRecord($command->queryRow());
}
//你看到了,就是调用了populateRecords($command->queryAll())
//再看populateRecords:
public function populateRecords($data,$callAfterFind=true)
//...
foreach($data as $attributes){
//...
}
}
绝望,在findAll函数里,实际上已经把数据库中所有行都预先取出来遍历了一次,把你整合成一个array传回……
难道遍历一个大一点的表(何况27w条数据的表并不大)不是一个常见操作吗?遇到这种情况,我只能分次limit??
期待qiang的解答,希望能提供fetch row by row的DB调用方式,谢谢~~
Touya
(Humandong)
December 14, 2009, 9:24am
2
等了一天也没人回答,各位大牛哪里去了啊?
Yii包装精良,extension现在也不少了,不想放弃这个框架,期待达人解答!
Touya
(Humandong)
December 14, 2009, 10:10am
3
暂时先用裸的PDO解决一下了:
$dbh = Yii::app()->db->getPdoInstance();
$sth = $dbh->prepare("select * from prop_value");
$sth->execute();
while($row = $sth->fetch()){
echo $row['prop_name'],"\n";
}
这种代码运行时内存使用60M
如果是以下代码:
$values = PropValue::model()->findAll();
肯定就爆了。
提供一个包装了PDO的fetch方法的函数应该不麻烦,希望能有个patch出来。
实际上页面上不会需要遍历整个数据库,应该还是会有limit的,不过在console中就不一样了。现在是在学习Yii的console框架,感觉数据库访问是一个亟待解决的问题。
pangjanne
(Pngjanne)
December 14, 2009, 10:25am
4
既然你想利用fetch_array()的式样,又想是单个单个的循环读取,那为什么不用DAO的CDbDataReader?
如果是AR,那么你用fetch_array()有干什么,一次读取一个对象?
Touya
(Humandong)
December 14, 2009, 10:35am
5
终于有回答了,不错。
我也刚刚看到了CDbDataReader,经过测试,的确是row by row的模式,不过返回的不是对象,只是数组了,难道一次取一行就不能读取一个对象?既然做了面向对象,为何要分这种情况呢?感觉很奇怪。
以下代码可行:
$command=Yii::app()->db->createCommand("select * from prop_value");
$command->execute(); // a non-query SQL statement execution
// or execute an SQL query and fetch the result set
$reader=$command->query();
foreach($reader as $row){
print_r($row);//array only,not object
}
而且使用了CDbDataReader,就是直接操作CDbConnection,要使用Criteria还挺麻烦,为什么这里要搞两套东西呢?
我看不出对象化和逐行读取有什么冲突啊?
pangjanne
(Pngjanne)
December 14, 2009, 1:58pm
6
为什么搞两套呢?
AR在做一些轻捷的任务时很方便,但是与DAO比起来,从效率上讲,以及复杂的统计的功能,它都还有些欠缺。
如果想在AR上实现step by step 的功能逐行取,也许你可以用PDO的fetch(PDO::FETCH_OBJ)的方法去增加/修改一个方法(感觉AR很强大,用单纯的fetch(PDO::FETCH_OBJ)的很难去维护一个完整的AR)。不管怎样框架又特别是Yii给了你很大的灵活性和预留接口,方便实现自己的功能。(当然这个我也没有去做).
现在很少碰到逐条读取记录的时候了。而且总是会碰到分页读取,感觉没有必要担心。即使以循环一条一条读取记录的方法,碰到如上所述的较多记录时,该方法也不怎么较好,毕竟循环读取的次数有点多吧。
qiang
(Qiang Xue)
December 14, 2009, 3:40pm
7
这种情况你可以考虑直接用DAO。如果希望用AR的话,那么需要分页分批处理(比如每次循环处理1000条)。