公司动态

达梦数据库堆栈分析

📅 2026/8/27 9:13:45
达梦数据库堆栈分析
一、前言达梦服务端只有一个进程dmserver里面很多线程执行 SQL 的、写日志的、做检查点的、在等锁的。其中一个线程出问题整个实例都可能没了。堆栈就是每个线程“怎么一层层调用到当前位置”的记录。日志能看到 停库前后打印了什么。堆栈看的是 每个线程当时执行到哪一层函数。Core 文件则是进程退出那一瞬间的内存镜像事后用 gdb 打开。当数据库突然连不上、进程消失、CPU 很高却没有任何结果返回时就要用堆栈说明。达梦 自带dmrdc可以从 Core 找到当时还在跑的 SQL 。二、堆栈怎么读调用栈是一层层函数调用。gdb 里每一层叫一帧#0 aagr2_exec_after_fetch () #1 ...... #11 clone ()#0是最里面、正在执行的函数。数字越大越靠近线程入口。达梦线程栈底经常是clone()表示这个线程是从进程里拉起来的。习惯先看#0现在停在哪再从下面往上扫看是谁把它调进来的。info threads里会有LWP xxxx。可以把它当成这个线程在操作系统里的编号。dmrdc抠出来的 SQL 前面也常带这个号。堆栈和 SQL 要对上靠的就是它。达梦是单进程多线程一份 Core 里会有几十上百条栈不能只看一条。三、故障处理里它排在哪先定性再定位能处理就处理处理不了再构造重现、把栈交给研发。实例还能登录时先看网络、内存、CPU、磁盘再查V$SESSIONS、V$LOCK、V$TRX。有会话卡住先关连接SELECTSESS_ID,THRD_ID,STATE,SQL_TEXTFROMV$SESSIONS;-- 确认后再关未提交事务会回滚SP_CLOSE_SESSION(sess_id);进程在但完全没响应、关会话也没用才用信号把 Core 留下ps-ef|grep-vgrep|grepdmserverkill-SIGSEGVPIDkill -11和这条是一类信号。不要用kill -9进程来不及按这套流程写 Core。副作用是再启动可能很慢也有起不来的情况。ps已经看不到dmserver了就去翻运行日志和 Core。没有 Core再去跟踪日志和dmesg。四、两种 Core服务端 Core 分成两类4.1 库自己停栈上常能看到dm_sys_halt、dm_sys_halt_low()。原因以运行日志 Halt 附近为准。文档举过 redo 环装不下大事务Redo log try flush over space优先看事务有多大、日志相关参数。4.2 被错误逼停非法地址、空指针之类。日志往往打着打着突然没了。这类经常能抠出正在执行的 SQL但 SQL 在场不等于它就是原因还跟当时的数据、REDO、UNDO、缓冲区有关。gdb 打开 Core 时开头那行信号Program terminated with signal SIGSEGV, Segmentation fault. Program terminated with signal SIGABRT, Aborted.SIGSEGV是非法访存培训取栈用的就是它。SIGABRT多半是进程自己 abort更要对着日志看。自己在实验机kill -11做出来的 Core只能练流程不能当成产品缺陷。五、没有 Core的情况先看这几项ulimit-ccat/proc/pid/limitscat/proc/sys/kernel/core_pattern gdb-versionulimit -c为 0 就是不许生成。limits.conf里可以给dmdba放开dmdba soft core unlimited dmdba hard core unlimited终端里执行ulimit -c unlimited只对当前会话有效。用 systemd 拉起的服务还要在服务里设LimitCORE实际生效看/proc/pid/limits。Core 上限如果小于当时dmserver的虚拟内存文件是截断的。core_pattern如果是下面这种文件不在当前目录/dmcore/core-%e-%t-%p-%u.core%e进程名%p是 PID。gdb 必须用出事那一套安装里的dmserver版本、补丁都要对gdb /dmdbms/bin/dmserver /dmcore/core-dmserver-22976.core实践中个人认为更多是 RELEASE就走dmrdc有 symbols 包再按说明加载。六、分析步骤运行日志看停库前后。Core 是当时内存。gdb 打出每个线程的栈。dmrdc给出当时的 SQL。用 LWP 把后两样对上。6.1 日志实例日志按时间搜[ERROR]、[FATAL]、Halt、signal。有 Halt原因听日志栈用来对调用链。只有类似下面这句就主要靠 Coresigterm_handler receive signal 11dmesg或/var/log/messages里如果是 OOM killer 杀掉dmserver先查内存和系统限制。6.2 gdbgdb$DM_HOME/bin/dmserver /path/to/core进去之后set pagination off info threads bt*标出来的是当前线程多数时候就是崩溃那条。bt和where一样。线程很多把全部栈导进文件set logging file /tmp/all_bt.txt set logging on thread apply all bt set logging off quit也可以不进交互gdb-batch\-exset pagination off\-exthread apply all bt\$DM_HOME/bin/dmserver /path/to/core\/tmp/all_bt.txt全线程栈里大量pthread_cond_wait、pthread_cond_timedwait很常见线程在等任务。真正要看的是带*的、日志里点名的dm_sql_thd以及能和dmrdc的 LWP 对上的那些。比如锁等待现场某个 LWP 可能接近Thread 106 (LWP 23555): #0 pthread_cond_timedwait () #1 os_event2_wait_timeout_low () #2 trx4_waiting_timeout () #3 trx4_waiting_interval () #4 trx4_waiting () #5 nupd2_exec_clu_update_check_rec_visible () ... #9 nupd2_exec_update () ... #17 uthr_db_main_for_sess ()trx4_waiting在等事务nupd2_exec_update在做更新。还不能下结论得看dmrdc里 23555 对应哪条 SQL。6.3 DEBUG 版从栈里取 SQLinfo threads thread 1 bt栈里如果有ntsk_process_exec、ntsk_process_prepare、ntsk_process_prepare_and_execframe 5 set print elements 0 p sess-sqls如果有vm_run_plnframe 8 p pln-sqlstrset print elements 0是防止 SQL 太长被截断。帧号按自己bt的实际输出改。6.4 RELEASE 版用 dmrdccd$DM_HOME/bin ./dmrdcSFILEcore文件名结果一般在 core 同目录文件名是核心文件名_tmp。也可以指定输出./dmrdcsfile/dmcore/core.31820dfile/tmp/core_31820.txt参数大小写看本机帮助。Analysing: 当前偏移/文件总大小是进度。输出里可能是这种形式!#%*^$[23555]:UPDATE T_CORE_LOCK SET C1 C1 1 WHERE ID 1; !#%*^$[23384]:UPDATE T_CORE_LOCK SET C1 C1 WHERE ID 1;方括号里的就是 LWP。回到 gdb 里找LWP 23555SQL 和栈就能对上。同一会话更早的语句用跟踪日志里的 sess、trxid 往上翻。官方也写了抠出来的 SQL 只说明它当时在不等于根因。最好在同版本测试库上再跑一遍。6.5 没有 Core 时内核日志里找到线程号比如dm_sql_thd、9190跟踪日志搜thrd:9190取宕机前最后一条。这要求故障前跟踪日志是开的SVR_LOG、SQL_TRACE_MASK。七、实例活着的情况运行中取栈有把库打挂的风险。pstackdmserver的PID/tmp/dmserver_pstack.txt某个会话对应的线程SELECTSESS_ID,THRD_ID,STATE,SQL_TEXTFROMV$SESSIONS;pstackTHRD_ID/tmp/one_thread.txtpstack依赖 gdb。attach 会把目标卡住用完要detachgdb (gdb) attach PID (gdb) info threads (gdb) thread apply all bt (gdb) detach (gdb) quit进程已经没了就只能看 Core 和日志。八、实验造一个锁等待会话 ADROPTABLEIFEXISTST_CORE_LOCK;CREATETABLET_CORE_LOCK(IDINTPRIMARYKEY,C1INT);INSERTINTOT_CORE_LOCKVALUES(1,100);COMMIT;UPDATET_CORE_LOCKSETC1C1WHEREID1;-- 先不 COMMIT会话 BUPDATET_CORE_LOCKSETC1C11WHEREID1;-- 会一直等 A然后ps-ef|grep-vgrep|grepdmserverkill-SIGSEGV1360cat/proc/sys/kernel/core_patternls-l/dmcore# 按实际路径gdb$DM_HOME/bin/dmserver /path/to/core# set pagination off# info threads# thread apply all btcd$DM_HOME/bin ./dmrdcSFILE/path/to/core以下为上机截图—