首批通过分布式安全可靠测评,为关键业务系统打造
含 NLJ 计划结果可能不对问题
更新时间:2026-07-17 07:16
问题现象
示例,下面 query 预期执行结果为 40,实际执行出来为 26,结果不对;从 SQL Plan Monitor 中分析,可知 12 号算子吐行结果不对。
OceanBase(SYS@SYS)>explain SELECT /*+xtracing(1,12,13) no_use_px*/ Z.ACTINSTID FROM temp_tab Z WHERE 1=1 AND Z.ACTINSTID = ( SELECT /*+LEADING(A,E,F,H,B,D,C,G)*/DISTINCT ACTINSTID FROM V_JBPM4_TASKTRACKS M, ( SELECT /*+merge*/MAX(ACTGENDATE) AS CC, WFINSTID CC1 FROM temp_tab2 N group by WFINSTID) WHERE (ACTACTOR = 'wangli-067') AND WFCODE = Z.WFCODE AND WFINSTID = Z.WFINSTID AND M.ACTGENDATE = CC AND CC1 = M.WFINSTID)\G
*************************** 1. row ***************************
Query Plan: ===========================================================================================
|ID|OPERATOR |NAME |EST. ROWS|COST |
-------------------------------------------------------------------------------------------
|0 |SUBPLAN FILTER | |2 |138530|
|1 | TABLE SCAN |Z |166 |65 |
|2 | MERGE DISTINCT | |1 |835 |
|3 | SORT | |1 |835 |
|4 | MERGE GROUP BY | |1 |835 |
|5 | SORT | |1 |835 |
|6 | NESTED-LOOP JOIN CARTESIAN | |1 |835 |
|7 | TABLE SCAN |N |2 |54 |
|8 | MATERIAL | |1 |781 |
|9 | SUBPLAN SCAN |V_JBPM4_TASKTRACKS |1 |781 |
|10| MERGE DISTINCT | |1 |781 |
|11| SORT | |1 |781 |
|12| NESTED-LOOP JOIN | |1 |781 |
|13| NESTED-LOOP OUTER JOIN | |1 |761 |
|14| NESTED-LOOP JOIN | |1 |199 |
|15| NESTED-LOOP JOIN | |1 |174 |
|16| NESTED-LOOP OUTER JOIN| |1 |147 |
|17| NESTED-LOOP JOIN | |1 |146 |
|18| NESTED-LOOP JOIN | |1 |119 |
|19| TABLE SCAN |A(JBPM4_HIST_PROCINST_ID) |1 |92 |
|20| TABLE SCAN |E(JBPM4_DEPLOYPROP_STRINGVAL) |1 |26 |
|21| TABLE SCAN |F(JBPM4_DEPLOYPROP_DEPLOYMENT) |1 |26 |
|22| TABLE SCAN |H(IDX_JBPM4_TASK_PROCINST) |60 |230 |
|23| TABLE SCAN |B(IDX_JBPM4_HIST_ACTINST_HPROCI)|2 |89 |
|24| TABLE GET |D |1 |29 |
|25| TABLE SCAN |C(IDX_JBPM4_PART_TASK) |66 |750 |
|26| TABLE SCAN |G(JBPM4_DEPLOYPROP_DEPLOYMENT) |1 |26 |
===========================================================================================
Outputs & filters:
-------------------------------------
0 - output([Z.ACTINSTID]), filter([Z.ACTINSTID = subquery(1)]),
exec_params_([Z.WFINSTID], [Z.WFCODE]), onetime_exprs_(nil), init_plan_idxs_(nil)
1 - output([Z.WFCODE], [Z.WFINSTID], [Z.ACTINSTID]), filter(nil),
access([Z.WFCODE], [Z.WFINSTID], [Z.ACTINSTID]), partitions(p0)
2 - output([M.ACTINSTID]), filter(nil),
distinct([M.ACTINSTID])
3 - output([M.ACTINSTID]), filter(nil), sort_keys([M.ACTINSTID, ASC])
4 - output([M.ACTINSTID]), filter([M.ACTGENDATE = T_FUN_MAX(N.ACTGENDATE)]),
group([M.WFINSTSTATE], [M.ACTINSTID], [M.WFVER], [M.ACTCODE], [M.ACTCREDATE], [M.ACTENDDATE], [M.ACTGENDATE], [M.CURRACTCODE], [M.CURRACTINSTID], [M.INSTENDACTCODE]), agg_func([T_FUN_MAX(N.ACTGENDATE)])
5 - output([M.ACTINSTID], [M.WFINSTSTATE], [M.WFVER], [M.ACTCODE], [M.ACTCREDATE], [M.ACTENDDATE], [M.ACTGENDATE], [M.CURRACTCODE], [M.CURRACTINSTID], [M.INSTENDACTCODE], [N.ACTGENDATE]), filter(nil), sort_keys([M.WFINSTSTATE, ASC], [M.ACTINSTID, ASC], [M.WFVER, ASC], [M.ACTCODE, ASC], [M.ACTCREDATE, ASC], [M.ACTENDDATE, ASC], [M.ACTGENDATE, ASC], [M.CURRACTCODE, ASC], [M.CURRACTINSTID, ASC], [M.INSTENDACTCODE, ASC])
6 - output([M.ACTINSTID], [M.WFINSTSTATE], [M.WFVER], [M.ACTCODE], [M.ACTCREDATE], [M.ACTENDDATE], [M.ACTGENDATE], [M.CURRACTCODE], [M.CURRACTINSTID], [M.INSTENDACTCODE], [N.ACTGENDATE]), filter(nil),
conds(nil), nl_params_(nil)
7 - output([N.ACTGENDATE]), filter([? = N.WFINSTID]),
access([N.WFINSTID], [N.ACTGENDATE]), partitions(p0)
8 - output([M.ACTINSTID], [M.WFINSTSTATE], [M.WFVER], [M.ACTCODE], [M.ACTCREDATE], [M.ACTENDDATE], [M.ACTGENDATE], [M.CURRACTCODE], [M.CURRACTINSTID], [M.INSTENDACTCODE]), filter(nil)
9 - output([M.ACTGENDATE], [M.ACTINSTID], [M.WFVER], [M.WFINSTSTATE], [M.ACTCODE], [M.ACTCREDATE], [M.ACTENDDATE], [M.CURRACTCODE], [M.CURRACTINSTID], [M.INSTENDACTCODE]), filter(nil),
access([M.ACTACTOR], [M.WFCODE], [M.WFINSTID], [M.ACTGENDATE], [M.ACTINSTID], [M.WFVER], [M.WFINSTSTATE], [M.ACTCODE], [M.ACTSTATE], [M.ACTCREDATE], [M.ACTENDDATE], [M.CURRACTCODE], [M.CURRACTINSTID], [M.INSTENDACTCODE])
10 - output([F.STRINGVAL_], [G.LONGVAL_], [A.ID_], [A.STATE_], [B.ACTIVITY_NAME_], [B.HTASK_], [D.STATE_], [B.START_], [B.END_], [B.DATE_], [H.NAME_], [H.DBID_], [CASE WHEN D.STATE_ = ? THEN D.ASSIGNEE_ ELSE CASE WHEN (T_OP_IS, C.USERID_, NULL, 0) THEN H.ASSIGNEE_ ELSE C.USERID_ END END], [A.ENDACTIVITY_]), filter(nil),
distinct([A.STATE_], [B.HTASK_], [G.LONGVAL_], [B.ACTIVITY_NAME_], [B.START_], [B.END_], [B.DATE_], [H.NAME_], [H.DBID_], [A.ENDACTIVITY_])
11 - output([F.STRINGVAL_], [G.LONGVAL_], [A.ID_], [A.STATE_], [B.ACTIVITY_NAME_], [B.HTASK_], [D.STATE_], [B.START_], [B.END_], [B.DATE_], [H.NAME_], [H.DBID_], [A.ENDACTIVITY_], [CASE WHEN D.STATE_ = ? THEN D.ASSIGNEE_ ELSE CASE WHEN (T_OP_IS, C.USERID_, NULL, 0) THEN H.ASSIGNEE_ ELSE C.USERID_ END END]), filter(nil), sort_keys([A.STATE_, ASC], [B.HTASK_, ASC], [G.LONGVAL_, ASC], [B.ACTIVITY_NAME_, ASC], [B.START_, ASC], [B.END_, ASC], [B.DATE_, ASC], [H.NAME_, ASC], [H.DBID_, ASC], [A.ENDACTIVITY_, ASC])
12 - output([F.STRINGVAL_], [G.LONGVAL_], [A.ID_], [A.STATE_], [B.ACTIVITY_NAME_], [B.HTASK_], [D.STATE_], [B.START_], [B.END_], [B.DATE_], [H.NAME_], [H.DBID_], [A.ENDACTIVITY_], [CASE WHEN D.STATE_ = ? THEN D.ASSIGNEE_ ELSE CASE WHEN (T_OP_IS, C.USERID_, NULL, 0) THEN H.ASSIGNEE_ ELSE C.USERID_ END END]), filter(nil),
conds(nil), nl_params_([E.DEPLOYMENT_], [CASE WHEN D.STATE_ = ? THEN D.ASSIGNEE_ ELSE CASE WHEN (T_OP_IS, C.USERID_, NULL, 0) THEN H.ASSIGNEE_ ELSE C.USERID_ END END])
13 - output([F.STRINGVAL_], [A.ID_], [A.STATE_], [B.ACTIVITY_NAME_], [B.HTASK_], [D.STATE_], [B.START_], [B.END_], [B.DATE_], [H.NAME_], [H.DBID_], [D.ASSIGNEE_], [C.USERID_], [H.ASSIGNEE_], [A.ENDACTIVITY_], [E.DEPLOYMENT_]), filter(nil),
conds(nil), nl_params_([B.HTASK_])
14 - output([F.STRINGVAL_], [A.ID_], [A.STATE_], [B.ACTIVITY_NAME_], [B.HTASK_], [D.STATE_], [B.START_], [B.END_], [B.DATE_], [H.NAME_], [H.DBID_], [D.ASSIGNEE_], [H.ASSIGNEE_], [A.ENDACTIVITY_], [E.DEPLOYMENT_]), filter(nil),
conds(nil), nl_params_([B.HTASK_])
15 - output([F.STRINGVAL_], [A.ID_], [A.STATE_], [B.ACTIVITY_NAME_], [B.HTASK_], [B.START_], [B.END_], [B.DATE_], [H.NAME_], [H.DBID_], [H.ASSIGNEE_], [A.ENDACTIVITY_], [E.DEPLOYMENT_]), filter(nil),
conds(nil), nl_params_([A.DBID_])
16 - output([F.STRINGVAL_], [A.ID_], [A.STATE_], [H.NAME_], [H.DBID_], [H.ASSIGNEE_], [A.ENDACTIVITY_], [E.DEPLOYMENT_], [A.DBID_]), filter(nil),
conds(nil), nl_params_([A.DBID_])
17 - output([F.STRINGVAL_], [A.ID_], [A.STATE_], [A.ENDACTIVITY_], [E.DEPLOYMENT_], [A.DBID_]), filter(nil),
conds(nil), nl_params_([E.DEPLOYMENT_])
18 - output([A.ID_], [A.STATE_], [A.ENDACTIVITY_], [E.DEPLOYMENT_], [A.DBID_]), filter(nil),
conds(nil), nl_params_([A.PROCDEFID_])
19 - output([A.DBID_], [A.PROCDEFID_], [A.ID_], [A.STATE_], [A.ENDACTIVITY_]), filter(nil),
access([A.DBID_], [A.PROCDEFID_], [A.ID_], [A.STATE_], [A.ENDACTIVITY_]), partitions(p0)
20 - output([E.DEPLOYMENT_]), filter([E.KEY_ = ?]),
access([E.DEPLOYMENT_], [E.KEY_]), partitions(p0)
21 - output([F.STRINGVAL_]), filter([F.STRINGVAL_ = ?], [F.KEY_ = ?]),
access([F.KEY_], [F.STRINGVAL_]), partitions(p0)
22 - output([H.NAME_], [H.DBID_], [H.ASSIGNEE_]), filter(nil),
access([H.NAME_], [H.DBID_], [H.ASSIGNEE_]), partitions(p0)
23 - output([B.HTASK_], [B.START_], [B.END_], [B.ACTIVITY_NAME_], [B.DATE_]), filter([B.TYPE_ = ?]),
access([B.HTASK_], [B.TYPE_], [B.START_], [B.END_], [B.ACTIVITY_NAME_], [B.DATE_]), partitions(p0)
24 - output([D.ASSIGNEE_], [D.STATE_]), filter(nil),
access([D.ASSIGNEE_], [D.STATE_]), partitions(p0)
25 - output([C.USERID_]), filter(nil),
access([C.USERID_]), partitions(p0)
26 - output([G.LONGVAL_]), filter([? = ?], [G.KEY_ = ?]),
access([G.KEY_], [G.LONGVAL_]), partitions(p0)
关键诊断信息
触发条件
SQL 有连接条件,且投影表达式中存在复杂共享表达式。
事前巡检
可以看下是否有连接条件和投影表达式中存在复杂共享表达式,如果存在,则进一步。
事后诊断
分析计划是否满足以下条件:
计划中存在 NLJ 算子(这里称为问题 NLJ 算子进行描述),且是非向量化和非 batch NLJ 场景。
问题 NLJ 算子下压参数为复杂表达式,非简单 column。
该下压表达式需要与问题 NLJ output 为共享表达式。
整个问题 NLJ 算子需要能够被调用 rescan,也就是需要在 NLJ/subplan filter 这种触发 rescan 算子的右支,且有下压参数到问题 NLJ 的子计划上。
在问题 NLP 算子及 rescan 算子间,需要有一个阻塞算子,将问题 NLJ 下压的的复杂表达式数据进行物化。
问题原因
示例中执行计划 12 号NLP 算子,下压参数为 case when 表达式,并且该表达式是 12 号 NLJ 算子的 output, 当前 NLJ 的实现,在 inner_get_next_row 实现中,获取左支数据前后,没有 clear evaluate flag,然后计算 case when 这个下压参数,并下压给 NLJ 右支,由于没有 clear evaluate flag,在 0 号算子 rescan 后,也没有清除 12 号算子中 case when 表达式上已计算标记,导致 rescan 后,12 号 NLJ 算子再进行获取左支数据计算 case when 时,不会触发实际计算,还是使用的上一次 case when 结果的数据,导致 12 号 NLJ 下压给右支的结果不对;最终导致 NLJ 执行结果不对。
可知 Nested Loop Join 算子,在获取内表数据前后,没有将标记表达式是否已经计算的标记清除,导致 Nested Loop Join 在调用 rescan 后,获取内表数据后,没有跟进新的内表数据计算下压的复杂表达式,复用了上一次内表数据计算的结果,导致下压表达式结果不对,最终导致 NLJ 执行结果不对。
问题的风险及影响
可能存在正确性问题。
影响租户
影响 OceanBase 数据库中的 Oracle 租户和 MySQL 租户,对于 SYS 租户无影响。
影响版本
OceanBase 数据库企业版 V3.1.2 GA(oceanbase-3.1.2-20210618150922)及之后版本、V3.2.3 GA(oceanbase-3.2.3.0-20220419)及之后版本、V3.2.4 GA(oceanbase-3.2.4.0-100000072022102819)及之后版本、V4.2.1 GA(oceanbase-4.2.1.0-100000182023092722)及之后版本、V4.2.2 GA(oceanbase-4.2.2.0-100000082024011317)及之后版本。
解决方法
升级至问题已修复版本。目前已修复的版本包括 OceanBase 数据库企业版 V3.2.3 BP10 Hotfix6(oceanbase-3.2.3.3-110060012024011615)、V3.2.3 BP10 Hotfix7(oceanbase-3.2.3.3-110070012024012316)、V3.2.3 BP10 Hotfix8(oceanbase-3.2.3.3-110080012024022713)、V3.2.3 BP10 Hotfix9(oceanbase-3.2.3.3-110090012024041611)、V3.2.3 BP10 Hotfix10(oceanbase-3.2.3.3-110100012024042814)、V3.2.3 BP10 Hotfix11(oceanbase-3.2.3.3-110110032024052217)、V3.2.3 BP10 Hotfix12(oceanbase-3.2.3.3-110120012024060510)、V3.2.3 BP10 Hotfix13(oceanbase-3.2.3.3-110130012024061819)、V3.2.3 BP10 Hotfix14(oceanbase-3.2.3.3-110140012024082010)、V3.2.3 BP10 Hotfix15(oceanbase-3.2.3.3-110150012024102111)、V3.2.3 BP10 Hotfix16(oceanbase-3.2.3.3-110160012025021315)、V3.2.3 BP10 Hotfix17(oceanbase-3.2.3.3-110170012025032112)、V3.2.3 BP10 Hotfix18(oceanbase-3.2.3.3-110180012025060316)、V3.2.3 BP10 Hotfix19(oceanbase-3.2.3.3-110190022025082615)、V3.2.3 BP11(oceanbase-3.2.3.3-111000032024070822)、V3.2.4 BP8(oceanbase-3.2.4.8-108000142024041520)、V4.2.1 BP3 Hotfix4(oceanbase-4.2.1.3-103040012024011620)、V4.2.1 BP3 Hotfix5(oceanbase-4.2.1.3-103050012024012511)、V4.2.1 BP4(oceanbase-4.2.1.4-104000062024022914)、V4.2.2 BP1(oceanbase-4.2.2.1-101000012024030619)。
通过绑定 Hint,改变执行计划,不走 Nested Loop Join。
规避方式
暂无确定的有效规避方式,需要升级到对应修复版本。