标签搜索

PLM 批量查询工具问题分析与解决办法

wehg489
2026-05-13 / 0 评论 / 1 阅读 / 正在检测是否收录...

初始问题
现象
上传 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

评论 (0)

取消
歌曲封面
0:00