testProjectConfiguration性能分析报告.md 11 KB

testProjectConfiguration 页面性能分析报告

项目:广西北港新材料检化验系统(beiHai-ui / beiHai-lims) 页面:src/views/standardManagement/components/testProjectConfiguration/testProjectConfiguration.vue 现象:页面从打开到渲染完毕约需 20 秒 分析时间:2026-08-28


一、结论速览

该页面 20 秒耗时不是数据库单条 SQL 慢(已实测全部核心 SQL 均在 250ms 以内),而是请求数量爆炸 + 前端渲染过重 + 无索引全表扫描三者叠加:

  1. 页面一打开并发发出约 19 个 HTTP 请求(父组件 5 个 + 3 个弹窗子组件 14 个),其中 5+ 个是 pageSize=9999 / 99999 的全量查询 —— 这是最大的元凶
  2. 3 个弹窗子组件在页面挂载时即被实例化并执行 mounted 请求,弹窗根本没打开数据就全拉回来了
  3. 页面同时渲染 3 个巨型表格(25 / 32 / 12 列),全部列 sortable + show-overflow-tooltip + show-summary 合计 + fixed 左列,el-tabs 非激活页也渲染,DOM 与 tooltip 实例开销巨大
  4. 后端核心表除主键外几乎无索引,ORDER BY CREATE_TIME DESC 每次全表排序;数据量增长后(生产库)单条 SQL 会指数级变慢
  5. 存在纯冗余请求:gettestItemNameTypeList() 请求的 testItemNameType 在模板中根本未使用

二、实测证据(数据库直连验证,只读)

连接:jdbc:oracle:thin:@172.16.4.164:1521/lims

2.1 数据量(开发库)

表 行数
LIMS_STD_TEST_ITEM(测试项目) 1050
LIMS_STD_ANLY_ITEM(分析项目) 741
LIMS_STD_TEST_ITEM_D(子表) 8309
LIMS_STD_TEST_MATCH(方法标准) 318
LIMS_BASE_INFO 542

2.2 索引情况

表 索引
LIMS_STD_TEST_ITEM 仅主键 TEST_ITEM_NO
LIMS_STD_TEST_ITEM_D 仅主键 TEST_ITEM_NO_D(TEST_ITEM_NO 无索引!)
LIMS_STD_TEST_MATCH 仅主键 TEST_STD_NO(TEST_ITEM_NO 无索引!)
LIMS_STD_ANLY_ITEM 无任何索引

执行计划确认:子表按 TEST_ITEM_NO 查询走 TABLE ACCESS FULL(全表扫描)。

2.3 核心 SQL 实测耗时(开发库,重复 3 次取最优)

SQL 耗时 行数
测试项目分页 50 条 52 ms 50
测试项目全量 validFlag=1 127 ms 329
分析项目全量 ORDER BY 235 ms 741
子表 D 按 TEST_ITEM_NO 查 24~49 ms 5
方法标准按 TEST_ITEM_NO 查 25 ms 0
各字典全量(规则/岗位/单位/修约/公式) 48~124 ms 12~383
COUNT(*) 25 ms 1

⚠️ 单条 SQL 都不慢,但 19 个请求 ×(count 查询 + 数据查询 + JSON 序列化 + 网络传输) 叠加,且生产库数据量可能是开发库的 10~100 倍,无索引的全表扫描会显著放大耗时。


三、问题清单(按严重程度排序)

🔴 P0-1 弹窗子组件挂载即发请求 —— 页面一打开并发 19 个请求

页面底部 3 个弹窗组件没有任何懒加载控制,组件在父页面 mounted 时就被实例化并执行各自的 mounted:

<!-- testProjectConfiguration.vue L320-323 -->
<div is="alertComponets" :showFlag="showFlag" :Params="Params" @refresh='refresh'></div>
<div is="alertComponets3" :showFlag="showFlag3" :Params="Params3" @refresh='refresh2'></div>
<div is="alertComponets4" :showFlag="showFlag4" :Params="Params5" :Params2="Params4" @refresh='refresh3'></div>

各子组件 mounted 发出的请求:

alertComponents.vue(新增/修改测试项目弹窗):

  • queryBaseInfoByBaseCode(4840)
  • queryLimsBaseRuleInfoPage pageSize=9999 ← 全量
  • queryLimsBasePostPage pageSize=99999 ← 全量
  • queryBaseInfoByBaseCode(4808)
  • queryBaseInfoByBaseCode(4804)

alertComponents3.vue(分析项目弹窗):

  • queryLimsStdAnlyItemPage pageSize=9999 ← 全量
  • queryLimsBaseUnitPage pageSize=9999 ← 全量
  • queryBaseInfoByBaseCode(4825)
  • queryBaseInfoByBaseCode(4810)
  • queryLimsBaseReviseRulePage pageSize=9999 ← 全量
  • queryBaseInfoByBaseCode(4804)
  • queryLimsBaseFormulaPage pageSize=9999 ← 全量

alertComponents4.vue(方法标准弹窗):

  • queryBaseInfoByBaseCode(4805)

父组件 mounted(L414-426):

  • queryBaseInfoByBaseCode(4804)
  • getList() → 分析项目分页 pageSize=500(仅弹窗使用,页面打开就拉)
  • getDataList() → 主表分页 50
  • gettestItemNameTypeList() → GET 全量(见 P0-2)
  • addScreen / addScreen2 / addScreen3 → 操作 DOM

合计约 19 个请求并发发出,浏览器同一域名并发上限通常 6 个,大量请求排队 + 后端逐个处理 + 响应体全量传输,是 20 秒的主要构成。

🔴 P0-2 冗余请求:gettestItemNameTypeList 结果从未使用

// L430-435
gettestItemNameTypeList () {
   this.axios.get('pass/baseManagement/v1/limsstdtestitems/'+ '?validFlag=1')
        .then(res => { this.testItemNameType = res.data.list; })
},
  • 该请求拉取全部测试项目(GET 无分页参数),结果存入 testItemNameType
  • 但模板中搜索 testItemNameType —— 从未被引用!下拉框用的是 itemTypeNameType(来自 queryBaseInfoByBaseCode)
  • 属于死代码请求,白拉全量数据,应删除

🟠 P1-3 表格渲染过重(3 个表格 × 全列 sortable/tooltip/fixed/合计)

  • 主表 25 列、分析项目表 32 列、方法标准表 12 列,所有列都加了 sortable + :show-overflow-tooltip="true"
  • 每个表都开启 show-summary 合计行 + :icore-filter-flag(自定义筛选)
  • el-tabs 的两个 tab 非激活也渲染 DOM(未配置 lazy)
  • el-table 的 show-overflow-tooltip 会给每个单元格注册 tooltip 实例,50 行 × 25 列 = 1250 个单元格 + tooltip 初始化,加上 fixed 列双份渲染,DOM 量巨大
  • tableRowClassName 等行回调每行每列执行,渲染阶段开销翻倍

🟠 P1-4 后端索引缺失,生产库风险高

  • 4 张核心表除主键外无业务索引
  • TEST_ITEM_NO 作为高频查询条件(子表 D、MATCH 表)无索引 → 全表扫描(已用执行计划证实)
  • ORDER BY CREATE_TIME DESC 无索引,每次全表排序;LIKE '%${x}%' 无法走索引
  • 开发库 1 千~8 千行还撑得住;生产库一旦上万行,单条 SQL 就会从 50ms 恶化到数秒,19 个请求全部变慢 → 20 秒甚至更久

🟡 P2-5 交互触发额外请求风暴

// L799-807 每次勾选变化就发 2 个请求
handleSelectionChange (val) {
  this.multipleSelection = val;
  if (val.length > 0) {
    this.testItemNo = val[val.length-1].testItemNo
    this.getDataList2(this.testItemNo);  // pageSize=990
    this.getDataList3(this.testItemNo);  // pageSize=990
  }
},
// L846-858 每次点击行也发 2 个请求
handleCurrentChange (val) { ... this.getDataList2(...); this.getDataList3(...); }
  • 点击/勾选任意一行,立即触发 2 个 pageSize=990 的子表查询
  • 勾选多行时会连续触发多次,形成请求风暴
  • 且 getDataList2/getDataList3 的 pageSize 高达 990,每次几乎全量拉取子表

🟡 P2-6 后端日志级别 DEBUG + SQL 全量输出

logging.level.com.steerinfo=DEBUG
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

每执行一条 SQL 都打印到控制台;19 个请求 × 多条 SQL = 几十行日志输出,生产环境日志量大时也会拖慢接口响应。


四、请求链路全景图

页面打开 (mounted)
├─ 父组件 testProjectConfiguration.vue
│   ├─ queryBaseInfoByBaseCode(4804)          字典
│   ├─ getList() → 分析项目 pageSize=500       ← 仅弹窗用,提前拉
│   ├─ getDataList() → 测试项目 pageSize=50   主表
│   ├─ gettestItemNameTypeList() → GET 全量    ← 死代码,结果未使用
│   └─ addScreen ×3                            DOM 操作
│
├─ alertComponents (弹窗1,挂载即请求)
│   ├─ queryBaseInfoByBaseCode(4840)
│   ├─ queryLimsBaseRuleInfoPage   pageSize=9999
│   ├─ queryLimsBasePostPage       pageSize=99999
│   ├─ queryBaseInfoByBaseCode(4808)
│   └─ queryBaseInfoByBaseCode(4804)
│
├─ alertComponents3 (弹窗2,挂载即请求)
│   ├─ queryLimsStdAnlyItemPage    pageSize=9999
│   ├─ queryLimsBaseUnitPage       pageSize=9999
│   ├─ queryBaseInfoByBaseCode(4825)
│   ├─ queryBaseInfoByBaseCode(4810)
│   ├─ queryLimsBaseReviseRulePage pageSize=9999
│   ├─ queryBaseInfoByBaseCode(4804)
│   └─ queryLimsBaseFormulaPage    pageSize=9999
│
└─ alertComponents4 (弹窗3,挂载即请求)
    └─ queryBaseInfoByBaseCode(4805)

≈ 19 个并发请求(其中 6 个为全量查询)

五、优化建议(按优先级)

立即执行(改动小、收益大)

  1. 弹窗组件懒加载:将 <div is="alertComponets..."> 改为 v-if 控制,仅在首次打开对应弹窗时才渲染组件(配合 dialogTableVisible/showFlag 判断),或直接放到对应 el-dialog 内部。这一步可砍掉约 14 个首屏请求
  2. 删除 gettestItemNameTypeList()(死代码,结果未使用)及其在 mounted 中的调用
  3. getList() 改为弹窗打开时调用(addData2 里触发),页面初始化不再拉 500 条分析项目

应当执行

  1. 补索引(后端,生产上线前必须): sql CREATE INDEX IDX_TID_TESTITEMNO ON LIMS_STD_TEST_ITEM_D(TEST_ITEM_NO); CREATE INDEX IDX_TMATCH_TESTITEMNO ON LIMS_STD_TEST_MATCH(TEST_ITEM_NO); CREATE INDEX IDX_TITEM_CREATETIME ON LIMS_STD_TEST_ITEM(CREATE_TIME); CREATE INDEX IDX_ANLY_CREATETIME ON LIMS_STD_ANLY_ITEM(CREATE_TIME); CREATE INDEX IDX_TITEM_VALIDFLAG ON LIMS_STD_TEST_ITEM(VALID_FLAG); 5. 降低弹窗字典查询 pageSize:9999/99999 → 实际需要的数量(如 500),或改为远程搜索 6. 控制子表分页 pageSize:getDataList2/3 的 990 → 50~100,并给子表表格加上分页组件(目前分页被注释了) ### 建议优化 7. 表格瘦身: - 去掉每列的 sortable(仅保留 1~2 个关键列) - 去掉 show-summary 合计行(getSummaries 只统计了行数,意义不大) - 收窄 min-width,减少横向滚动 - 评估去掉 :icore-filter-flag(自定义筛选)是否为必需 8. el-tabs 开启 lazy:<el-tabs lazy> 让非激活 tab 延迟渲染 9. 后端关闭 SQL 日志输出:生产环境将 mybatis.configuration.log-impl 设为 NoLoggingImpl,日志级别从 DEBUG 提为 INFO 10. 防抖选择事件:handleSelectionChange/handleCurrentChange 触发子表查询前加防抖(300ms),避免勾选多行时请求风暴 --- ## 六、补充说明 - 本次分析对数据库仅执行了只读查询(SELECT / EXPLAIN PLAN),未做任何修改 - 前端仅读代码,未改动任何文件 - 开发库数据量较小,实测 SQL 单条均在 250ms 内;生产环境请务必先补索引再观察,否则数据量增长后问题会急剧恶化 - 建议先用浏览器 DevTools Network 面板实测一次:若首屏 19 个请求总耗时中「Waiting (TTFB)」占比高,进一步佐证后端全量查询 + 无索引问题;若「Content Download」占比高,则响应体全量传输是主因