You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化PHP代码快速删除服务器未被引用的闲置图片

性能问题根因

你现有代码速度慢且存在严重逻辑bug,核心问题出在这几个地方:

  • 最大性能瓶颈是in_array查找为线性遍历,时间复杂度O(n)。你现在需要遍历15万个文件,每次查找都要扫完12万条数据库记录,总操作次数达180亿次,跑30分钟属于正常情况。
  • 存在致命逻辑bug:mysqli_fetch_all(MYSQLI_ASSOC)返回的是二维数组(每个元素是包含image、media两个键的数组),直接拿字符串做in_array的搜索值永远无法匹配成功,代码实际运行会删除目录下所有图片。
  • glob会一次性把15万个文件路径全加载到内存,内存占用高,大目录场景下容易触发PHP内存限制。
  • 用str_replace截取文件名鲁棒性极差,只要路径中出现重复字符串就会出现替换错误。
优化方案

核心优化思路是把数据库内的在用文件名存为哈希表(PHP关联数组),将文件名查找的时间复杂度从O(n)降到O(1),整体耗时可以压到2秒以内。
优化点说明:

  • 遍历数据库结果时,把image和media两个字段的文件名都作为关联数组的键存储,查找时直接用isset()判断,性能是in_array的上千倍。
  • 用DirectoryIterator逐文件遍历目录,不需要一次性加载全量文件列表,内存占用稳定。
  • 直接通过迭代器内置方法获取文件名和扩展名,避免字符串处理带来的错误。
  • 增加后缀校验逻辑,避免误删非图片文件。

优化后可直接使用的代码:

<?php
// 数据库查询逻辑保持原有逻辑,本身仅耗时1秒无优化必要
$sql = "SELECT image, media FROM products p, product_media pm WHERE p.id = pm.id";
$result = mysqli_query($con, $sql) or die(mysqli_error($con));
$rows = mysqli_fetch_all($result, MYSQLI_ASSOC);

// 将所有在用文件名存入哈希表,键为文件名,值固定为true即可
$usedImages = [];
foreach ($rows as $row) {
    if (!empty($row['image'])) {
        $usedImages[$row['image']] = true;
    }
    if (!empty($row['media'])) {
        $usedImages[$row['media']] = true;
    }
}
// 释放查询结果占用的冗余内存
mysqli_free_result($result);
unset($rows);

$directory = "images/products/full";
// 用目录迭代器逐文件读取,不一次性加载全量文件列表
$dirIterator = new DirectoryIterator($directory);
$allowExt = ['jpg', 'jpeg', 'png', 'bmp'];

foreach ($dirIterator as $fileInfo) {
    // 跳过系统点文件、子目录
    if ($fileInfo->isDot() || $fileInfo->isDir()) {
        continue;
    }
    // 仅处理指定后缀的图片文件
    $ext = strtolower($fileInfo->getExtension());
    if (!in_array($ext, $allowExt)) {
        continue;
    }
    // 直接获取文件名,无需字符串替换处理
    $filename = $fileInfo->getFilename();
    // 哈希表查找,时间复杂度O(1)
    if (!isset($usedImages[$filename])) {
        // 首次运行请先注释unlink,替换为echo $filename . PHP_EOL; 打印待删除列表,核对无误后再执行删除,避免误删
        unlink($fileInfo->getPathname());
    }
}
?>
注意事项
  • 第一次运行务必先做删除校验,不要直接执行删除操作,确认待删除文件列表完全符合预期后再放开unlink逻辑。
  • 如果你的media字段存储的是带路径的文件名,需要提前做路径裁剪,保证和目录内读取到的文件名格式完全一致,否则会出现匹配失败误删的问题。
  • 如果目录下存在需要递归处理的子目录,可以把DirectoryIterator换成RecursiveDirectoryIterator,核心匹配逻辑不需要改动。

内容的提问来源于stack exchange,提问作者bobi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 03:39:43