sqlsrv_fetch_array遍历中断:SQL Server转MySQL脚本异常终止问题
看起来你遇到了一个挺棘手的无声故障——明明sqlsrv_num_rows返回了正确的16万行总数,但脚本处理到5万条左右就悄悄终止了,连个错误提示都没有。这种情况通常和资源限制、驱动行为或者隐性的超时有关,我来给你几个排查和解决的方向:
1. 检查PHP的执行超时与内存限制
处理16万条数据是个耗时且占内存的操作,PHP默认的执行时间(max_execution_time通常30/60秒)和内存限制(memory_limit通常128M)可能不足以支撑,到阈值后脚本会直接终止,且如果没开启错误输出可能看不到任何提示。
你可以在脚本开头临时调整这些参数:
// 取消执行时间限制(0表示无限制) ini_set('max_execution_time', 0); // 调整内存限制为512M,根据实际情况可适当增大 ini_set('memory_limit', '512M');
2. 避免使用可滚动游标,改用流式或分批读取
你当前使用了Scrollable => 'static'静态游标,虽然能正确获取总行数,但静态游标在处理超大结果集时,驱动可能会在后台缓存大量数据,容易引发隐性的资源耗尽或读取中断。
方案一:改用默认顺向游标+实时监控
去掉Scrollable参数,使用默认的顺向游标(只能向前读取),同时增加批量日志输出,方便定位终止位置:
$tsql= "SELECT * FROM ordini_all ORDER BY id ASC"; // 去掉Scrollable参数,使用默认顺向游标 $getResults= sqlsrv_query($msconn, $tsql); if ($getResults === FALSE){ die(FormatErrors(sqlsrv_errors()));} else{echo "ok query - Righe " . sqlsrv_num_rows($getResults) . "\n\n\n";} // ... 其余数据库连接代码不变 ... while($row = sqlsrv_fetch_array($getResults, SQLSRV_FETCH_ASSOC)) { $counter++; // 每处理1000条输出一次进度,强制刷新缓冲区确保能看到实时日志 if ($counter % 1000 === 0) { echo "已处理 {$counter} 条记录...\n"; ob_flush(); flush(); } // ... 你的插入逻辑 ... }
方案二:使用SQL分页查询,分批处理
通过OFFSET ... FETCH NEXT语法分批次读取数据,避免一次性加载所有结果到内存,同时每次处理完一批就释放资源:
$batchSize = 1000; // 每批次处理1000条 $offset = 0; $totalProcessed = 0; do { $tsql = "SELECT * FROM ordini_all ORDER BY id ASC OFFSET {$offset} ROWS FETCH NEXT {$batchSize} ROWS ONLY"; $getResults = sqlsrv_query($msconn, $tsql); if ($getResults === FALSE) { die(FormatErrors(sqlsrv_errors())); } $batchCount = 0; while ($row = sqlsrv_fetch_array($getResults, SQLSRV_FETCH_ASSOC)) { // ... 你的插入逻辑 ... $batchCount++; $totalProcessed++; } sqlsrv_free_stmt($getResults); // 释放当前批次的资源 $offset += $batchSize; echo "累计处理 {$totalProcessed} 条记录...\n"; } while ($batchCount === $batchSize); // 当批次处理数小于设定值时,说明已处理完所有数据
3. 显性检查MySQL写入错误
mysqli默认不会主动抛出异常,即使插入失败也可能静默跳过,你需要手动检查每次插入的结果:
// 假设你的插入语句示例 $insertSql = "INSERT INTO your_mysql_table (col1, col2) VALUES (?, ?)"; $stmt = $mysql_conn->prepare($insertSql); $stmt->bind_param("ss", $row['col1'], $row['col2']); // 根据实际字段类型调整 if (!$stmt->execute()) { $err_counter++; echo "插入第 {$counter} 条记录失败:" . $stmt->error . "\n"; // 可选:记录错误到日志,或者继续处理下一条 continue; } $stmt->close();
另外,也可以检查MySQL的max_allowed_packet参数,如果插入的字段包含大文本/二进制数据,这个参数太小也可能导致静默失败。
4. 检查系统级资源限制
如果服务器的CPU、内存占用过高,系统可能会直接终止PHP进程,这种情况PHP不会输出错误,需要查看服务器的系统日志:
- Linux:查看
/var/log/syslog或/var/log/messages - Windows:打开事件查看器,查看应用程序日志
5. 开启PHP错误日志排查隐性错误
有些错误不会输出到控制台,但会写入PHP错误日志,你可以在脚本开头开启日志:
ini_set('log_errors', 1); ini_set('error_log', '/path/to/your/php-error.log'); // 替换为实际路径
运行脚本后查看日志文件,可能会找到导致终止的隐性错误信息。
你提供的原始代码片段:
$tsql= "SELECT * FROM ordini_all ORDER BY id ASC"; $getResults= sqlsrv_query($msconn, $tsql, array(), array( "Scrollable" => 'static' )); if ($getResults === FALSE){ die(FormatErrors(sqlsrv_errors()));} else{echo "ok query - Righe " . sqlsrv_num_rows($getResults) . "\n\n\n";} // Create connection - to local mysql db $mysql_conn = new mysqli($servername, $username, $password, $dbname); if ($mysql_conn->connect_error) { die("Connection failed: " . $mysql_conn->connect_error);} $inizio = date("h:i:sa"); $counter = 0; $err_counter = 0; $tot_orders = sqlsrv_num_rows($getResults); echo "\n Rows " . $tot_orders; //this is the correct number while($row = sqlsrv_fetch_array($getResults, SQLSRV_FETCH_ASSOC)) { ... do your things to insert rows in another db } echo "\nInizio " . $inizio . " Fine " . date("h:i:sa") . " tot err...";
内容的提问来源于stack exchange,提问作者sissy

