使用PHP批量导入CSV至Azure数据库仅写入479条、处理耗时久的问题咨询
根因定位
从现有代码和实际现象(仅入库479条、无报错直接显示完成)拆解,存在以下核心问题:
- PDO默认错误模式为静默模式,所有SQL执行失败不会抛出异常,编写的
catch块完全不会触发,中途的所有报错都会被直接吞掉,脚本会顺顺当当走完所有循环,哪怕从第480条开始全部插入失败,也会正常弹出导入完成的提示。 - 未使用参数绑定,直接将CSV字段值拼入SQL语句,只要字段中包含单引号(如姓氏O'Neil)、特殊字符、空值,就会直接触发SQL语法错误,且错误被静默吞掉。
- 未做执行超时适配,PHP默认30秒超时限制、Azure应用服务的资源限制,会导致脚本跑到479条时被强制终止;且Azure默认关闭错误输出,用户看不到超时提示,只会看到中断后的页面误以为流程跑完了。
fgetcsv设置1000字节的行长度限制,只要某行CSV内容超过1000字节,就会被截断成错误的字段值,对应插入必然失败。- 逐行自动提交的插入方式效率极低,2000行数据的自动提交会大幅拖慢处理速度,更容易触发超时限制。
修复方案
- 强制开启PDO的异常抛出模式,让所有SQL、连接层面的错误都能被捕获
- 改为预编译参数绑定模式,预编译语句仅在循环外准备一次,避免逐行重复编译、杜绝特殊字符导致的语法错误
- 全量导入逻辑包裹在单事务中,一次性提交所有成功的插入,避免逐行提交的性能损耗,同时防止半截脏数据入库
- 取消脚本执行超时限制,放宽CSV行读取的截断限制
- 增加逐行错误统计,明确输出成功条数、失败条数及具体原因,不再出现“不知道差了多少条、不知道错在哪”的问题
修正后可直接运行的代码
try { $connection = new PDO("sqlsrv:server = tcp:".$SrvName.",1433; Database = ".$DBName, $SQL_USER, $SQL_PASS); // 开启异常抛出模式,不再静默吞错误 $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 开启全局事务 $connection->beginTransaction(); // 取消脚本执行超时限制 set_time_limit(0); $successCount = 0; $failRecords = []; // 循环外预编译插入语句,仅编译一次 $insertStmt = $connection->prepare(" INSERT INTO personal (clientId, lastname, ssn) VALUES (?, ?, ?) "); if (($handle = fopen("personal.csv", "r")) !== FALSE) { // 读取长度设为0,不截断长行 while (($data = fgetcsv($handle, 0, ",")) !== FALSE) { // 跳过CSV表头行 if ($successCount === 0 && strtolower(trim($data[0])) === 'clientid') { continue; } try { // 绑定参数插入,自动处理特殊字符 $insertStmt->execute([ trim($data[0]), trim($data[1]), trim($data[2]) ]); $successCount++; } catch (PDOException $singleRowError) { // 单行错误不中断整体导入,记录错误详情 $rowIndex = $successCount + count($failRecords) + 2; $failRecords[] = "第{$rowIndex}行:".$singleRowError->getMessage(); } } fclose($handle); } // 所有行处理完成后统一提交事务 $connection->commit(); // 输出处理结果 echo "导入流程结束,成功入库 {$successCount} 条记录"; if (count($failRecords) > 0) { echo "<br><br>共 ".count($failRecords)." 条记录插入失败:"; echo "<pre>".implode("\n", $failRecords)."</pre>"; } } catch (PDOException $fatalError) { // 遇到连接断开、事务错误等严重问题时,回滚所有未提交数据,避免脏数据 if ($connection->inTransaction()) { $connection->rollBack(); } echo "导入流程中断:" . $fatalError->getMessage(); }
额外排查提示
- 现有代码只入库479条的最常见原因,是处理到479条时刚好触发PHP30秒默认超时,脚本被平台强制终止;修正代码后可以直观看到所有失败原因(如主键重复、字段长度超限、数据类型不匹配等),无需再盲目猜测。
- 首次运行建议先把超时设置、行长度设置改完,再跑全量2000行数据,就能得到准确的导入统计结果。
内容的提问来源于stack exchange,提问作者John Iacoucci
相关产品推荐
相关产品推荐

