Debugging OceanBase: GDB, Debug-Info Packages, Logging, SQL Trace and Debug Sync 📅 发布时间:2026/9/15 15:32:46 👁 浏览次数: Debugging OceanBase: GDB, Debug-Info Packages, Logging, SQL Trace and Debug Sync【免费下载链接】oceanbaseOceanBase is the unified distributed database for the AI era — open-source, multi-model, one engine for your most demanding workloads.项目地址: https://gitcode.com/GitHub_Trending/oc/oceanbaseOceanBase 是一个 C 实现的高性能分布式数据库进程内部线程众多、状态机复杂调试方式也与普通单机程序有显著差异。本文基于 docs/docs/en/debug.md 整理出完整的调试方法论从 GDB 附加进程与 debug-info 符号包的使用到日志埋点与检索、SQL 全链路 Trace再到专为分布式场景设计的 Debug Sync 同步点机制。读完本文你将掌握针对 observer 进程从定位线程到在代码任意位置挂起并观察的完整实战技能并了解每类手段的适用场景与限制。调试前的准备优先使用 Debug 模式构建官方文档强烈建议以 debug 模式构建 OceanBase。Debug 模式保留了完整符号与断言信息gdb 附加后可以直接看到函数名、参数与源码行号而 Release 模式RelWithDebInfo构建产物通常只保留行号级别的信息需要额外配合 debug-info 包才能获得良好调试体验。无论采用哪种方式都请先确认你已经基于源码构建并部署了 observer进程名为observer后续所有调试手段都围绕该进程展开。GDB附加到运行中的 observer 进程GDB 功能强大但官方文档明确指出GDB 调试 OceanBase 难度较大且场景受限。原因是 GDB 会挂起整个进程而 OceanBase 依赖心跳heartbeat与各副本协同才能正常工作进程被长时间挂起会引发选主、超时等一系列连锁反应。因此 GDB 仅推荐用于单个 observer 进程、单个线程的低风险调试多线程/多副本场景更推荐日志手段。查找进程 IDps -ef | grep observer或pidof observer附加进程gdb observer pid附加成功后可正常设置断点、打印变量。GDB 本身的使用方式与调试其他 C 程序一致不再赘述。使用 debug-info 包调试 RPM 部署的 OceanBase如果你的 observer 是通过 RPM 包部署的例如生产环境二进制中不包含调试符号直接 gdb 附加后无法获得源码与参数信息。此时需要先获取并加载对应的 debug-info 包。官方建议优先采用加载load而非安装installdebug-info 包系统里可能同时存在多个版本的 debug-info 包安装后难以清理。第一步获取版本 revision在 observer 运行目录下执行clusters/local/bin [83] $ ./observer -V ./observer -V observer (OceanBase_CE 4.1.0.1) REVISION: 102000042023061314-43bca414d5065272a730c92a645c3e25768c1d05 BUILD_BRANCH: HEAD BUILD_TIME: Jun 13 2023 14:26:23 BUILD_FLAGS: RelWithDebInfo BUILD_INFO: Copyright (c) 2011-2022 OceanBase Inc.如果直接执行报动态库缺失错误./observer: error while loading shared libraries: libmariadb.so.3: cannot open shared object file: No such file or directory说明需要手动指定依赖库路径改用如下命令LD_LIBRARY_PATH../lib:$LD_LIBRARY_PATH ./observer -V输出中REVISION的第一段102000042023061314就是后续在 rpm 站点搜索 debug-info 包的关键标识。第二步下载 debug-info 包在官方社区版 rpm 镜像站点上按照发行版与 CPU 架构选择对应目录分别提供 el7/el8 的 x86_64 与 aarch64 架构目录用 revision 值搜索包名形如oceanbase-ce-debuginfo-4.1.0.1-102000042023061314.distro.arch.rpm的安装包第三步从 rpm 中解压 debug-info 文件rpm2cpio oceanbase-ce-debuginfo-4.1.0.1-102000042023061314.el7.x86_64.rpm | cpio -div解压后得到如下目录结构~/tmp/debug-info [83] $ tree -a . └── usr └── lib └── debug ├── .build-id │ └── ee │ ├── f87ee72d228069aab083d8e6d2fa2fcb5c03f2 - ../../../../../home/admin/oceanbase/bin/observer │ └── f87ee72d228069aab083d8e6d2fa2fcb5c03f2.debug - ../../home/admin/oceanbase/bin/observer.debug └── home └── admin └── oceanbase └── bin └── observer.debug其中usr/lib/debug/home/admin/oceanbase/bin/observer.debug就是我们需要的符号文件.build-id目录下则是用于 gdb 自动查找符号的 build-id 软链接。第四步附加进程或打开 core 文件# 附加进程 gdb ./observer pidof observer或# 打开 coredump 文件 gdb ./observer coredump file name未加载符号时gdb 会提示(No debugging symbols found ...)此时执行bt只能看到地址与函数名、拿不到源码行号和参数(gdb) bt #0 0x00007fb6e9c36d62 in pthread_cond_timedwaitGLIBC_2.3.2 () from /lib64/libpthread.so.0 #1 0x00007fb6f9f44862 in ob_pthread_cond_timedwait () #2 0x00007fb6eee8d206 in oceanbase::common::ObThreadCond::wait_us(unsigned long) () #3 0x00007fb6f34b21c8 in oceanbase::observer::ObUniqTaskQueueoceanbase::observer::ObServerSchemaTask, oceanbase::observer::ObServerSchemaUpdater::run1() () #4 0x00007fb6f9f44259 in oceanbase::lib::Threads::run(long) () #5 0x00007fb6f9f40aca in oceanbase::lib::Thread::__th_start(void*) ()第五步加载 debug-info 符号并重新调试在 gdb 内加载符号文件(gdb) symbol-file usr/lib/debug/home/admin/oceanbase/bin/observer.debug Reading symbols from usr/lib/debug/home/admin/oceanbase/bin/observer.debug...建议使用 debug-info 文件的完整路径避免相对路径导致加载失败。再次执行bt即可看到完整的源码位置、函数参数和模板实例化信息(gdb) bt #0 0x00007fb6e9c36d62 in pthread_cond_timedwaitGLIBC_2.3.2 () from /lib64/libpthread.so.0 #1 0x00007fb6f9f44862 in ob_pthread_cond_timedwait (__cond0x7fb6fb1d5340, __mutex0x7fb6fb1d5318, __abstime0x7fb6b3ed41d0) at deps/oblib/src/lib/thread/ob_tenant_hook.cpp:124 #2 0x00007fb6eee8d206 in oceanbase::common::ObThreadCond::wait_us (thisoptimized out, time_us140422679606016) at deps/oblib/src/lib/lock/ob_thread_cond.cpp:106 #3 0x00007fb6f34b21c8 in oceanbase::common::ObThreadCond::wait (this0x7fb6fb1d5310, time_ms200) at deps/oblib/src/lib/lock/ob_thread_cond.h:69 #4 oceanbase::observer::ObUniqTaskQueueoceanbase::observer::ObServerSchemaTask, oceanbase::observer::ObServerSchemaUpdater::run1 ( thisoptimized out) at src/observer/ob_uniq_task_queue.h:417从堆栈中可以清晰地看到ObThreadCond::wait等待 200ms 的调用发生在 ob_uniq_task_queue.h这正是ObUniqTaskQueue线程池run1()中ObServerSchemaUpdater处理 schema 任务的阻塞点。符号加载让排查从猜测变成了定位。Logging最常用、覆盖面最广的调试手段官方文档明确推荐Logging 是调试 OceanBase 最常用的方式易用且适用于绝大多数场景。常规做法是在代码中埋点打印变量 → 重新构建并部署 → 在日志中观察输出。如何在代码中添加日志在源码中直接使用日志宏例如LOG_DEBUG(insert sql generated, K(insert_sql));LOG_DEBUG是打印 DEBUG 级别日志的宏。与printf/fprintf风格不同OceanBase 日志宏的第一个参数是描述信息字符串后续参数通常是K(变量名)。K宏会自动展开为变量名, 变量值的键值对例如K(insert_sql)展开为insert_sql, insert_sql最终在日志中输出形如insert_sql...的内容避免手写格式串出错。日志宏与级别速查结合 docs/docs/en/logging.md日志级别与对应宏如下级别宏说明DEBUGLOG_DEBUG开发调试日志TRACELOG_TRACE链路追踪日志INFOLOG_INFO系统状态变更日志WARNLOG_DBA_WARN面向 DBA服务可用但行为不符合预期ERRORLOG_DBA_ERROR面向 DBA服务不可用需 DBA 介入恢复WDIAGLOG_WARN告警诊断辅助故障排查、预期内失败EDIAGLOG_ERROR错误诊断非预期逻辑错误通常是程序缺陷除K外还常用以下参数宏定义见deps/oblib/src/lib/utility/ob_log_module.h宏示例说明K_K_(consistency_level)打印成员变量自动在变量名后补_KRKR(ret)同时打印错误码与错误码名称KPKP(plan)以十六进制打印指针值KPCKPC(session)指针为空输出 NULL否则调用其to_stringKTIMEKTIME(cur_time)微秒时间戳转字符串KPHEXKPHEX(buf, 20)以十六进制打印缓冲区内容KERRMSGKERRMSG输出系统错误码信息如何检索日志日志存放在home_path目录下的 log 子目录中即 observer 安装路径下的 log 目录可用grep检索。典型的一行日志如下[2023-07-05 16:40:42.635136] INFO [SQL.EXE] explicit_start_trans (ob_sql_trans_control.cpp:194) [88022][T1003_ArbSer][T1003][YD9F97F000001-0005FFB71FCF95C7-0-0] [lt42] start_trans(ret0, tx_id{txid:2118151}, session{this:0x7ff2663d6188, id:1, tenant:sys, tenant_id:1, effective_tenant:sys, effective_tenant_id:1003, database:oceanbase, user:root%, consistency_level:3, session_state:0, autocommit:true, tx:0x7ff26b0e4300}, read_onlyfalse, ctx.get_execution_id()18446744073709551615)日志字段从左到右依次为字段示例含义时间戳[2023-07-05 16:40:42.635136]日志打印时间微秒精度日志级别INFO日志级别模块名[SQL.EXE]所属模块主模块.子模块函数名explicit_start_trans打印日志的函数代码位置(ob_sql_trans_control.cpp:194)文件名与行号线程标识[88022][T1003_ArbSer]线程 ID 与线程名租户 ID[T1003]租户 IDTrace ID[YD9F97F000001-0005FFB71FCF95C7-0-0]单条 SQL 请求的全局唯一 ID打印开销[lt42]上一条日志打印耗时微秒其中Trace ID 是每条 SQL 请求的唯一标识按 Trace ID 检索即可拿到某条 SQL 从进入到返回的全链路日志是排查单请求问题的利器。日志调试实用技巧获取上一条 SQL 的 Trace IDselect last_trace_id();动态调整日志级别set ob_log_leveldebug;放开日志流量控制如果找不到自己的日志很可能是被日志流量控制限流丢弃了可执行alter system set syslog_io_bandwidth_limit1G; alter system set diag_syslog_per_error_limit1000;其中syslog_io_bandwidth_limit控制日志磁盘 IO 带宽上限默认 30MB限流日志含REACH SYSLOG RATE LIMIT关键字diag_syslog_per_error_limit控制每种错误码每秒 WDIAG 日志条数上限默认 200超限日志含Throttled WDIAG logs in last second关键字实现细节可参考ObSyslogPerErrLimiter::do_acquire。同步打印日志异步日志默认开启可能导致日志延迟落盘调试时可强制同步alter system set enable_async_syslogFalse;在日志中打印调用栈在日志宏中追加K(lbt())即可输出当前调用栈地址序列LOG_DEBUG(insert sql generated, K(insert_sql), K(lbt()));输出形如lbt()0x14371609 0xe4ce783 0x54fd9b6 0x54ebb1b 0x905e62e 0x92a4dc8 0x905df11 0x905dc94 0x13d2278e 0x13d22be3 0x6b10b81 0x6b0f0f7 0x62e2491 0x10ff6409 0x1475f87a 0x10ff6428 0x1475f1c2 0x1476ba83 0x14767fb5 0x14767ae8 0x7ff340250e25 0x7ff33fd0ff1dlbt()是 OceanBase 在 ob_backtrace.cpp 中提供的取栈函数内部通过ob_backtrace采集当前栈地址。拿到地址后可用addr2line解析为符号addr2line -pCfe ./bin/observer 0x14371609 0xe4ce783 0x54fd9b6 0x54ebb1b 0x905e62e 0x92a4dc8 0x905df11 0x905dc94 0x13d2278e 0x13d22be3 0x6b10b81 0x6b0f0f7 0x62e2491 0x10ff6409 0x1475f87a 0x10ff6428 0x1475f1c2 0x1476ba83 0x14767fb5 0x14767ae8 0x7ff340250e25 0x7ff33fd0ff1d解析结果示例oceanbase::common::lbt() at /home/distcc/tmp/./deps/oblib/src/lib/utility/ob_backtrace.cpp:130 (discriminator 2) operator() at /home/distcc/tmp/./src/sql/session/ob_basic_session_info.cpp:599 (discriminator 2) oceanbase::sql::ObBasicSessionInfo::switch_tenant(unsigned long) at /home/distcc/tmp/./src/sql/session/ob_basic_session_info.cpp:604 oceanbase::observer::ObInnerSQLConnection::switch_tenant(unsigned long) at /home/distcc/tmp/./src/observer/ob_inner_sql_connection.cpp:1813 (discriminator 2) ... oceanbase::lib::Thread::run() at /home/distcc/tmp/./deps/oblib/src/lib/thread/thread.cpp:162 oceanbase::lib::Thread::__th_start(void*) at /home/distcc/tmp/./deps/oblib/src/lib/thread/thread.cpp:312 ?? ??:0这样就能在不打断进程的前提下把热点路径的调用链完整还原出来。SQL Trace一键诊断慢查询的执行阶段耗时对慢 SQL无需改代码即可通过 SQL Trace 查看每个执行阶段的耗时分布。首先开启 trace 开关4.x 版本set ob_enable_show_trace1;然后执行待诊断的 SQLselect * from t, t1 where t.idt1.id;执行完毕后用show trace查看obclient show trace; ----------------------------------------------------------------------------------- | Operation | StartTime | ElapseTime | ----------------------------------------------------------------------------------- | com_query_process | 2023-07-06 15:30:49.907532 | 9.547 ms | | └── mpquery_single_stmt | 2023-07-06 15:30:49.907552 | 9.506 ms | | ├── sql_compile | 2023-07-06 15:30:49.907615 | 6.605 ms | | │ ├── pc_get_plan | 2023-07-06 15:30:49.907658 | 0.024 ms | | │ └── hard_parse | 2023-07-06 15:30:49.907763 | 6.421 ms | | │ ├── parse | 2023-07-06 15:30:49.907773 | 0.119 ms | | │ ├── resolve | 2023-07-06 15:30:49.907952 | 0.780 ms | | │ ├── rewrite | 2023-07-06 15:30:49.908857 | 1.320 ms | | │ ├── optimize | 2023-07-06 15:30:49.910209 | 3.002 ms | | │ ├── code_generate | 2023-07-06 15:30:49.913243 | 0.459 ms | | │ └── pc_add_plan | 2023-07-06 15:30:49.914016 | 0.140 ms | | └── sql_execute | 2023-07-06 15:30:49.914239 | 2.675 ms | | ├── open | 2023-07-06 15:30:49.914246 | 0.217 ms | | ├── response_result | 2023-07-06 15:30:49.914496 | 1.956 ms | | │ └── do_local_das_task | 2023-07-06 15:30:49.914584 | 0.862 ms | | └── close | 2023-07-06 15:30:49.916474 | 0.415 ms | | ├── close_das_task | 2023-07-06 15:30:49.916486 | 0.037 ms | | └── end_transaction | 2023-07-06 15:30:49.916796 | 0.064 ms | ----------------------------------------------------------------------------------- 18 rows in set (0.01 sec)如上例所示optimize阶段耗时 3ms 是编译期的明显瓶颈占比接近一半而hard_parse6.421ms说明未命中计划缓存可据此针对性优化如开启 plan cache 或使用绑定变量。Trace 结果以树形结构呈现 SQL 从解析、优化到执行、提交的完整时间线无需阅读源码即可快速定位慢在哪个环节。Debug Sync在代码任意位置安全地挂起线程为什么需要 Debug Syncgdb 附加会挂起整个进程而 OceanBase 依赖心跳机制维持集群正常运转进程长时间挂起会导致租户无主、选举超时。为此 OceanBase 提供了Debug Sync 同步点机制在代码中埋入同步点后只有命中该点的特定线程会挂起等待进程其余部分照常运行此时你可以安全地用 gdb 附加进程或执行 SQL 获取现场信息调试完成后发送信号放行该线程。Debug Sync 在 Release 模式同样生效因此也可用于生产环境。第一步在代码中定义调试同步点打开 ob_debug_sync_point.h在宏OB_DEBUG_SYNC_POINT_DEF中追加你的同步点定义#define OB_DEBUG_SYNC_POINT_DEF(ACT) \ ACT(INVALID_DEBUG_SYNC_POINT, 0) \ ACT(NOW,) \ ACT(MAJOR_FREEZE_BEFORE_SYS_COORDINATE_COMMIT,) \ ACT(BEFORE_REBALANCE_TASK_EXECUTE,) \ ACT(REBALANCE_TASK_MGR_BEFORE_EXECUTE_OVER,) \ ACT(UNIT_BALANCE_BEFORE_PARTITION_BALANCE,) \ ACT(BEFORE_UNIT_MANAGER_LOAD,) \ ...该宏通过DECLARE_ENUM/DEFINE_ENUM_FUNC自动生成ObDebugSyncPoint枚举及名称映射见 ob_debug_sync_point.cpp当前仓库已内置数百个覆盖合并、迁移、备份恢复、DDL、负载均衡等流程的同步点可直接复用。第二步在目标函数中埋入同步点在需要调试的函数中调用DEBUG_SYNC(...)宏例如int ObRootService::do_restart() { int ret OB_SUCCESS; const int64_t tenant_id OB_SYS_TENANT_ID; SpinWLockGuard rs_list_guard(broadcast_rs_list_lock_); ... DEBUG_SYNC(BEFORE_UNIT_MANAGER_LOAD); ... }同一同步点可放置在任意多个位置。仓库中大量业务代码已埋点例如 ob_archive_checkpoint.cpp 中的DEBUG_SYNC(BEFORE_UPDATE_PIECE_TO_ACTIVE)、ob_kv_storecache.cpp 中的DEBUG_SYNC(BEFORE_BACKGROUND_WASH)可作为埋点范式参考。第三步开启 Debug Sync 总开关Debug Sync 默认关闭通过debug_sync_timeout配置项开启该值为 0 时关闭大于 0 时启用alter system set debug_sync_timeout100000s;注意debug_sync_timeout的单位是微秒microsecond。第四步激活指定的同步点用会话级变量ob_global_debug_sync激活目标同步点set ob_global_debug_sync BEFORE_UNIT_MANAGER_LOAD wait_for signal_name execute 10000;语法说明wait_for signal_name命中该同步点的线程将等待名为signal_name的信号execute 10000该动作最多执行 10000 次后自动失效execute控制生效次数。此后当目标线程执行到该同步点时便会挂起等待此时即可用 gdb 附加进程或执行 SQL 查询现场状态。第五步发送信号放行线程set ob_global_debug_sync now signal signal_name; -- 或 set ob_global_debug_sync now broadcast signal_name;signal唤醒单个等待线程broadcast唤醒所有等待该信号的线程。收到信号后挂起的线程继续执行。第六步清理并关闭调试结束后务必清理同步点并关闭总开关-- 清理指定同步点 set ob_global_debug_sync BEFORE_UNIT_MANAGER_LOAD clear; -- 关闭 Debug Sync 总开关 alter system set debug_sync_timeout0;Debug Sync 的底层原理从源码结构看ob_debug_sync.h、ob_debug_sync.cppDebug Sync 由三部分组成ObDebugSyncAction描述一个同步点动作包含sync_point_同步点枚举、timeout_等待超时、execute_剩余生效次数、signal_/broadcast_/wait_事件名等字段并实现了is_valid()校验与序列化ObDSActionArray/ObDSSessionActions动作的存储容器fetch_action每次命中后execute_自减归零即自动清除该动作这正是execute 10000生效次数限制的实现ObDSEventControl基于condition_variableObThreadCond实现的事件控制维护signal_cnt_/waiter_cnt_计数提供signal/broadcast/wait原语——线程执行到同步点时在此等待收到信号后继续执行。也就是说整个机制可以概括为DEBUG_SYNC宏在埋点处通过条件变量等待事件ob_global_debug_sync会话变量负责注册动作与发送信号debug_sync_timeout控制开关。理解这三层关系后你完全可以为任意新流程设计自己的同步点实现定点挂起、按需放行的精细化调试。小结如何选择调试手段手段适用场景限制GDB单进程单线程问题、core 文件分析挂起整个进程受心跳机制约束debug-info 包RPM 部署场景的符号还原需按 revision 匹配符号包Logging绝大多数场景多线程/多副本问题需要重新编译部署后观察SQL Trace慢查询、执行阶段耗时分析仅覆盖 SQL 执行路径Debug Sync定点挂起特定线程配合 gdb/SQL 观察需改代码定义同步点并重新编译推荐的组合拳是先用 SQL Trace 与日志缩小问题范围再用 Debug Sync 精确定位到线程挂起点最后用 GDB配合 debug-info 包做符号级分析。这套方法论同样适用于基于源码自行构建的调试场景相关同步点定义与埋点示例可直接在 src/share/ob_debug_sync_point.h 与src/share/ob_debug_sync.cpp中查阅。【免费下载链接】oceanbaseOceanBase is the unified distributed database for the AI era — open-source, multi-model, one engine for your most demanding workloads.项目地址: https://gitcode.com/GitHub_Trending/oc/oceanbase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考