注册
达梦数据库堆栈分析
专栏/技术分享/ 文章详情 /

达梦数据库堆栈分析

🌌 2026/08/28 168 0 0
摘要

一、前言

达梦服务端只有一个进程 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$SESSIONSV$LOCKV$TRX。有会话卡住,先关连接:

SELECT SESS_ID, THRD_ID, STATE, SQL_TEXT FROM V$SESSIONS; -- 确认后再关,未提交事务会回滚 SP_CLOSE_SESSION(sess_id);

进程在但完全没响应、关会话也没用,才用信号把 Core 留下:

ps -ef | grep -v grep | grep dmserver kill -SIGSEGV <PID>

kill -11 和这条是一类信号。不要用 kill -9,进程来不及按这套流程写 Core。副作用是再启动可能很慢,也有起不来的情况。

ps 已经看不到 dmserver 了,就去翻运行日志和 Core。没有 Core,再去跟踪日志和 dmesg


四、两种 Core

服务端 Core 分成两类

4.1 库自己停

栈上常能看到 dm_sys_haltdm_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 -c cat /proc/<pid>/limits cat /proc/sys/kernel/core_pattern gdb -version

ulimit -c 为 0 就是不许生成。limits.conf 里可以给 dmdba 放开:

dmdba  soft  core  unlimited
dmdba  hard  core  unlimited

终端里执行 ulimit -c unlimited 只对当前会话有效。用 systemd 拉起的服务,还要在服务里设 LimitCORE,实际生效看 /proc/&lt;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]Haltsignal。有 Halt,原因听日志,栈用来对调用链。只有类似下面这句,就主要靠 Core:

sigterm_handler receive signal 11

dmesg/var/log/messages 里如果是 OOM killer 杀掉 dmserver,先查内存和系统限制。

6.2 gdb

gdb $DM_HOME/bin/dmserver /path/to/core

进去之后:

set pagination off
info threads
bt

* 标出来的是当前线程,多数时候就是崩溃那条。btwhere 一样。

线程很多,把全部栈导进文件:

set logging file /tmp/all_bt.txt
set logging on
thread apply all bt
set logging off
quit

也可以不进交互:

gdb -batch \ -ex "set pagination off" \ -ex "thread apply all bt" \ $DM_HOME/bin/dmserver /path/to/core \ > /tmp/all_bt.txt

全线程栈里大量 pthread_cond_waitpthread_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 版从栈里取 SQL

info threads
thread 1
bt

栈里如果有 ntsk_process_execntsk_process_preparentsk_process_prepare_and_exec

frame 5
set print elements 0
p sess->sqls

如果有 vm_run_pln

frame 8
p pln->sqlstr

set print elements 0 是防止 SQL 太长被截断。帧号按自己 bt 的实际输出改。

6.4 RELEASE 版用 dmrdc

cd $DM_HOME/bin ./dmrdc SFILE=<core文件名>

结果一般在 core 同目录,文件名是 核心文件名_tmp。也可以指定输出:

./dmrdc sfile=/dmcore/core.31820 dfile=/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 23555,SQL 和栈就能对上。同一会话更早的语句,用跟踪日志里的 sess、trxid 往上翻。

官方也写了:抠出来的 SQL 只说明它当时在,不等于根因。最好在同版本测试库上再跑一遍。

6.5 没有 Core 时

内核日志里找到线程号,比如 dm_sql_thd、9190,跟踪日志搜:

thrd:9190

取宕机前最后一条。这要求故障前跟踪日志是开的(SVR_LOGSQL_TRACE_MASK)。


七、实例活着的情况

运行中取栈有把库打挂的风险。

pstack <dmserver的PID> >> /tmp/dmserver_pstack.txt

某个会话对应的线程:

SELECT SESS_ID, THRD_ID, STATE, SQL_TEXT FROM V$SESSIONS;
pstack <THRD_ID> >> /tmp/one_thread.txt

pstack 依赖 gdb。attach 会把目标卡住,用完要 detach

gdb
(gdb) attach <PID>
(gdb) info threads
(gdb) thread apply all bt
(gdb) detach
(gdb) quit

进程已经没了就只能看 Core 和日志。


八、实验

造一个锁等待

会话 A:

DROP TABLE IF EXISTS T_CORE_LOCK; CREATE TABLE T_CORE_LOCK(ID INT PRIMARY KEY, C1 INT); INSERT INTO T_CORE_LOCK VALUES(1, 100); COMMIT; UPDATE T_CORE_LOCK SET C1 = C1 WHERE ID = 1; -- 先不 COMMIT

会话 B:

UPDATE T_CORE_LOCK SET C1 = C1 + 1 WHERE ID = 1; -- 会一直等 A

然后:

ps -ef | grep -v grep | grep dmserver kill -SIGSEGV <1360> cat /proc/sys/kernel/core_pattern ls -l /dmcore # 按实际路径
gdb $DM_HOME/bin/dmserver /path/to/core # set pagination off # info threads # thread apply all bt
cd $DM_HOME/bin ./dmrdc SFILE=/path/to/core

以下为上机截图:
01_ps_pid.png

02_vsessions.png

03_kill_core.png

04_gdb_thread1.png

05_gdb_lwp3098.png

06_dmrdc.png

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服