为何原本毫秒级的查询,突然执行时间变慢?
1. 原 SQL
sql
SELECT /*+ OPTIMIZER_OR_NBEXP(2) USE_HASH(A D) */ count(1)
from aviewer.v_app_inp_apply_sheet a,
aviewer.v_dic_bill_type_dict b,
aviewer.v_exam_report_master c,
zoeods_pacs.exam_report_apply_idx_rec d
where a.bill_type_code = b.bill_type_code
and a.apply_no = d.his_bill_no(+)
and d.bill_no = c.bill_no(+)
and a.apply_sheet_status_code != '9'
and a.pres_no is not null
and (b.item_class_code like 'E%' OR b.item_class_code LIKE 'M%')
and a.ITEM_CODE<>'133241'
and (a.REPORT_STATUS_CODE not in ('77', '66') or
a.REPORT_STATUS_CODE is null)
and c.VALID_FLAG = '1'
and c.visit_type = '2'
and (c.PATIENT_ID = '0000886536')
and (c.VISIT_NO = '0113728006' or '0113728006' is null)
AND (c.visit_type = '2' or '2' is null);
手里没有"毫秒级"时期的历史执行计划,无法直接进行前后对比,因此先从当前执行计划入手,分析哪里不符合一个毫秒级查询应有的数据访问特征。
2. 先分析四表关联关系
SQL 中 4 个对象的关联关系为:
text
B --- A --- D --- C
其中 C 表存在比较精确的过滤条件:
text
PATIENT_ID = 某患者
VISIT_NO = 某次就诊
VISIT_TYPE = '2'
VALID_FLAG = '1'
从业务逻辑上看,C 表经过患者、就诊号等条件过滤后,结果集应该比较小,因此理想的数据访问路径应尽量接近:
text
C 很小
↓
通过 BILL_NO 找 D
↓
通过 HIS_BILL_NO 找 A
↓
通过 BILL_TYPE_CODE 找 B
3. 看当前执行计划的 JOIN 顺序
整理当前执行计划的数据流(底层 → 高层):

| 执行计划 | 对应逻辑 |
|---|---|
| 节点17、22 → 节点16(15) | A 表 |
| 节点16(15)、12 → 节点11(10) | A 表 + B 表(NEST LOOP) |
| 节点11(10)、27 → 节点9 | A 表 + B 表 + D 表(HASH) |
| 节点9、5 → 节点4 | A 表 + B 表 + D 表 + C 表(HASH) |
当前实际 JOIN 顺序大致为:
text
A + B
↓
再关联 D
↓
最后关联 C
这与开始设想的 C → D → A → B 几乎是反过来的。
4. 看"预估行数 → 实际行数"
当前执行计划带有实际执行信息,可以看到优化器预估行数与真实执行行数之间存在明显偏差:
- 节点21:相差约 30 倍;
- 节点26:相差约 800 倍;
- 节点16:相差约 700 倍;
- 节点11:相差约 4000 倍;
- 节点9:相差约 400 倍。
其中节点4最后才加入 C 表,实际结果为 0 行。
也就是说,在最终一行数据都不需要的情况下,前面 A、B、D 已经产生并处理了大量中间结果,存在明显无用功。当前计划中 C 表参与 JOIN 过晚。
5. 第一阶段假设:是否只是 JOIN 顺序导致性能下降?
看到当前 JOIN 顺序后,先提出一个最直接的假设:
如果能够让 C 表先利用
PATIENT_ID、VISIT_NO等高选择性条件缩小结果集,再沿着C → D → A → B进行关联,是否就能恢复到毫秒级?
先将 SQL 改写成更容易观察关联链的形式,用于验证这个假设。
5.1 JOIN 顺序验证 SQL
sql
WITH C_FILTER AS
(
SELECT BILL_NO
FROM AVIEWER.V_EXAM_REPORT_MASTER
WHERE VALID_FLAG = '1'
AND VISIT_TYPE = '2'
AND PATIENT_ID = '0000886536'
AND VISIT_NO = '0113728006'
),
CD AS
(
SELECT D.BILL_NO,
D.HIS_BILL_NO
FROM C_FILTER C
JOIN ZOEODS_PACS.EXAM_REPORT_APPLY_IDX_REC D
ON D.BILL_NO = C.BILL_NO
)
SELECT COUNT(1)
FROM CD
JOIN AVIEWER.V_APP_INP_APPLY_SHEET A
ON A.APPLY_NO = CD.HIS_BILL_NO
JOIN AVIEWER.V_DIC_BILL_TYPE_DICT B
ON B.BILL_TYPE_CODE = A.BILL_TYPE_CODE
WHERE A.APPLY_SHEET_STATUS_CODE <> '9'
AND A.PRES_NO IS NOT NULL
AND (B.ITEM_CLASS_CODE LIKE 'E%' OR B.ITEM_CLASS_CODE LIKE 'M%')
AND A.ITEM_CODE <> '133241'
AND (A.REPORT_STATUS_CODE NOT IN ('77', '66')
OR A.REPORT_STATUS_CODE IS NULL);
这段 SQL 的目的不是直接作为最终优化方案,而是把关联关系尽量写清楚,方便验证:
text
C_FILTER
↓
D
↓
A
↓
B
5.2 验证结果
待补充:执行时间、执行计划、实际 JOIN 顺序。
如果改写后仍然没有恢复到预期毫秒级,则说明 JOIN 顺序不合理只是现象之一,还需要继续往下缩小问题范围。
6. 继续缩小范围:只验证关键的 A、D 关联
四表关联中,最关键的一段是:
sql
A.APPLY_NO = D.HIS_BILL_NO
因此继续缩小 SQL,只保留 A、D 两个对象,判断问题究竟出在 JOIN 本身,还是 A 对象的访问路径上。
6.1 使用 A 视图测试
sql
SELECT COUNT(1)
FROM AVIEWER.V_APP_INP_APPLY_SHEET A
JOIN ZOEODS_PACS.EXAM_REPORT_APPLY_IDX_REC D
ON A.APPLY_NO = D.HIS_BILL_NO
WHERE D.BILL_NO = '待替换为实际 BILL_NO';
记录:
text
执行时间:
执行计划:
A 是否走 APPLY_NO 相关索引:
6.2 将 A 视图替换为底层物理表测试
sql
SELECT COUNT(1)
FROM ZOEODS_HIS.APP_INP_APPLY_SHEET A
JOIN ZOEODS_PACS.EXAM_REPORT_APPLY_IDX_REC D
ON A.APPLY_NO = D.HIS_BILL_NO
WHERE D.BILL_NO = '待替换为实际 BILL_NO';
记录:
text
执行时间:
执行计划:
A 是否走 APPLY_NO 主键/索引:
如果直接使用物理表时可以正常利用 APPLY_NO 的主键/索引,而换成 V_APP_INP_APPLY_SHEET 视图后无法走上相同访问路径,则问题范围可以进一步缩小到 A 视图本身。
7. 检查 A 视图定义
查看 AVIEWER.V_APP_INP_APPLY_SHEET 定义,发现两个 UNION ALL 分支都对 APPLY_NO 做了类型转换:
sql
SELECT
TO_CHAR(APPLY_NO) APPLY_NO,
...
FROM ZOEODS_HIS.APP_INP_APPLY_SHEET
UNION ALL
SELECT
TO_CHAR(APPLY_NO) APPLY_NO,
...
FROM ZOEODS_HIS.APP_INP_APPLY_SHEET_POOL;
而原 SQL 中 A、D 的关键关联条件是:
sql
A.APPLY_NO = D.HIS_BILL_NO
通过视图后,外层看到的 A.APPLY_NO 实际已经不是底层物理表中的原始 APPLY_NO 列,而是:
sql
TO_CHAR(APPLY_NO)
因此实际关联逻辑相当于:
sql
TO_CHAR(APP_INP_APPLY_SHEET.APPLY_NO) = D.HIS_BILL_NO
底层 APPLY_NO 原有主键/索引建立在原始列上,当列被 TO_CHAR() 包裹后,原有索引无法像直接使用裸列等值关联时一样被高效利用。
这会影响原本希望形成的访问路径:
text
C
↓
D
↓ 通过 HIS_BILL_NO
A.APPLY_NO 主键/索引定位
↓
A
8. 当前结论
本次性能问题从执行计划上首先表现为:
- JOIN 顺序不理想;
- C 表高选择性条件没有尽早发挥作用;
- A 表产生大量中间结果;
- 最终节点返回 0 行,前面的海量数据处理成为无效工作。
继续通过 SQL 简化和"视图 / 物理表"对比后,将问题逐步缩小到 V_APP_INP_APPLY_SHEET。
最终发现该视图对关键关联字段 APPLY_NO 使用了:
sql
TO_CHAR(APPLY_NO)
类型转换,导致外层 A.APPLY_NO = D.HIS_BILL_NO 实际作用在函数表达式上,影响底层 APPLY_NO 主键/索引的正常使用。
因此,JOIN 顺序异常更像是最终表现出来的现象;更深层的原因是 A 视图对关联字段做了类型转换,使得原本可以通过 D.HIS_BILL_NO → A.APPLY_NO 进行高效级联定位的访问路径无法正常形成。
后续待补充:
- JOIN 顺序验证 SQL 的实际执行计划和耗时;
- A-D 视图测试与物理表测试的执行计划截图;
APP_INP_APPLY_SHEET.APPLY_NO主键/索引定义;- 修正视图或统一字段类型后的最终验证结果。