在PHP中使用 XlsWriter(如 xlswriter
扩展)处理百万级数据的导入导出,需重点解决内存占用和性能问题。
以下是分步骤的实现方案:
一、环境准备
1 安装 xlswriter
扩展
从PECL安装:
pecl install xlswriter
在 php.ini
中启用扩展:
extension=xlswriter.so
2 调整PHP配置
处理大数据时需增加内存和执行时间限制:
memory_limit = 1024M
max_execution_time = 3600
二、百万级数据导出(Excel)
核心思路
-
流式写入:避免一次性加载所有数据到内存。
-
分页查询:从数据库分批读取数据。
-
直接输出到浏览器:减少临时文件占用。
代码实现
<?php
// 1. 初始化Excel对象
$config = ['path' => '/tmp']; // 临时目录(可选)
$excel = new \Vtiful\Kernel\Excel($config);
$file = $excel->fileName('export.xlsx')->header(['ID', 'Name', 'Email']);
// 2. 设置HTTP头直接下载
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="export.xlsx"');
header('Cache-Control: max-age=0');
$file->output();
// 3. 连接数据库
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
// 4. 分页查询并写入数据
$pageSize = 10000; // 每页数据量
$page = 1;
do {
$offset = ($page - 1) * $pageSize;
$stmt = $pdo->prepare("SELECT id, name, email FROM users LIMIT :offset, :limit");
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->bindValue(':limit', $pageSize, PDO::PARAM_INT);
$stmt->execute();
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($data)) {
break;
}
// 写入当前页数据
foreach ($data as $row) {
$file->data([$row['id'], $row['name'], $row['email']]);
}
$page++;
ob_flush(); // 刷新输出缓冲区
flush();
} while (true);
// 5. 结束写入
$file->output();
关键点
-
分页查询:通过
LIMIT
分批拉取数据,避免一次性加载百万数据。 -
流式输出:直接输出到浏览器,减少内存占用。
-
缓冲区刷新:使用
ob_flush()
和flush()
实时推送数据到客户端。
三、百万级数据导入(Excel到数据库)
核心思路
-
分块读取Excel:避免一次性加载整个文件。
-
批量插入:使用事务和批量SQL减少数据库操作次数。
-
错误处理:记录错误数据,避免单条失败导致全部回滚。
代码实现
<?php
// 1. 上传文件处理
$uploadFile = $_FILES['file']['tmp_name'];
if (!is_uploaded_file($uploadFile)) {
die('非法文件');
}
// 2. 初始化Excel读取器
$excel = new \Vtiful\Kernel\Excel();
$excel->openFile($uploadFile);
$sheet = $excel->getSheet();
// 3. 连接数据库
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
$pdo->beginTransaction();
// 4. 分块读取并插入
$batchSize = 5000; // 每批插入量
$batchData = [];
$currentRow = 0;
try {
while ($row = $sheet->nextRow()) {
$currentRow++;
if ($currentRow === 1) {
continue; // 跳过标题行
}
// 数据校验(示例)
if (empty($row[1]) || !filter_var($row[2], FILTER_VALIDATE_EMAIL)) {
error_log("Invalid data at row $currentRow: " . json_encode($row));
continue;
}
// 构建批量插入数据
$batchData[] = [
'id' => $row[0],
'name' => $row[1],
'email' => $row[2]
];
// 批量插入
if (count($batchData) >= $batchSize) {
insertBatch($pdo, $batchData);
$batchData = [];
}
}
// 插入剩余数据
if (!empty($batchData)) {
insertBatch($pdo, $batchData);
}
$pdo->commit();
echo "导入成功!";
} catch (Exception $e) {
$pdo->rollBack();
echo "导入失败: " . $e->getMessage();
}
// 批量插入函数
function insertBatch($pdo, $data) {
$sql = "INSERT INTO users (id, name, email) VALUES ";
$values = [];
$placeholders = [];
foreach ($data as $item) {
$values[] = $item['id'];
$values[] = $item['name'];
$values[] = $item['email'];
$placeholders[] = '(?, ?, ?)';
}
$sql .= implode(', ', $placeholders);
$stmt = $pdo->prepare($sql);
$stmt->execute($values);
}
关键点
-
分块读取:逐行读取Excel,避免内存爆炸。
-
事务提交:批量插入后提交事务,减少数据库压力。
-
错误跳过:记录错误行,避免单条数据错误导致整体失败。
四、性能优化技巧
1 索引优化:
-
在导入前移除索引,导入完成后重新创建。
-
使用
ALTER TABLE ... DISABLE KEYS
和ALTER TABLE ... ENABLE KEYS
(MyISAM引擎)。
2 调整MySQL配置:
innodb_buffer_pool_size = 2G
innodb_flush_log_at_trx_commit = 0
3 压缩Excel文件:
$file = $excel->fileName('export.xlsx')->setCompressionLevel(6);
五、注意事项
-
内存监控:使用
memory_get_usage()
实时监控内存。 -
超时处理:通过
set_time_limit(0)
禁用脚本超时。 -
日志记录:记录导入导出的进度和错误。
通过以上方案,可高效处理百万级数据的导入导出,同时保证系统稳定性和性能。