目 录CONTENT

文章目录

【openGauss】排查一起panweidb2.0子链接提升的coredump案例

DarkAthena
2026-10-31 / 0 评论 / 0 点赞 / 0 阅读 / 0 字

【openGauss】排查一起panweidb2.0子链接提升的coredump案例

背景

最近同事收到panweidb2.0一起故障请求,说是有在B库里,一个7张表关联的查询SQL,大概六十几行,只要只执行,数据库就coredump了,同事就把建表语句和SQL发我,让我来分析一下。
另外,同事提醒,这个用例在panweidb 3.0没有coredump。

初步判断

panweidb2.0 会coredump ,但是3.0没有,自然会想到,可能是openGauss 5.0有BUG,在openGauss 6.0修掉了。所以我在我本地的openGauss 5.0.1里测了下,的确可以复现,在7.0里则正常执行。 (后面我在GaussDB 503.1.0SPC1700 版本测了,也会coredump,505以上就没问题了)

接下来的目标则是:

  1. 找到coerdump的源码级原因
  2. 构造出最小化测试用例
  3. 找到是哪个commit进行的修复

以往,就是复现问题后,用gdb去看coredump文件,在去对源码,分析源码上下文逻辑,识别到问题,再看git历史记录。说着简单,但真正纯手动去做,耗时估计也得有个半天到一天吧。

不过,现在有AI了,只要能复现,上面这些事就可以完全交给AI去干。

本次我用colpilot-cli连我本地的deepseek-flash来分析。

根因诊断(AI)

结论

这不是数据/磁盘问题,是 openGauss 5.0.1 规划器(子链接提升)缺陷导致的空指针解引用。

项值
信号SIGSEGV, si_code=1 (SEGV_MAPERR), si_addr=0x4
崩溃点list_nth_cell ← list_nth(NULL, 0),var.cpp:1295
上层函数flatten_join_alias_vars_mutator(展开 JOIN RTE 别名)
进程pid 789944(主进程)/ 崩溃线程 tid 790080
时间2026-10-20 16:29:50(实例 16:28:48 启动,62 秒后崩溃)

环境

  • openGauss 5.0.1 build 33b035fd,Kylin V10 x86_64,单机单 DN(/opt/og501/data,dn_6001)
  • 库为 B 兼容:sql_compatibility=B,走 dolphin 插件的 planner(已安装 dolphin.so,栈中可见)
  • 关键 GUC:rewrite_rule=magicset、enable_bbox_dump=on、dolphin.optimizer_switch=default
  • 符号表 已于崩溃后 16:32 手工装入 $GAUSSHOME/bin(gaussdb0.symbol,debuglink 一致,版本串与二进制逐字相同);该 build 无 Build-ID

证据链

#3 list_nth_cell (n=0, list=0x0)            list.cpp:637
#4 list_nth (list=0x0, n=0)                 list.cpp:639
#5 flatten_join_alias_vars_mutator (node=0x153b7ca6e3b0, context=...)  var.cpp:1295
#6..19 expression_tree_mutator ×7(T_BoolExpr/T_OpExpr/T_CoalesceExpr)
#20 flatten_join_alias_vars                 var.cpp:1229
#22 preprocess_expression                   dolphin.so planner.cpp:2006
#25 subquery_planner → #28 planner → #29 pg_plan_query → #31 exec_simple_query

崩溃指令(flatten_join_alias_vars_mutator+1436):

mov 0x68(%r13),%rdi      ; r13 = RangeTblEntry 0x153b7ca6eb60, 0x68 = joinaliasvars
lea -0x1(%rax),%esi      ; varattno-1 = 0
call list_nth            ; list_nth(NULL,0) → 读 list-\>length @ 0x4 → si_addr=0x4

触发 SQL(core 中 exec_simple_query 的 query_string 原文,节选):

select count(1) from (SELECT pi.id ... FROM  piii
  INNER JOIN  pi ... LEFT JOIN  bi ... LEFT JOIN  ci ...
 WHERE ... AND NOT EXISTS (SELECT 1 FROM  cyi ...)
   AND ( (current_date - piii.padt) \<= 30
      OR ( ... AND EXISTS (SELECT 1 FROM  mi ...)
           AND ( COALESCE( (SELECT MAX(to_date(substring(mi2.pet,1,10),'yyyy-MM-dd'))
                             FROM  mi2 WHERE mi2.pii = pi.id ...),
                           (SELECT MAX(...) FROM  mi3 WHERE ...) ) \>= current_date ))) t;

崩溃时 parse tree 的实际状态(core 中遍历 rtable,共 14 项):

 9 cyi       kind=RTE_RELATION
10 subquery  kind=RTE_SUBQUERY  (mi)
11 subquery  kind=RTE_SUBQUERY  (mi2, tlist: max, ?column?)
12 unnamed_join kind=RTE_JOIN jointype=JOIN_LEFT  colnames=0  joinaliasvars=NIL  ← 0 列 JOIN RTE
13 subquery  kind=RTE_SUBQUERY  (mi3)
14 unnamed_join kind=RTE_JOIN jointype=JOIN_LEFT  colnames=0  joinaliasvars=NIL  ← 0 列 JOIN RTE
COALESCE 的两个参数: Var(varno=12,varattno=1,type=date), Var(varno=14,varattno=1,type=date)

根因

  1. rewrite_rule=magicset 打开后,计划阶段执行子链接提升:subselect.cpp convert_expr_subink_with_agg_targetlist() 把 COALESCE 里的两个 MAX(...) 标量子查询提升成 LEFT JOIN,并在 subselect.cpp:5942 用
    addRangeTableEntryForJoin(NULL, NIL, JOIN_LEFT, NIL, NULL, true) 追加一个占位用的 0 列 JOIN RTE(parse_relation.cpp:1679 直接令 joinaliasvars = aliasvars = NIL)。5776/6341 行是同样写法。
  2. 重写后的 qual 中出现 Var(varno=12/14, varattno=1),指向的正是这两个没有任何列的 JOIN RTE。
  3. 随后 subquery_planner → preprocess_qual_conditions → preprocess_expression 对顶层 qual 无条件调用 flatten_join_alias_vars();var.cpp:1295 假定 joinaliasvars 至少有 varattno 个元素,直接 list_nth(NIL, 0) → 段错误。

已证实:崩溃点、非法 Var、0 列 JOIN RTE 及其产生代码。未证实:为何 varno 落在 12/14(空 JOIN RTE)而非 11/13(子查询 RTE)——这是该重写内部 varno 计算/后续偏移的具体缺陷,需带调试信息单步才能定性。

影响面与旁证

  • 只有 1 个 core,即本次事件;pg_log 在 16:29:35 后完全静默,之后无重启记录,实例目前处于停止状态。
  • 启动日志有 database system was not properly shut down; automatic recovery in progress(16:28:49),说明更早一次是非正常关闭(最后一次日志为 2026-01-10),但无对应 core,无法确认是否同类崩溃。

处置建议

  • 规避(会话级,先在测试环境验证):该版本 rewrite_rule 支持 disable_pullup_expr_sublink(二进制校验串已确认),可试
    SET rewrite_rule = 'disable_pullup_expr_sublink'; 后再跑该 SQL。
  • SQL 改写(更稳妥的临时方案):把 COALESCE 内两个 MAX(...) 标量子查询改成一次预聚合的 LEFT JOIN / 临时表两步取数,避开提升路径。
  • 上报内核:附 core、ffic_gaussdb-2026-10-20_162950.log、完整 SQL、gaussdb --version(5.0.1 build 33b035fd)与上述栈;这是 planner 产生非法 parse tree,非用户数据问题,加索引/调参无效。

操作说明:全程只读,未改动实例、配置或 core;仅在 /tmp 生成过分析文件,已清理,保留一份完整栈 /tmp/og501-core-bt.txt。如需我启动 og501 实例做复现与 rewrite_rule 规避验证(会再次触发崩溃并生成新 core),请确认。


最小化用例(AI)

-- DDL:0 行数据即可,崩溃发生在计划阶段
create table t1 (id int, d date);

-- 崩溃语句
select * from (
  select 1 from t1 a
   where (false
          or coalesce((select max(b.d) from t1 b where b.id = a.id),
                      (select max(b.d) from t1 b where b.id = a.id)) >= current_date)
) t;

实测结果:connection to server was lost,DN 进程整体退出,生成 core-gaussdb-\<pid\>-*-bbox.lz4。共复现 12 次,13 份 ffic 日志(含原始那次)栈完全一致:si_addr=0x4、list_nth ← flatten_join_alias_vars_mutator。

收敛过程(每一条都是实测)

变体结果
单个聚合标量子查询 + OR + 派生表不崩
COALESCE(子查询, 常量)不崩
COALESCE(两个聚合子查询),AND 连接不崩
COALESCE(两个聚合子查询) + OR,无派生表不崩
两个非聚合标量子查询不崩
两个无关联聚合子查询不崩
三表版本(原始形状 3 表)崩
单表自关联(上例)崩
count(*) 代替 max()(比较改 \>= 0)崩
A 兼容库(postgres)里同样语句也崩,栈相同
set rewrite_rule='disable_pullup_expr_sublink'不崩,正常返回

崩溃三要素:① 外层派生表(子查询上拉,varno 偏移);② WHERE 顶层是 OR(走 convert_OREXPR/OREXISTS_to_join);③ COALESCE 内两个带聚合函数的相关标量子查询(各被提升为一个 LEFT JOIN,并追加 0 列的占位 JOIN RTE,joinaliasvars=NIL)。任一去掉都不崩,与数据量、列类型、是否 B 兼容无关。

补充:set rewrite_rule=''(显示 none)仍然崩,说明与 MAGIC_SET 无关,只是 disable_pullup_expr_sublink 这个开关能剪掉该路径。


修复commit(AI)

已修复,修复提交是 dfb53df30

上游提交(opengauss/openGauss-server,Gitee):
dfb53df3074ccab75d78fc6e9b5c32b962845052 — 作者 wangfeihuo,2024-09-26,标题“修复pullup导致的core问题”,并入 master 的 PR !6476(merge 提交 3c8307560)。
6.0.0 回合(cherry-pick):da08958ae1a64b52ea88b7a40a59652fe912aeb8,PR !6477(merge e0c591eea)。

改动只有 7 行删除,落在 src/gausskernel/optimizer/plan/subselect.cpp 的 convert_ORCLAUSE_to_join():

// 删除前(有 bug)
if (notNullExpr != NULL) {
    notNullAnd = make_and_qual(notNullAnd, notNullExpr);   // ← 删
    replace = true;
}
if (notNullAnd != NULL) {
    or_clause = (BoolExpr*)replace_node_clause(or_clause, clause, notNullAnd, RNC_RECURSE_AGGREF); // ← 删
}

即:不再把被提升子链接所在的 OR 分支整体替换成 “not null” 表达式。上一版 5.0.1 的那次替换会留下指向“0 列 JOIN RTE”的 Var,随后 flatten_join_alias_vars_mutator → list_nth(NIL, 0) 崩溃。这与我们 core 里观察到的 varno=12/14(两个 0 列 JOIN RTE)、si_addr=0x4 完全对应。

同一函数族里还有一个更早的半修复 3f02e6568(2024-05-07,“子查询中使用orderby数据丢失问题”),它只改了 convert_OREXPR_to_join,去掉了 notNullTest 的返回,不构成完整修复。

该提交自带回归用例,与你的场景同族

src/test/regress/sql/sublink_pullup_enhance.sql 新增:

select count(0) from (
  select t1.* from t1
  where (((select count(1) from t2 where t2.c1=t1.c1) +
          (select count(1) from t3 where t3.c1=t1.c1)) \> 0
         or t1.c1 = 5)
) tmp_count;

派生表 + OR + 同一表达式里两个相关标量子查询,就是这次的形状。

我在本机做的版本对照(实测)

版本构建日期该 SQL 结果
openGauss 5.0.1 (33b035fd)2023-12-15崩溃(日志:connection to server was lost)
openGauss 5.1.0 (b5a8d5b0)2023-09-28崩溃
openGauss-lite 6.0.0 (aee4abd5)2024-09-29正常,返回 count=3;我们的最小用例也正常
openGauss 7.0.0-RC3 (3700f950)2026-07-27正常

即:上游这条回归用例的 SQL,在 5.0.1 上照样把 DN 打挂 —— 直接证明该提交修的就是我们这个 bug。

5.x 系列没有回合,这一点很重要

按 tag 逐个核对 subselect.cpp 里 notNullAnd / notNullTest 是否残留:

v5.0.0 v5.0.2 v5.0.3 v5.0.5 v5.1.0  → 仍有(4 / 3 处),即未修复
v6.0.0 v6.0.5 v6.0.6 v7.0.0          → 已无,即已修复

分支 HEAD origin/5.0.0、origin/5.1.0 也仍是老代码。结论:所有 5.0.x / 5.1.0 发布版都没有这个修复,你手上的 5.0.1 无法通过打小补丁消除,只能升级到 6.0.0 及以上(建议 6.0.5/6.0.6 或 7.0.0),或升级前用会话级规避。

另:2025-08-28 还有一条同族后续修复 009c009b9 “fix count subquery with or sublink pullup”(仅 v7.0.0),去掉 isnull 参数传递,属于该路径的第二处缺陷。6.0.x 若遇到同族变体,需评估这条是否也要回合。

给你的可执行结论

  1. 根治:升级 ≥ 6.0.0;升级前用 set rewrite_rule='disable_pullup_expr_sublink';(已实测可绕过,见 /home/og501/repro_coredump_og501/),或改写 SQL 避开 OR 中的双标量子查询。
  2. 报障时可直接引用 dfb53df30 / PR !6476 / !6477,并说明 5.x 未回合,要求给出 5.0.1 的修复版本或回合补丁。
  3. 需要的证据材料(core、ffic 日志、最小用例、版本对照表)都已在机上的 repro_coredump_og501 目录。

总结

AI回复的这些内容,我核实了,的确都是对的。对于这种可复现的开源数据库内核BUG,本地部署的flash级别模型都可以轻松诊断定位。(本文有点水,一次普普通通的常规AI使用罢了)

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
博主关闭了所有页面的评论