项目:广西北港新材料检化验系统(beiHai-ui / beiHai-lims)
页面:src/views/standardManagement/components/testProjectConfiguration/testProjectConfiguration.vue
现象:页面从打开到渲染完毕约需 20 秒
分析时间:2026-08-28
该页面 20 秒耗时不是数据库单条 SQL 慢(已实测全部核心 SQL 均在 250ms 以内),而是请求数量爆炸 + 前端渲染过重 + 无索引全表扫描三者叠加:
pageSize=9999 / 99999 的全量查询 —— 这是最大的元凶sortable + show-overflow-tooltip + show-summary 合计 + fixed 左列,el-tabs 非激活页也渲染,DOM 与 tooltip 实例开销巨大ORDER BY CREATE_TIME DESC 每次全表排序;数据量增长后(生产库)单条 SQL 会指数级变慢gettestItemNameTypeList() 请求的 testItemNameType 在模板中根本未使用连接:jdbc:oracle:thin:@172.16.4.164:1521/lims
| 表 | 行数 |
|---|---|
| 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 |
| 表 | 索引 |
|---|---|
| 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(全表扫描)。
| 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 倍,无索引的全表扫描会显著放大耗时。
页面底部 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() → 主表分页 50gettestItemNameTypeList() → GET 全量(见 P0-2)addScreen / addScreen2 / addScreen3 → 操作 DOM合计约 19 个请求并发发出,浏览器同一域名并发上限通常 6 个,大量请求排队 + 后端逐个处理 + 响应体全量传输,是 20 秒的主要构成。
// L430-435
gettestItemNameTypeList () {
this.axios.get('pass/baseManagement/v1/limsstdtestitems/'+ '?validFlag=1')
.then(res => { this.testItemNameType = res.data.list; })
},
testItemNameTypetestItemNameType —— 从未被引用!下拉框用的是 itemTypeNameType(来自 queryBaseInfoByBaseCode)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 等行回调每行每列执行,渲染阶段开销翻倍TEST_ITEM_NO 作为高频查询条件(子表 D、MATCH 表)无索引 → 全表扫描(已用执行计划证实)ORDER BY CREATE_TIME DESC 无索引,每次全表排序;LIKE '%${x}%' 无法走索引// 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(...); }
getDataList2/getDataList3 的 pageSize 高达 990,每次几乎全量拉取子表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 个为全量查询)
<div is="alertComponets..."> 改为 v-if 控制,仅在首次打开对应弹窗时才渲染组件(配合 dialogTableVisible/showFlag 判断),或直接放到对应 el-dialog 内部。这一步可砍掉约 14 个首屏请求gettestItemNameTypeList()(死代码,结果未使用)及其在 mounted 中的调用getList() 改为弹窗打开时调用(addData2 里触发),页面初始化不再拉 500 条分析项目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」占比高,则响应体全量传输是主因