PHP写入大文件优化及MySQL生成产品TXT Feed技术咨询
嘿,我看你现在在从MySQL生成竖线分隔的产品TXT Feed,不过遇到了文件体积过大的问题,而且当前的代码写法可能还隐藏着内存占用过高的风险。我给你几个实用的优化方案,从代码逻辑到文件压缩都有:
1. 先解决内存爆炸问题:逐行写入,不要累积内容
你当前的代码是把所有行都拼到$feedRow变量里持续累加,数据量一大,内存会直接撑爆,最后写入时也会很慢。正确的做法是每处理一行就直接写入文件,完全不用累积变量:
$feedFile = fopen(APP_PATH . "external_feed.txt", "w") or die("Unable to open file!"); $separator = '|'; ini_set('max_execution_time', 300); // 逐行读取并写入,内存只存当前一行的数据 while ($row = $mp->getRows()) { // 用implode拼接字段比手动连写更简洁不易出错 $currentRow = implode($separator, [ $row['ClientCode'], $row['CategoryName'], $row['Brand'], $row['Name'], $row['Price'], // 把剩下的8个字段依次填在这里 $row['Field5'], $row['Field6'], $row['Field7'], $row['Field8'], $row['Field9'], $row['Field10'], $row['Field11'], $row['Field12'] ]) . PHP_EOL; // 加上换行符,保证每行独立 // 直接写入文件,写完就释放当前行的内存 fwrite($feedFile, $currentRow); } // 记得关闭文件句柄,避免资源泄漏 fclose($feedFile);
这种写法不管你有1万还是100万条数据,内存占用都极低,只会保留当前处理的那一行内容。
2. 压缩文件:直接生成gzip格式,体积骤减70%-90%
如果生成的TXT还是太大,直接生成压缩后的gzip文件是最有效的办法——绝大多数Feed消费系统都支持读取gzip格式的文件,而且压缩率非常可观。修改代码用gzopen和gzwrite就行:
// 用gzopen创建压缩文件,"w9"表示最高压缩级别(平衡速度可以用w6) $feedFile = gzopen(APP_PATH . "external_feed.txt.gz", "w9") or die("Unable to open compressed file!"); $separator = '|'; ini_set('max_execution_time', 300); while ($row = $mp->getRows()) { $currentRow = implode($separator, [ // 字段列表和上面一样 $row['ClientCode'], $row['CategoryName'], // ... 其他字段 ]) . PHP_EOL; gzwrite($feedFile, $currentRow); } gzclose($feedFile);
生成的.gz文件体积会比原TXT小很多,传输和存储都更方便。
3. 优化数据查询:只拉取需要的字段
检查一下你的$mp->getRows()方法是不是查询了表中所有字段?如果是的话,改成只查询Feed需要的这12个字段(产品编码、分类、品牌、价格+8个其他字段),这样每行的长度会缩短,整体文件大小也会变小。比如把SQL从SELECT * FROM products改成SELECT ClientCode, CategoryName, Brand, ... FROM products,只选需要的字段。
4. 清理字段内容:去掉冗余字符,避免格式破坏
如果你的字段里有多余的空格、换行符,或者不小心包含了分隔符|,不仅会破坏Feed的格式,还会增加文件体积。可以加一个字段清理函数:
function cleanFeedField($value) { // 去掉字段中的分隔符,防止破坏每行的结构 $value = str_replace('|', '', $value); // 把多个连续的空格/换行替换成单个空格,再去掉首尾空白 $value = trim(preg_replace('/\s+/', ' ', $value)); return $value; }
然后在拼接字段的时候用这个函数处理每个值:
$currentRow = implode($separator, [ cleanFeedField($row['ClientCode']), cleanFeedField($row['CategoryName']), // ... 其他字段 ]) . PHP_EOL;
这样既保证了Feed格式的正确性,又减少了不必要的字符,进一步缩小文件体积。
5. 分批查询数据:避免一次性加载所有数据到内存
如果你的$mp->getRows()是一次性把所有数据都加载到内存里(比如用了fetchAll()),那即使逐行写入,查询阶段也会占用大量内存。改成分页查询,每次只拉取1000条左右的数据:
$feedFile = fopen(APP_PATH . "external_feed.txt", "w") or die("Unable to open file!"); $separator = '|'; ini_set('max_execution_time', 300); $offset = 0; $limit = 1000; // 先获取总记录数,用来判断分页结束 $totalRows = $mp->getTotalProductCount(); while ($offset < $totalRows) { // 分页查询,每次取1000条 $batchRows = $mp->getRowsByPage($offset, $limit); foreach ($batchRows as $row) { $currentRow = implode($separator, [ cleanFeedField($row['ClientCode']), // ... 其他字段 ]) . PHP_EOL; fwrite($feedFile, $currentRow); } $offset += $limit; } fclose($feedFile);
你需要给你的$mp类加一个分页查询的方法(比如getRowsByPage),或者直接在SQL里用LIMIT $offset, $limit来实现。
这些方案组合起来,既能解决文件过大的问题,又能避免内存溢出,让你的Feed生成过程更稳定高效。
内容的提问来源于stack exchange,提问作者calin24

