初始问题
现象
上传 Excel(大量编号),页面显示 500 Internal Server Error。
原因分析
逐条查询导致超时
原始代码对每个编号都执行 2 次独立的 SQL 查询,假设有 200 个编号,就需要 400 次数据库往返。PHP 默认 max_execution_time 为 30 秒,这种循环必然超时。超时后 PHP 进程被强制终止,Web 服务器返回 500 错误。
无超时与内存配置
未设置 set_time_limit 与 memory_limit,无法应对大数据量处理。
错误信息不可见
代码未将错误细节暴露给前端,只返回笼统的 500,难以排查。
解决办法(根本方案)
改循为批量查询:将所有编号一次传入 SQL,用单次查询替代多次查询。
增加超时与内存设置:set_time_limit(600)、ini_set('memory_limit', '512M')。
增强错误提示:每个可能失败点都将错误信息捕获到 $error 变量,在前端明确显示。
再次出现问题:ORA-01460
现象
改用批量查询后,当编号总量大时,提示:
批量查询执行失败:ORA-01460: 转换请求无法实施或不合理
原因分析
批量查询将多个编号拼接成 'id1,id2,id3,...' 的字符串,绑定到 :id_list 参数。
Oracle 的 VARCHAR2 默认最大长度为 4000 字节(字符集 AL32UTF8 下,一个中文占 3 字节,但编号为字母数字,每个占 1 字节)。
当编号数量很多时(比如 500 个编号,每个 12 字符,加上逗号可达 6500 字节),超出 4000 字节限制,Oracle 无法隐式转换,抛出 ORA-01460。
解决办法:分批查询(Batch)
将编号数组按 100 个一组切片(array_chunk)。
循环执行批量查询,每次绑定 :id_list 只包含 100 个以内的编号,保证字符串长度远低于 4000 字节。
合并各批次结果,生成最终的 CSV。
计算验证:
编号格式如 CB115F0B0002 只有 12 字符。100 个编号拼接长度 ≈ 12×100 + 99 = 1299 字节,安全。
批量查询 SQL(单批次)
$batchSql = "
WITH
input_ids AS (
SELECT TRIM(COLUMN_VALUE) AS body_id
FROM XMLTABLE(('\"' || REPLACE(:id_list, ',', '\",\"') || '\"'))
),
comp_masters AS (
SELECT i.body_id,
bs.END2\$MASTER AS master_fk
FROM input_ids i
JOIN PLM2024.BOMVIEW_0 bv ON bv.MD_ID = i.body_id
JOIN PLM2024.BOMSTRUCTURE_0 bs ON bs.VIEWFK = bv.GUID
),
item_with_rn AS (
SELECT cm.body_id,
it.MD_NAME, it.MD_ID, it.SPECIFICATION, it.REVISIONID,
ROW_NUMBER() OVER (PARTITION BY cm.body_id, it.MD_ID ORDER BY it.REVISIONID DESC) AS rn
FROM comp_masters cm
JOIN PLM2024.ITEM_0 it ON it.MASTERFK = cm.master_fk
),
latest_component AS (
SELECT body_id, MD_NAME, MD_ID, SPECIFICATION FROM item_with_rn WHERE rn = 1
),
original_item AS (
SELECT i.body_id,
i0.SPECIFICATION AS spec,
i1.F_000160 AS product_desc
FROM input_ids i
JOIN PLM2024.ITEM_0 i0 ON i0.MD_ID = i.body_id
LEFT JOIN PLM2024.ITEM_1 i1 ON i1.FOUNDATIONFK = i0.GUID
)
SELECT cs.body_id AS \"编号\",
oi.product_desc AS \"品号描述\",
oi.spec AS \"规格\",
cs.MD_NAME, cs.MD_ID, cs.SPECIFICATION
FROM latest_component cs
LEFT JOIN original_item oi ON cs.body_id = oi.body_id
ORDER BY cs.body_id, cs.MD_ID
";分批执行并合并结果
foreach ($batches as $batch) {
$idListStr = implode(',', $batch);
$stmt = oci_parse($conn, $batchSql);
if (!$stmt) {
$e = oci_error($conn);
$error = "SQL 解析失败:" . $e['message'];
break;
}
oci_bind_by_name($stmt, ':id_list', $idListStr);
if (!oci_execute($stmt)) {
$e = oci_error($stmt);
$error = "查询执行失败:" . $e['message'];
oci_free_statement($stmt);
break;
}
while ($row = oci_fetch_array($stmt, OCI_ASSOC + OCI_RETURN_NULLS)) {
$outputData[] = $row;
}
oci_free_statement($stmt);
}超时与错误可见性
set_time_limit(600);
ini_set('memory_limit', '512M');
ini_set('display_errors', 0); // 生产环境禁止直接输出
error_reporting(E_ALL);
// 致命错误兜底
register_shutdown_function(function() use (&$error) {
$last = error_get_last();
if ($last && in_array($last['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
if (empty($error)) {
$error = "致命错误: " . $last['message'];
}
}
});问题解决流程总结
原始 500 错误 → 循环查询导致超时 → 改为单次批量查询。
批量查询又报 ORA-01460 → 输入字符串超过 4000 字节 → 改为按 100 个一批分多次查询。
增强健壮性 → 每个步骤检测错误并显式输出,配置超时与内存。
最终效果:无论 Excel 包含多少个编号,都能在几秒内稳定完成查询,且错误原因一目了然。
评论 (0)