本文目录导读:

- 核心思路:从“单条插入”到“批量操作”
- 方案一:基于 CSV/Excel 文件导入(最常见)
- 方案二:针对超大文件(10万行以上)的优化
- 方案三:使用 MySQL 的
LOAD DATA LOCAL INFILE(最快) - 重要注意事项(避坑指南)
- 总结建议
在PHP项目中实现批量导入数据,通常涉及文件上传、解析文件、数据处理和数据库写入这四个核心步骤。
考虑到数据量(几百条到几十万条)和性能要求,实现方案会有所不同,以下是几种主流的实现方式及详细代码示例。
核心思路:从“单条插入”到“批量操作”
错误做法(性能极差):
foreach ($rows as $row) {
// 逐条插入,每次连接、解析、写入,非常慢
$db->query("INSERT INTO table (col1, col2) VALUES ('{$row[0]}', '{$row[1]}')");
}
正确做法(核心优化):
- 使用
INSERT ... VALUES (...), (...), (...)批量SQL:一次SQL插入成千上万行。 - 使用数据库事务(Transaction):将多次插入放在一个事务里,减少磁盘提交次数。
- 使用预处理语句(Prepared Statement):解析一次SQL模板,多次绑定参数,防止SQL注入并提高效率。
基于 CSV/Excel 文件导入(最常见)
大多数项目使用 CSV(逗号分隔)或 XLSX(Excel)文件。
前端 HTML 表单
<form action="import.php" method="post" enctype="multipart/form-data">
<input type="file" name="import_file" accept=".csv, .xlsx, .xls" required>
<button type="submit">导入数据</button>
</form>
后端 PHP 处理逻辑 (import.php)
<?php
// 确保文件上传成功
if ($_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
die('文件上传失败');
}
$file = $_FILES['import_file']['tmp_name'];
$extension = pathinfo($_FILES['import_file']['name'], PATHINFO_EXTENSION);
// 1. 读取文件数据到数组
$rows = [];
if ($extension === 'csv') {
$rows = readCsv($file);
} elseif (in_array($extension, ['xlsx', 'xls'])) {
// 读取Excel需要 PhpSpreadsheet 库 (composer require phpoffice/phpspreadsheet)
$rows = readExcel($file);
} else {
die('不支持的格式');
}
if (empty($rows)) {
die('文件中没有有效数据');
}
// 2. 批量插入数据库
$db = new mysqli('localhost', 'user', 'pass', 'db_name');
if ($db->connect_error) {
die('数据库连接失败: ' . $db->connect_error);
}
// 开启事务
$db->begin_transaction();
try {
// 预处理SQL语句 (假设表有 name, email, age)
$stmt = $db->prepare("INSERT INTO users (name, email, age) VALUES (?, ?, ?)");
if (!$stmt) {
throw new Exception('SQL准备失败: ' . $db->error);
}
$successCount = 0;
$errorCount = 0;
$batchSize = 1000; // 每1000条提交一次(对于大型文件可以分批提交事务)
foreach ($rows as $index => $row) {
// 数据清洗与验证
$name = trim($row[0] ?? '');
$email = trim($row[1] ?? '');
$age = intval($row[2] ?? 0);
// 简单验证 (可以根据需要增强)
if (empty($name) || empty($email)) {
$errorCount++;
continue;
}
// 绑定参数并执行
$stmt->bind_param('ssi', $name, $email, $age);
if ($stmt->execute()) {
$successCount++;
} else {
$errorCount++;
}
// 可选:每处理N行,释放一点内存(对大文件有用)
if ($index % 1000 === 0) {
usleep(100); // 稍微休息,避免CPU爆满
}
}
// 提交事务
$db->commit();
echo "导入完成:成功 {$successCount} 条,失败 {$errorCount} 条。";
} catch (Exception $e) {
$db->rollback();
echo "导入失败,已回滚:".$e->getMessage();
} finally {
$stmt->close();
$db->close();
}
// --- 辅助函数 ---
/**
* 读取CSV文件 (注意处理BOM头和编码)
*/
function readCsv($filePath, $delimiter = ',') {
$rows = [];
$handle = fopen($filePath, 'r');
if (!$handle) {
return $rows;
}
// 读取第一行,可能是标题行
$header = fgetcsv($handle, 0, $delimiter);
// 如果你有标题行且想忽略,直接继续读取
while (($line = fgetcsv($handle, 0, $delimiter)) !== false) {
// 过滤空行
if (count($line) > 1 && !empty(trim(implode('', $line)))) {
$rows[] = $line;
}
}
fclose($handle);
return $rows;
}
/**
* 读取Excel文件 (需要安装PhpSpreadsheet)
*/
function readExcel($filePath) {
require 'vendor/autoload.php'; // 确保composer autoload加载
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($filePath);
$worksheet = $spreadsheet->getActiveSheet();
$data = $worksheet->toArray();
// 通常第一行是标题,移除
array_shift($data);
return $data;
}
针对超大文件(10万行以上)的优化
如果单文件有几十万甚至上百万行,上述方案可能会超时或内存溢出,需要采用流式读取 + 分批写入 + 内存控制。
关键点:
- 增加PHP执行时间:
set_time_limit(0); - 增加内存限制:
ini_set('memory_limit', '512M'); - 使用
yield生成器:逐行读取,不一次性加载全部数据到内存。 - 分批提交事务:每500~2000条提交一次事务(避免事务日志过大)。
优化后的核心循环示例:
set_time_limit(0);
ini_set('memory_limit', '512M');
$batchSize = 1000;
$batchData = [];
$db->begin_transaction();
$stmt = $db->prepare("INSERT INTO users (name, email, age) VALUES (?, ?, ?)");
foreach (getCsvGenerator($file) as $row) { // 使用生成器逐行读取
// 处理数据...
$batchData[] = [$name, $email, $age];
if (count($batchData) >= $batchSize) {
insertBatch($stmt, $batchData);
$batchData = []; // 清空缓冲区
$db->commit(); // 提交当前批次
$db->begin_transaction(); // 开启下一个事务
}
}
// 处理剩余数据
if (!empty($batchData)) {
insertBatch($stmt, $batchData);
$db->commit();
}
function getCsvGenerator($filePath) {
$handle = fopen($filePath, 'r');
fgetcsv($handle); // 跳过标题行
while (($line = fgetcsv($handle)) !== false) {
yield $line; // 每次只返回一行
}
fclose($handle);
}
function insertBatch($stmt, $data) {
foreach ($data as $row) {
$stmt->bind_param('ssi', ...$row);
$stmt->execute();
}
}
使用 MySQL 的 LOAD DATA LOCAL INFILE(最快)
如果你能控制 MySQL 配置,且数据是纯文本(CSV格式),这是最快的方案,它直接在MySQL服务器端解析文件,避免了PHP逐行解释的开销。
$db = new mysqli('localhost', 'user', 'pass', 'db_name');
// 构造SQL
$sql = "LOAD DATA LOCAL INFILE '" . $db->real_escape_string($filePath) . "'
INTO TABLE users
FIELDS TERMINATED BY ','
ENCLOSED BY '\"'
LINES TERMINATED BY '\\n'
IGNORE 1 LINES -- 跳过标题行
(name, email, age)"; // 对应数据库字段
if ($db->query($sql)) {
$affectedRows = $db->affected_rows;
echo "成功导入 {$affectedRows} 条";
} else {
echo "导入失败: " . $db->error;
}
注意:
- 需要MySQL配置允许
local-infile=1 - 文件需要在服务器本地,或者使用绝对路径
- 比PHP循环快5-10倍以上
重要注意事项(避坑指南)
- 数据验证:在写入数据库之前,一定要对每一行数据进行验证(格式、长度、重复性),给出清晰错误报告。
- SQL注入:永远不要使用变量拼接SQL,必须使用预处理语句或
mysqli_real_escape_string(推荐前者)。 - 错误处理:不要一次性全部成功或全部失败,可以记录哪些行失败(比如写入日志文件),最后告知用户:“成功2000条,失败3条,请下载错误报告”。
- 文件大小限制:
- 前端限制:
<input type="hidden" name="MAX_FILE_SIZE" value="10485760" /> - 后端修改php.ini:
upload_max_filesize = 10M,post_max_size = 10M
- 前端限制:
- 超时问题:对于大文件,使用 Web 界面直接导入容易超时,更好的策略是:
- 异步导入:REST API 接收文件 -> 立即返回“任务已提交” -> 后台进程(CLI脚本/队列系统如 Redis + Resque)慢慢处理。
- 分片上传:前端将大文件切片,后端逐片接收。
总结建议
| 数据量 | 推荐方案 | 原因 |
|---|---|---|
| < 1000 条 | 方案一(事务+批量SQL) | 简单,代码短,性能足够 |
| 1000 ~ 10万条 | 方案一优化版(加大batchSize,使用事务) | 一次性完成,注意超时设置 |
| 10万+ 条 | 方案二(生成器+分批事务) 或 方案三(LOAD DATA) | 防止内存溢出,性能极高 |
| 需要更好的用户体验 | 异步导入(队列) | 界面不阻塞,可做进度条 |
推荐一个轻量级库: maennchen/zipstream + box/spout (用于超高效读取CSV/Excel,内存占用极低)。
希望这些方案能帮你快速实现可靠的批量导入功能!如果有具体的错误信息或需求(比如需要支持多sheet或特殊编码),欢迎补充说明。