You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React改用useState传递查询参数致后端冻结问题求助

后端冻结问题:React+Express+Oracle应用切换到useState参数后必须重启服务

我有一个基于React、Axios、Express、Node和Oracle SQL的应用。之前前端硬编码查询参数时一切正常,但改成用表单结合useState让参数随用户输入更新后,后端出现冻结,必须重启才能恢复。我多次回滚到可用版本排查,还是没找到原因。

前端代码

import { useState, useEffect } from 'react';
import axios from 'axios';

const Query3 = () => {
    const [queryInfo, setQueryInfo] = useState([]);
    const [year1, setYear1] = useState("\"");
    const [year2, setYear2] = useState("\"");
    const [area1, setArea1] = useState("\"");
    const [area2, setArea2] = useState("\"");
    const [itemName, setItemName] = useState("\"");
    const queryId = 'query3';

    // const year1 = '1990';
    // const year2 = '1999';
    // const area1 = 'United States of America';
    // const area2 = 'Europe';
    // const itemName = 'Apples';

    useEffect(() => {
        const loadQueryInfo = async () => {
            setArea1("\"Eurpoe\"");                   // These set methods are placed here for testing purposes only.
            setArea2("\"United States of America\""); // They will eventually go in html that is returned.
            setYear1("\"1990\"");
            setYear2("\"1999\"");
            setItemName("\"Apples\"");
            const response = await axios.get(
                `http://localhost:5000/${queryId}`, 
                {params: {
                    area1,
                    area2,
                    year1,
                    year2,
                    itemName
                }});
            const newQueryInfo = response.data.rows.at(0).at(1);
            setQueryInfo([newQueryInfo]);
        }
        loadQueryInfo();
    }, [area1, area2, itemName, queryId, year1, year2]);

    return (
        <>
        <p>This is the query result (Query 3): {queryInfo} </p>
        </>
    );
}

export default Query3;

后端接口代码

app.get('/query3', (req,res) => {
    async function fetchQuery3() {
        try{
            const connection = await oracledb.getConnection();
            
            oracledb.outFormat = oracledb.OUT_FORMAT_ARRAY;
            const area1 = req.query.area1;
            const area2 = req.query.area2;
            const year1 = req.query.year1;
            const year2 = req.query.year2;
            const itemName = req.query.itemName;

            const query = 
            `select a1.a1yr year1,
                    a1.area1 area1,
                    a1.ratio1 area1_ratio,  
                    a2.a2yr year2,
                    a2.area2 area2,
                    a2.ratio2 area2_ratio
             from (
                 select c1.area_name area1, c1.year a1yr, c1.value hectares, value_2 tonnes, c1.value/value_2 ratio1
                 from crop_data c1
                     inner join
                         (select c2.year c2_yr, c2.area_name c2_country, c2.item_name, c2.unit unit_2, c2.value value_2
                         from crop_data c2
                         where c2.year between ${year1} and ${year2}
                         and c2.unit = 'ha'
                         and c2.item_name = '${itemName}'
                         and c2.area_name = '${area1}')
                     on c1.year = c2_yr and c1.area_name = c2_country
                 where c1.year between ${year1} and ${year2}
                 and c1.unit = 'tonnes'
                 and c1.item_name = '${itemName}'
                 and c1.area_name = '${area1}'
             ) a1
             inner join (
                 select c1.area_name area2, c1.year a2yr, c1.value hectares, value_2 tonnes, c1.value/value_2 ratio2
                 from crop_data c1
                     inner join
                         (select c2.year c2_yr, c2.area_name c2_country, c2.item_name, c2.unit unit_2, c2.value value_2
                         from crop_data c2
                         where c2.year between ${year1} and ${year2}
                         and c2.unit = 'ha'
                         and c2.item_name = '${itemName}'
                         and c2.area_name = '${area2}')
                     on c1.year = c2_yr and c1.area_name = c2_country
                 where c1.year between ${year1} and ${year2}
                 and c1.unit = 'tonnes'
                 and c1.item_name = '${itemName}'
                 and c1.area_name = '${area2}'
             ) a2
             on a1.a1yr = a2.a2yr`;

            const result = await connection.execute(query);

            try {
                await connection.close();
            }
            catch (err) {
                console.log("\"Encountered an error closing a connection in the connection pool.\"");
            }
            return result;
            
        } catch (error) {
            return error;
        }

    }
    fetchQuery3().then(dbRes => {
        res.send(dbRes);
    })
    .catch(err => {
        res.send(err);
    })

})

问题根源及修复方案

1. 前端无限循环导致请求轰炸

问题:useEffect的依赖项包含area1、year1等状态变量,但在useEffect内部又调用setArea1、setYear1修改这些状态,触发新一轮useEffect执行,形成无限循环,短时间内大量请求打向后端,耗尽Oracle连接池资源。

修复:移除useEffect内的测试用setState,或用useRef标记首次渲染避免重复触发,同时添加表单输入逻辑:

import { useState, useEffect, useRef } from 'react';
import axios from 'axios';

const Query3 = () => {
    const [queryInfo, setQueryInfo] = useState([]);
    const [year1, setYear1] = useState("1990");
    const [year2, setYear2] = useState("1999");
    const [area1, setArea1] = useState("Europe");
    const [area2, setArea2] = useState("United States of America");
    const [itemName, setItemName] = useState("Apples");
    const queryId = 'query3';
    const isFirstRender = useRef(true);

    useEffect(() => {
        const loadQueryInfo = async () => {
            // 首次渲染不发起请求,或仅初始化参数
            if (isFirstRender.current) {
                isFirstRender.current = false;
                return;
            }
            try {
                const response = await axios.get(
                    `http://localhost:5000/${queryId}`, 
                    {params: { area1, area2, year1, year2, itemName }}
                );
                const newQueryInfo = response.data.rows.at(0)?.at(1);
                setQueryInfo(newQueryInfo ? [newQueryInfo] : []);
            } catch (err) {
                console.error('请求失败:', err);
            }
        }
        loadQueryInfo();
    }, [area1, area2, itemName, queryId, year1, year2]);

    return (
        <>
            <div className="query-form">
                <input type="text" value={year1} onChange={(e) => setYear1(e.target.value)} placeholder="起始年份" />
                <input type="text" value={year2} onChange={(e) => setYear2(e.target.value)} placeholder="结束年份" />
                <input type="text" value={area1} onChange={(e) => setArea1(e.target.value)} placeholder="区域1" />
                <input type="text" value={area2} onChange={(e) => setArea2(e.target.value)} placeholder="区域2" />
                <input type="text" value={itemName} onChange={(e) => setItemName(e.target.value)} placeholder="物品名称" />
            </div>
            <p>This is the query result (Query 3): {queryInfo} </p>
        </>
    );
}

export default Query3;

2. 后端连接泄漏+SQL注入风险

问题:

  • 原代码中如果SQL执行出错,会进入catch块但未关闭数据库连接,导致连接占用池资源,最终池满后服务冻结;
  • 直接拼接参数到SQL中,不仅存在SQL注入风险,还会因为前端传入带引号的参数(如"Europe")导致SQL语法错误,加剧连接泄漏。

修复:使用参数化查询,并用try/finally确保连接必关闭:

app.get('/query3', async (req, res) => {
    let connection;
    try{
        connection = await oracledb.getConnection();
        oracledb.outFormat = oracledb.OUT_FORMAT_ARRAY;
        
        const { area1, area2, year1, year2, itemName } = req.query;

        // 用占位符代替直接参数拼接
        const query = 
        `select a1.a1yr year1,
                a1.area1 area1,
                a1.ratio1 area1_ratio,  
                a2.a2yr year2,
                a2.area2 area2,
                a2.ratio2 area2_ratio
         from (
             select c1.area_name area1, c1.year a1yr, c1.value hectares, value_2 tonnes, c1.value/value_2 ratio1
             from crop_data c1
                 inner join
                     (select c2.year c2_yr, c2.area_name c2_country, c2.item_name, c2.unit unit_2, c2.value value_2
                     from crop_data c2
                     where c2.year between :year1 and :year2
                     and c2.unit = 'ha'
                     and c2.item_name = :itemName
                     and c2.area_name = :area1)
                 on c1.year = c2_yr and c1.area_name = c2_country
             where c1.year between :year1 and :year2
             and c1.unit = 'tonnes'
             and c1.item_name = :itemName
             and c1.area_name = :area1
         ) a1
         inner join (
             select c1.area_name area2, c1.year a2yr, c1.value hectares, value_2 tonnes, c1.value/value_2 ratio2
             from crop_data c1
                 inner join
                     (select c2.year c2_yr, c2.area_name c2_country, c2.item_name, c2.unit unit_2, c2.value value_2
                     from crop_data c2
                     where c2.year between :year1 and :year2
                     and c2.unit = 'ha'
                     and c2.item_name = :itemName
                     and c2.area_name = :area2)
                 on c1.year = c2_yr and c1.area_name = c2_country
             where c1.year between :year1 and :year2
             and c1.unit = 'tonnes'
             and c1.item_name = :itemName
             and c1.area_name = :area2
         ) a2
         on a1.a1yr = a2.a2yr`;

        // 传入参数映射,自动处理类型和转义
        const result = await connection.execute(query, {
            year1: parseInt(year1),
            year2: parseInt(year2),
            itemName,
            area1,
            area2
        });

        res.send(result);
    } catch (error) {
        console.error('数据库查询错误:', error);
        res.status(500).send({ error: error.message });
    } finally {
        // 无论成功失败都关闭连接
        if (connection) {
            try {
                await connection.close();
            } catch (err) {
                console.error('关闭连接失败:', err);
            }
        }
    }
})

内容的提问来源于stack exchange,提问作者Brian.H

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 05:57:04