如何解决PHP文件扫描器处理15000个文件时的超时问题
嘿,我来帮你搞定这个扫描超时的问题!先聊聊你当前代码里拖慢进程的几个关键点,然后咱们一步步优化~
解决PHP文件扫描器超时问题的实用方案
你的代码现在遇到的问题主要来自三个地方:
scandir()一次性把整个目录的文件都塞进内存,15000个文件的话内存压力会很大,进程容易卡壳- 每遍历一个文件就执行一次
INSERT,数据库连来连去的开销累加起来,效率低到爆炸 - PHP默认的执行时间限制(一般30秒)根本扛不住这么多文件的遍历和插入操作
下面是针对性的优化方案,从易到难:
1. 先给脚本“松绑”:调整时间和内存限制
先让脚本有足够的运行空间,在代码最顶部加上这两行:
// 取消执行时间限制(或者设个足够大的值,比如300秒=5分钟) set_time_limit(0); // 给脚本分配更多内存,比如256M,可根据实际情况调整 ini_set('memory_limit', '256M');
这是最基础的操作,先确保脚本不会因为时间或内存不够直接挂掉。
2. 换成迭代式遍历,别一次性加载所有文件
scandir()会把目录里的所有文件一次性读进数组,文件多的时候内存直接爆。换成RecursiveDirectoryIterator来遍历,它是迭代式的,读一个处理一个,内存占用会小很多,还能自动遍历子目录(你说“一半目录”,估计是有子目录没处理吧?):
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('manual/')); foreach ($iterator as $file) { // 只处理文件,跳过.和..以及目录 if ($file->isFile() && !$file->isDot()) { $filename = $file->getFilename(); $fullPath = $file->getPathname(); // 这里是文件的完整路径,你原代码里的$file其实是文件名,可能不符合需求? // 接下来处理文件名拆分逻辑 } }
这个迭代器会自动钻进所有子目录,不用你自己写递归逻辑,省心又高效。
3. 批量插入数据库,减少SQL请求次数
你现在每插一条数据就发一次SQL请求,15000次请求数据库肯定扛不住。改成批量插入,比如每攒100条数据就插一次,能把请求次数降到150次左右,效率直接拉满:
set_time_limit(0); ini_set('memory_limit', '256M'); $db = mysqli_connect('localhost','test','test123','tests'); // 开启事务,批量插入的时候速度更快 $db->begin_transaction(); $batchSize = 100; // 每100条插一次,可根据服务器性能调整 $insertRows = []; $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('manual/')); foreach ($iterator as $file) { if ($file->isFile() && !$file->isDot()) { $filename = $file->getFilename(); $coll = explode(".", $filename); // 处理没有后缀的文件,避免报错 $fname = $coll[0] ?? ''; $lname = $coll[1] ?? ''; $fullPath = $file->getPathname(); // 一定要转义数据,防止SQL注入! $fname = $db->real_escape_string($fname); $lname = $db->real_escape_string($lname); $fullPath = $db->real_escape_string($fullPath); $insertRows[] = "('$fname', '$lname', '$fullPath')"; // 攒够数量就执行插入 if (count($insertRows) >= $batchSize) { $sql = "INSERT INTO `data`(`fname`, `lname`, `directory`) VALUES " . implode(',', $insertRows); $db->query($sql); $insertRows = []; // 清空数组,继续攒下一批 } } } // 处理最后一批不够100条的数据 if (!empty($insertRows)) { $sql = "INSERT INTO `data`(`fname`, `lname`, `directory`) VALUES " . implode(',', $insertRows); $db->query($sql); } $db->commit(); // 提交事务 $db->close();
开启事务也很重要,它能避免每次插入都刷新磁盘,大幅提升插入速度。
4. 进阶方案:用命令行执行或者拆分任务
如果优化后还是超时,试试这些方法:
- 用CLI模式执行:命令行下的PHP默认没有执行时间限制,比网页模式稳定得多,直接在终端跑脚本就行
- 拆分任务:比如先扫描
manual/A到manual/F的目录,再扫manual/G到manual/L,分几次执行 - 用预处理语句:比直接拼SQL更安全,批量执行的时候效率也更高,比如:
$stmt = $db->prepare("INSERT INTO `data`(`fname`, `lname`, `directory`) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $fname, $lname, $fullPath); // 循环里只需要绑定参数然后执行 $stmt->execute();
最后提个醒:防SQL注入!
你的原代码直接把变量拼进SQL,很容易被注入攻击,一定要用real_escape_string()转义,或者用上面的预处理语句,安全第一~
内容的提问来源于stack exchange,提问作者Chris Ngure
相关产品推荐
相关产品推荐

