解析CSV文件超时问题:如何优化4.5万行数据的去重逻辑?
优化方案
原代码的核心问题是每行发起一次数据库查询,45000行就会产生45000次数据库请求,往返开销直接导致超时。以下是针对性的优化方法:
1. 批量查询存在的ID,减少数据库交互次数
先把CSV中所有ID提取出来,再分批用IN语句一次性查询数据库中已存在的ID,最后对比过滤行。这样数据库请求次数从45000次降到几十次,性能提升显著。
修改后的代码示例
$file = fopen($filename, 'r'); $temp = fopen($tempFilename, 'w'); $batchSize = 1000; // 每批次处理的ID数量,避免IN语句过长 $currentBatch = []; $existingIds = []; // 第一步:读取CSV,收集ID并分批查询数据库 while(($row = fgetcsv($file)) !== FALSE){ $id = $row[6]; $currentBatch[] = $id; // 达到批次大小或文件读取结束时,执行批量查询 if(count($currentBatch) === $batchSize){ // 生成预处理占位符 $placeholders = implode(',', array_fill(0, $batchSize, '?')); $sql = "SELECT id FROM table WHERE id IN ($placeholders)"; $stmt = mysqli_prepare($conn, $sql); // 绑定参数(所有ID按字符串类型处理) mysqli_stmt_bind_param($stmt, str_repeat('s', $batchSize), ...$currentBatch); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); // 收集已存在的ID到数组,方便后续快速判断 while($rowDb = mysqli_fetch_assoc($result)){ $existingIds[$rowDb['id']] = true; } mysqli_stmt_close($stmt); $currentBatch = []; // 重置当前批次 } } // 处理剩余的未达批次大小的ID if(!empty($currentBatch)){ $batchSizeLeft = count($currentBatch); $placeholders = implode(',', array_fill(0, $batchSizeLeft, '?')); $sql = "SELECT id FROM table WHERE id IN ($placeholders)"; $stmt = mysqli_prepare($conn, $sql); mysqli_stmt_bind_param($stmt, str_repeat('s', $batchSizeLeft), ...$currentBatch); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); while($rowDb = mysqli_fetch_assoc($result)){ $existingIds[$rowDb['id']] = true; } mysqli_stmt_close($stmt); } // 第二步:重新读取CSV,过滤并写入临时文件 rewind($file); // 将文件指针重置到开头 while(($row = fgetcsv($file)) !== FALSE){ $id = $row[6]; // 如果ID不在已存在列表中,写入临时文件 if(!isset($existingIds[$id])){ fputcsv($temp, $row); } } fclose($file); fclose($temp); // 覆盖原文件 rename($tempFilename, $filename);
2. 额外优化点
- SQL注入防护:原代码直接将
$id拼入SQL,存在注入风险,优化后的代码使用预处理语句彻底解决了这个问题。 - 增大CSV读取缓冲区:调用
fgetcsv时可以指定第二个参数(比如fgetcsv($file, 4096)),默认值较小,增大后能减少IO操作次数。 - 数据库索引优化:确保数据库表的
id字段有索引,执行CREATE INDEX idx_table_id ON table(id);,这会让IN查询的速度大幅提升。 - 调整脚本超时时间:如果还是有超时风险,可以在脚本开头添加
set_time_limit(0);(仅在安全环境下使用,避免无限循环)。
内容的提问来源于stack exchange,提问作者sverdon
相关产品推荐
相关产品推荐

