Laravel从CSV导入数据库时出现额外行问题求助
1. 最可能的低级错误:表名不匹配
你的Seeder代码里操作的是terms表,但Migration创建的是items表,还在Migration里调用了ItemsSeeder。如果这不是笔误,会出现:
- 你以为数据导入到了
items表,但实际Seeder把数据插到了terms表; - 若你查看的
items表之前已有数据,加上其他操作插入的内容,就会出现“多出数百行”的错觉; - 而
terms表被truncate后插入了100万行,最后id是1000000,符合预期。
解决:把Seeder里的terms改成items,保持表名一致。
2. CSV文件解析异常导致多插入行
(1)未跳过表头行
如果你的CSV第一行是表头(比如serial,output),代码里没有跳过这一行,表头会被当作数据插入。若表头的serial不重复,会多一行;但你说多出数百行,更可能是下面的情况。
(2)字段长度限制导致行拆分
你调用fgetcsv时设置了第二个参数100,表示每行最多读取100个字符。如果CSV里的output字段长度超过100,或者包含未转义的换行符、逗号,fgetcsv会把一行拆成多个片段读取,每一个片段都被当作一行插入,累计下来就会多出数百行无效数据。
解决:去掉fgetcsv的第二个参数(或设为0,表示不限制长度):
while (($data = fgetcsv($csvFile, 0, ",")) !== FALSE) { // ... }
(3)CSV末尾有空行
如果CSV文件末尾有大量空行,fgetcsv会读取这些空行并尝试插入。因为MySQL的unique约束对NULL值不生效(NULL不等于NULL),所以这些空行都会被成功插入,导致行数增加。
解决:在插入前判断数据是否为空:
while (($data = fgetcsv($csvFile, 0, ",")) !== FALSE) { // 跳过空行 if (empty(trim($data[0])) && empty(trim($data[1]))) { continue; } DB::table("items")->insert([ "serial" => $data[0], "output" => $data[1] ]); }
3. 事务未处理插入异常(不符合你最后id为1000000的情况)
如果CSV里有重复的serial,插入时会触发唯一约束错误,默认情况下Laravel会抛出异常并回滚整个事务,你应该看不到任何数据。但如果你的数据库关闭了严格模式,或者代码里捕获了异常却没有回滚,可能会导致部分数据插入,同时自增ID因为失败的插入尝试而递增。但你说最后id是1000000且行数多了数百,这个情况不太符合,可检查数据库严格模式配置。
4. 自增ID的误解
你说最后一行id是1000000但行数比100万多,这看起来矛盾,除非你是在未truncate干净的表上插入数据:
- 如果
truncate()执行失败(比如没有权限),旧数据保留,新插入的100万行的id会从旧的最大id开始递增,最后id会是旧最大id+100万,但你说最后id是1000000,说明旧表是空的,这个情况不成立。 - 另一种可能是你混淆了“行数”和“id序列”:比如有数百次插入失败,自增ID跳过了这些次数,最后成功插入的行的id是1000000,但实际成功插入的行数是1000000 - 失败次数,这时候行数应该比100万少,和你说的“多出数百行”相反,所以这个可能性排除。
内容的提问来源于stack exchange,提问作者Hashim Aziz

