PHP mysqli 批量导入 CSV 怎么控制内存:分批事务、参数绑定与失败回滚
手里有一个 300 MB 的客户 CSV,最容易踩的坑不是 CSV 分隔符,而是把整份文件读进数组后才开始写库。PHP 进程会先吃掉一大块内存,写到一半又遇到一行格式错误,最后既不知道哪一批成功,也很难安全重跑。更稳的做法是让 `fgetcsv()` 一行一行产出记录,每 500 行组成一个事务,批次成功就提交,批次失败就回滚并记录行号。
批量导入的核心不是把 SQL 拼得更长,而是把“读取、写入、提交、复查”切成可控的小批次:单批 500 行只是起点,最终应按内存、锁等待和单批耗时调整。
要点速览
- 用 `fgetcsv()` 流式读取,避免把 CSV 全部加载到 PHP 数组。
- 预编译一条 `mysqli` INSERT,循环绑定每行参数,减少 SQL 拼接和类型混乱。
- 每 500 行开启一次事务;批次内出错立即回滚,保留失败行号和错误文本。
- 验收时同时核对导入计数、失败记录和数据库中的唯一键结果,不能只看脚本退出码。
先把 CSV 导入任务的边界定清楚
这个小工具只负责读取已经存在的 CSV,并把数据写入 `customer_import` 表。上传、权限和文件病毒扫描属于上游流程,不能混在一次导入里。示例文件有四列:`external_no`、`name`、`email`、`joined_on`;其中 `external_no` 建唯一索引,用它避免重复导入。
CREATE TABLE customer_import ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, external_no VARCHAR(40) NOT NULL, name VARCHAR(80) NOT NULL, email VARCHAR(160) NOT NULL, joined_on DATE NOT NULL, UNIQUE KEY uk_customer_external_no (external_no) );
运行前先确认 PHP 已启用 `mysqli`,MySQL 账号只有目标库的必要权限。不要拿生产文件直接试第一遍,先用几十行样本覆盖空值、中文、重复编号和日期格式错误。

用 fgetcsv 和 mysqli 组成 500 行小批次
下面的核心代码故意保持短:文件句柄一次只保留当前行,预编译语句只创建一次,事务边界围绕一个批次。`bind_param()` 的类型串是 `ssss`,因为本例把四列先按字符串接收,日期格式检查通过后再写入 MySQL。
set_charset('utf8mb4');
$db->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT;
$input = fopen(__DIR__ . '/customers.csv', 'rb');
$insert = $db->prepare(
'INSERT INTO customer_import (external_no, name, email, joined_on)
VALUES (?, ?, ?, ?)'
);
$batchSize = 500;
$batchRows = 0;
$totalRows = 0;
$failedRows = [];
$line = 1;
fgetcsv($input); // 跳过表头
$db->begin_transaction();
while (($row = fgetcsv($input)) !== false) {
$line++;
if (count($row) !== 4 || !check_date($row[3])) {
$failedRows[] = ['line' => $line, 'reason' => '字段数量或日期格式不正确'];
continue;
}
[$externalNo, $name, $email, $joinedOn] = array_map('trim', $row);
$insert->bind_param('ssss', $externalNo, $name, $email, $joinedOn);
$insert->send_long_data(0, $externalNo);
$insert->store_result();
$batchRows++;
$totalRows++;
if ($batchRows === $batchSize) {
$db->commit();
$batchRows = 0;
$db->begin_transaction();
}
}
if ($batchRows > 0) {
$db->commit();
}
$insert->close();
fclose($input);
printf("imported=%d failed=%d\n", $totalRows, count($failedRows));
这里的 `store_result()` 只是让语句结果及时收束;真正的批次控制由 `begin_transaction()` 和 `commit()` 完成。示例里的 `check_date()` 应替换成项目已有的日期校验函数,并在函数内部明确使用 `Y-m-d` 规则。生产代码还应捕获数据库异常:任何一行触发唯一键冲突或连接异常,都要回滚当前批次,而不是继续写下一行。
失败时回滚当前批次,成功后留下可对账记录
批量导入最怕“半成功”。如果第 734 行有重复编号,而前 499 行已经写进数据库,下一次重跑就会出现一部分重复、一部分跳过的混乱状态。推荐把每批处理包在 `try/catch` 中:批次成功提交,异常则回滚,并把批次起止行和错误文本写到 `import_failures`。
$batchStart = 2;
try {
$db->begin_transaction();
// 读取并绑定本批次的 CSV 行
// 每成功写入一行就递增 $batchRows
$db->commit();
} catch (mysqli_sql_exception $error) {
$db->rollback();
error_log(json_encode([
'batch_start' => $batchStart,
'batch_rows' => $batchRows,
'message' => $error->getMessage(),
], JSON_UNESCAPED_UNICODE));
throw $error;
}
不要把 CSV 原文和数据库密码一起写进日志。日志只保留行号、外部编号的脱敏值、错误类型和批次编号即可。若业务允许“坏行跳过、好行继续”,就把单行校验错误和数据库异常分开:前者进入失败清单,后者回滚整批并终止任务。

本地运行后,按三组数字验收
准备一个包含 1,200 行数据的测试文件,其中故意放 3 行日期错误、2 行重复 `external_no`。验收不要只看页面上的“导入完成”,至少记录以下三组数字:
- 读取行数:去掉表头后实际扫描了多少行。
- 成功行数:事务提交后确实写入的数量。
- 失败行数:格式失败、唯一键冲突和批次异常分别多少行。
成功行数加失败行数应等于读取行数;如果某批异常回滚,成功数不能把那一批已经尝试过的行算进去。数据库侧再执行 `SELECT COUNT(*)`,并检查 `uk_customer_external_no` 没有重复。导入脚本应输出批次号和耗时,方便比较 100、500、1000 行批次的差异。
常见问题:内存、重复导入和批次大小
为什么不建议先用 file 读取整个 CSV?
`file()` 会把每行都放进 PHP 数组,数据量变大时内存峰值很快超过 `memory_limit`。流式读取让内存主要由当前行、预编译语句和当前批次计数占用。
导入重跑会不会插入重复客户?
`external_no` 需要唯一索引。是否跳过重复、更新已有记录,应该由业务明确选择;不要依赖应用层先查询再插入来“猜测”唯一性。
500 行是不是固定最佳值?
不是。先用 100、500、1000 行做测试,观察单批耗时、锁等待、内存峰值和失败重试成本。事务越大,吞吐可能更高,但回滚代价也更大。
验收清单
真正接入定时任务前,先用测试库跑一遍:CSV 表头已校验;空字段和日期格式有明确错误;每批有提交或回滚;失败记录能定位到行号;数据库唯一键已建立;重新导入同一文件的结果符合预期;最终成功数、失败数和数据库行数能够对上。
Go JSON Decoder 为什么读到 EOF:连续 JSON、空白与尾部脏数据怎么判
- 上一篇
- Go JSON Decoder 为什么读到 EOF:连续 JSON、空白与尾部脏数据怎么判
- 下一篇
- Go os.ReadDir 读取大目录怎么控内存:DirEntry、排序与错误边界
-
- 文章 · php教程 | 17小时前 | 依赖管理 · PHP · composer · 自动加载 · php Composer autoload-dev require-dev
- PHP Composer autoload-dev 为何在线环境找不到类
- 460浏览 收藏
-
- 文章 · php教程 | 18小时前 | 数据结构 · php教程 · SplFixedArray · 数组对比 · PHP实战 · php 数据结构 PHP数组 SplFixedArray 数组性能
- PHP SPLFixedArray 和普通数组有什么取舍
- 281浏览 收藏
-
- 文章 · php教程 | 21小时前 | PHP · curl · 并发请求 · curl_multi_exec curl_multi_select curl_multi_info_read
- PHP curl_multi_exec 如何处理多个并发请求
- 371浏览 收藏
-
- 文章 · php教程 | 23小时前 |
- PHP readonly 属性初始化后为何不能重新赋值
- 387浏览 收藏
-
- 文章 · php教程 | 1天前 | 错误处理 · php教程 · 接口排查 · JSON解析 · php json_decode json_last_error JSON_THROW_ON_ERROR JSON_ERROR_NONE
- PHP json_decode 返回 null 如何区分解析错误
- 239浏览 收藏
-
- 文章 · php教程 | 1天前 | 字符串 · PHP · mbstring · mb_str_split · 中文处理 · php 多字节字符串 中文字符串 mb_str_split 字符串切分
- PHP mb_str_split 按字符切中文怎么控制长度
- 485浏览 收藏
-
- 文章 · php教程 | 1天前 | WEB开发 · PHP · 数据清洗 · 数组处理 · 类型判断 · php 匿名函数 array_values array_filter 保留0值
- PHP array_filter 保留 0 值时回调怎么写
- 300浏览 收藏
-
- 文章 · php教程 | 1天前 | PHP · 时区 · DateTimeImmutable · php 时区 日期处理 DateTimeImmutable
- PHP DateTimeImmutable 修改时区后如何保持业务日期不变
- 175浏览 收藏
-
- 文章 · php教程 | 1天前 | 参数校验 · php教程 · 常见问题 · 输入过滤 · php 查询参数 输入校验 filter_input filter_var $_GET
- PHP filter_input 读取查询参数时为什么拿不到修改后的 $_GET
- 443浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 97次使用
-
- OpenCompass
- OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
- 28次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 252次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 180次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 111次使用
-
- MySQL 明明加了索引,为什么查询还是很慢?先查这 6 个点
- 2026-06-27 374浏览
-
- 接口返回的数据和数据库不一致怎么办?按数据生命周期排查
- 2026-06-27 398浏览
-
- Go语言操作redis数据库的方法
- 2023-01-07 214浏览
-
- Go单元测试对数据库CRUD进行Mock测试
- 2023-02-25 411浏览
-
- Beego中ORM操作各类数据库连接方式详细示例
- 2023-01-07 444浏览
